/** When an actual data segment was received; deals with request callbacks if necessary */ private void handleReceivedData(String data) { // deal with any queued callbacks first QueuedRequest request = null; // then check for any requests // (should release lock as soon as possible) synchronized (_lock) { if (_activeRequest != null) { request = _activeRequest; _activeRequest = null; } } if (request != null && request.timeout > 0) { // make sure it hasn't been too long i.e. timeout if (request.isExpired()) { _logger.debug("Active request has expired"); // fire the timeout handler _callbackHandler.handle(_timeoutCallback, _callbackErrorHandler); } else { // fire the response request's response handler request.setResponse(data); _callbackHandler.handle(request.responseHandler, data, _callbackErrorHandler); } } // ...then fire the 'received' callback next _callbackHandler.handle(_receivedCallback, data, _callbackErrorHandler); processQueue(); }
/** The main thread. */ private void begin() { while (!_shutdown) { // only need to set the thread state once here _threadStateHandler.handle(); try { connectAndRead(); } catch (Exception exc) { if (_shutdown) { // thread can gracefully exit return; } if (_recentlyConnected) _backoffTime = MIN_CONNETION_GAP; else { _backoffTime = Math.min(_backoffTime * 2, MAX_BACKOFF); _backoffTime = Math.max(MIN_CONNETION_GAP, _backoffTime); } long timeDiff = (System.nanoTime() - _lastSuccessfulConnection) / 1000000; if (timeDiff > _timeout) { // reset the timestamp _lastSuccessfulConnection = System.nanoTime(); _callbackHandler.handle(_timeoutCallback, _callbackErrorHandler); } _recentlyConnected = false; } Threads.wait(_lock, _backoffTime); } // (while) }
/** Expires and initiates requests in the queue (assumes not synced) */ private void processQueue() { // if any new requests are found QueuedRequest nextRequest = null; // if a timeout callback needs to be fired boolean callTimeout = false; synchronized (_lock) { // check if any active requests need expiring if (_activeRequest != null) { if (_activeRequest.responseHandler == null || _activeRequest.timeout <= 0) { // was a blind request or _activeRequest = null; } else if (_activeRequest.isExpired()) { _logger.debug("Active request has expired"); // timeout callback must be fired callTimeout = true; // clear active request _activeRequest = null; } else if (_activeRequest.isLongTermExpired()) { _logger.debug("Active request has long term expired"); callTimeout = true; _activeRequest = null; } else { // there is still an valid active request, // so just get out of here return; } } } // call the timeout callback, (there's an opportunity for request queue to be cleared by // callback handler) if (callTimeout) _callbackHandler.handle(_timeoutCallback, _callbackErrorHandler); // an active request might have come in synchronized (_lock) { if (_activeRequest != null) { // there's an active request, so leave it to play out return; } // record this for logging int longTermDropped = 0; try { for (; ; ) { // no active request, check for queued ones nextRequest = _requestQueue.poll(); if (nextRequest == null) { // no more requests either, so nothing more to do _logger.debug("No new requests in queue."); return; } _queueLength--; if (!nextRequest.isLongTermExpired()) break; // otherwise, continue to expire the long term queued longTermDropped++; } } finally { if (longTermDropped > 0) _logger.debug("(dropped {} long term queued requests.)", longTermDropped); } // set active request and start timeout *before* sending _activeRequest = nextRequest; nextRequest.startTimeout(); } // if the request has send data 'data' send now if (nextRequest.requestBuffer != null) sendBufferNow(nextRequest.requestBuffer, nextRequest.request, false); }
/** Establishes a socket and continually reads. */ private void connectAndRead() throws Exception { Socket socket = null; OutputStream os = null; try { socket = new Socket(); InetSocketAddress socketAddress = parseAndResolveDestination(_dest); socket.connect(socketAddress, CONNECT_TIMEOUT); _counterConnections.incr(); socket.setSoTimeout(_timeout); // 'inject' countable stream os = new CountableOutputStream(socket.getOutputStream(), _counterSendOps, _counterSendRate); // update flag _lastSuccessfulConnection = System.nanoTime(); synchronized (_lock) { if (_shutdown) return; _socket = socket; _outputStream = os; // connection has been successful so reset variables // related to exponential back-off _recentlyConnected = true; _backoffTime = MIN_CONNETION_GAP; } // fire the connected event _callbackHandler.handle(_connectedCallback, _callbackErrorHandler); // start reading if (_mode == Modes.LengthDelimitedRaw) readLengthDelimitedLoop(socket, _binaryStartFlag, _binaryStopFlag); else if (_mode == Modes.CharacterDelimitedText) readTextLoop(socket); else { // mode is 'UnboundedRaw' readUnboundedRawLoop(socket); } // (any non-timeout exceptions will be propagated to caller...) } catch (Exception exc) { // fire the disconnected handler if was previously connected if (os != null) Handler.tryHandle(_disconnectedCallback, _callbackErrorHandler); throw exc; } finally { // always gracefully close the socket and invalidate the socket fields synchronized (_lock) { _socket = null; _outputStream = null; } Stream.safeClose(socket); } }