package co.codewizards.cloudstore.ls.client.handler; import static co.codewizards.cloudstore.core.util.AssertUtil.*; import static co.codewizards.cloudstore.core.util.Util.*; import java.lang.ref.WeakReference; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import co.codewizards.cloudstore.core.Uid; import co.codewizards.cloudstore.core.dto.Error; import co.codewizards.cloudstore.ls.client.LocalServerClient; import co.codewizards.cloudstore.ls.core.dto.ErrorResponse; import co.codewizards.cloudstore.ls.core.dto.InverseServiceRequest; import co.codewizards.cloudstore.ls.core.dto.InverseServiceResponse; import co.codewizards.cloudstore.ls.core.dto.NullResponse; import co.codewizards.cloudstore.ls.rest.client.LocalServerRestClient; import co.codewizards.cloudstore.ls.rest.client.request.PollInverseServiceRequest; import co.codewizards.cloudstore.ls.rest.client.request.PushInverseServiceResponse; public class InverseServiceRequestHandlerThread extends Thread { private static final Logger logger = LoggerFactory.getLogger(InverseServiceRequestHandlerThread.class); private static final AtomicInteger nextThreadId = new AtomicInteger(); private volatile boolean interrupted; private final WeakReference<LocalServerClient> localServerClientRef; private final WeakReference<LocalServerRestClient> localServerRestClientRef; private final InverseServiceRequestHandlerManager inverseServiceRequestHandlerManager = InverseServiceRequestHandlerManager.getInstance(); private final Executor executor = Executors.newCachedThreadPool(); public InverseServiceRequestHandlerThread(final LocalServerClient localServerClient) { this.localServerClientRef = new WeakReference<LocalServerClient>(assertNotNull(localServerClient, "localServerClient")); this.localServerRestClientRef = new WeakReference<LocalServerRestClient>(assertNotNull(localServerClient.getLocalServerRestClient(), "localServerRestClient")); setName(getClass().getSimpleName() + '-' + nextThreadId.getAndIncrement()); setDaemon(true); } @Override public void interrupt() { // We use our own field instead of isInterrupted() to make absolutely sure, we end the thread. The isInterrupted() // flag may be reset by an InterruptedException, while our flag cannot be reset. interrupted = true; super.interrupt(); } @Override public boolean isInterrupted() { return interrupted || super.isInterrupted(); } @Override public void run() { int consecutiveErrorCounter = 0; while (! isInterrupted()) { try { final InverseServiceRequest inverseServiceRequest = getLocalServerRestClientOrFail().execute(new PollInverseServiceRequest()); if (inverseServiceRequest != null) executor.execute(new HandlerRunnable(inverseServiceRequest)); consecutiveErrorCounter = 0; } catch (Exception x) { logger.error(x.toString(), x); // Wait a bit before retrying (increasingly longer) in order to prevent the log from filling up too quickly. // We wait 1 second longer after each consecutive error up to a maximum of 1 minute. consecutiveErrorCounter = Math.min(60, ++consecutiveErrorCounter); try { Thread.sleep(consecutiveErrorCounter * 1000L); } catch (Exception y) { doNothing(); } } } } private class HandlerRunnable implements Runnable { private final InverseServiceRequest inverseServiceRequest; public HandlerRunnable(final InverseServiceRequest inverseServiceRequest) { this.inverseServiceRequest = assertNotNull(inverseServiceRequest, "inverseServiceRequest"); } @Override public void run() { assertNotNull(inverseServiceRequest, "inverseServiceRequest"); final Uid requestId = inverseServiceRequest.getRequestId(); assertNotNull(requestId, "inverseServiceRequest.requestId"); final LocalServerRestClient localServerRestClient = getLocalServerRestClientOrFail(); InverseServiceResponse inverseServiceResponse = null; try { @SuppressWarnings("unchecked") final InverseServiceRequestHandler<InverseServiceRequest, InverseServiceResponse> handler = inverseServiceRequestHandlerManager.getInverseServiceRequestHandlerOrFail(inverseServiceRequest); final LocalServerClient localServerClient = getLocalServerClientOrFail(); handler.setLocalServerClient(localServerClient); inverseServiceResponse = handler.handle(inverseServiceRequest); if (inverseServiceResponse == null) inverseServiceResponse = new NullResponse(requestId); if (!requestId.equals(inverseServiceResponse.getRequestId())) throw new IllegalStateException(String.format("Implementation error in %s: handle(...) returned a response with a requestId different from the request!", handler.getClass().getName())); } catch (final Exception x) { logger.warn("handleInverseServiceRequest: " + x, x); final ErrorResponse errorResponse = new ErrorResponse(requestId, new Error(x)); localServerRestClient.execute(new PushInverseServiceResponse(errorResponse)); } // Send this outside of the try-catch, because it might otherwise cause 2 responses for the same requestId to be delivered to the server. if (inverseServiceResponse != null) localServerRestClient.execute(new PushInverseServiceResponse(inverseServiceResponse)); } } private LocalServerClient getLocalServerClientOrFail() { final LocalServerClient localServerClient = localServerClientRef.get(); if (localServerClient == null) throw new IllegalStateException("LocalServerClient already garbage-collected!"); return localServerClient; } private LocalServerRestClient getLocalServerRestClientOrFail() { final LocalServerRestClient localServerRestClient = localServerRestClientRef.get(); if (localServerRestClient == null) throw new IllegalStateException("LocalServerRestClient already garbage-collected!"); return localServerRestClient; } }