static void close(OioDatagramChannel channel, ChannelFuture future) { boolean connected = channel.isConnected(); boolean bound = channel.isBound(); try { channel.socket.close(); if (channel.setClosed()) { future.setSuccess(); if (connected) { // Notify the worker so it stops reading. Thread currentThread = Thread.currentThread(); Thread workerThread = channel.workerThread; if (workerThread != null && currentThread != workerThread) { workerThread.interrupt(); } fireChannelDisconnected(channel); } if (bound) { fireChannelUnbound(channel); } fireChannelClosed(channel); } else { future.setSuccess(); } } catch (Throwable t) { future.setFailure(t); fireExceptionCaught(channel, t); } }
private void connect( OioDatagramChannel channel, ChannelFuture future, SocketAddress remoteAddress) { boolean bound = channel.isBound(); boolean connected = false; boolean workerStarted = false; future.addListener(ChannelFutureListener.CLOSE_ON_FAILURE); // Clear the cached address so that the next getRemoteAddress() call // updates the cache. channel.remoteAddress = null; try { channel.socket.connect(remoteAddress); connected = true; // Fire events. future.setSuccess(); if (!bound) { fireChannelBound(channel, channel.getLocalAddress()); } fireChannelConnected(channel, channel.getRemoteAddress()); String threadName = "Old I/O datagram worker (channelId: " + channel.getId() + ", " + channel.getLocalAddress() + " => " + channel.getRemoteAddress() + ')'; if (!bound) { // Start the business. workerExecutor.execute( new IoWorkerRunnable( new ThreadRenamingRunnable(new OioDatagramWorker(channel), threadName))); } else { // Worker started by bind() - just rename. Thread workerThread = channel.workerThread; if (workerThread != null) { try { workerThread.setName(threadName); } catch (SecurityException e) { // Ignore. } } } workerStarted = true; } catch (Throwable t) { future.setFailure(t); fireExceptionCaught(channel, t); } finally { if (connected && !workerStarted) { OioDatagramWorker.close(channel, future); } } }
@Override protected boolean scheduleWriteIfNecessary(final AbstractNioChannel<?> channel) { final Thread currentThread = Thread.currentThread(); final Thread workerThread = thread; if (currentThread != workerThread) { if (channel.writeTaskInTaskQueue.compareAndSet(false, true)) { boolean offered = writeTaskQueue.offer(channel.writeTask); assert offered; } if (!(channel instanceof NioAcceptedSocketChannel) || ((NioAcceptedSocketChannel) channel).bossThread != currentThread) { final Selector workerSelector = selector; if (workerSelector != null) { if (wakenUp.compareAndSet(false, true)) { workerSelector.wakeup(); } } } else { // A write request can be made from an acceptor thread (boss) // when a user attempted to write something in: // // * channelOpen() // * channelBound() // * channelConnected(). // // In this case, there's no need to wake up the selector because // the channel is not even registered yet at this moment. } return true; } return false; }
static void setInterestOps(OioDatagramChannel channel, ChannelFuture future, int interestOps) { // Override OP_WRITE flag - a user cannot change this flag. interestOps &= ~Channel.OP_WRITE; interestOps |= channel.getInterestOps() & Channel.OP_WRITE; boolean changed = false; try { if (channel.getInterestOps() != interestOps) { if ((interestOps & Channel.OP_READ) != 0) { channel.setInterestOpsNow(Channel.OP_READ); } else { channel.setInterestOpsNow(Channel.OP_NONE); } changed = true; } future.setSuccess(); if (changed) { synchronized (channel.interestOpsLock) { channel.setInterestOpsNow(interestOps); // Notify the worker so it stops or continues reading. Thread currentThread = Thread.currentThread(); Thread workerThread = channel.workerThread; if (workerThread != null && currentThread != workerThread) { workerThread.interrupt(); } } fireChannelInterestChanged(channel); } } catch (Throwable t) { future.setFailure(t); fireExceptionCaught(channel, t); } }
public void run() { channel.workerThread = Thread.currentThread(); final MulticastSocket socket = channel.socket; while (channel.isOpen()) { synchronized (channel.interestOpsLock) { while (!channel.isReadable()) { try { // notify() is not called at all. // close() and setInterestOps() calls Thread.interrupt() channel.interestOpsLock.wait(); } catch (InterruptedException e) { if (!channel.isOpen()) { break; } } } } ReceiveBufferSizePredictor predictor = channel.getConfig().getReceiveBufferSizePredictor(); byte[] buf = new byte[predictor.nextReceiveBufferSize()]; DatagramPacket packet = new DatagramPacket(buf, buf.length); try { socket.receive(packet); } catch (InterruptedIOException e) { // Can happen on interruption. // Keep receiving unless the channel is closed. continue; } catch (Throwable t) { if (!channel.socket.isClosed()) { fireExceptionCaught(channel, t); } break; } fireMessageReceived( channel, channel.getConfig().getBufferFactory().getBuffer(buf, 0, packet.getLength()), packet.getSocketAddress()); } // Setting the workerThread to null will prevent any channel // operations from interrupting this thread from now on. channel.workerThread = null; // Clean up. close(channel, succeededFuture(channel)); }
static void disconnect(OioDatagramChannel channel, ChannelFuture future) { boolean connected = channel.isConnected(); try { channel.socket.disconnect(); future.setSuccess(); if (connected) { // Update the worker's thread name to reflect the state change. Thread workerThread = channel.workerThread; if (workerThread != null) { try { workerThread.setName("Old I/O datagram worker (" + channel + ')'); } catch (SecurityException e) { // Ignore. } } // Notify. fireChannelDisconnected(channel); } } catch (Throwable t) { future.setFailure(t); fireExceptionCaught(channel, t); } }
private ChannelBuffer unwrap( ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer, int offset, int length) throws SSLException { ByteBuffer inNetBuf = buffer.toByteBuffer(offset, length); ByteBuffer outAppBuf = bufferPool.acquire(); try { boolean needsWrap = false; loop: for (; ; ) { SSLEngineResult result; synchronized (handshakeLock) { if (!handshaken && !handshaking && !engine.getUseClientMode() && !engine.isInboundDone() && !engine.isOutboundDone()) { handshake(); } try { result = engine.unwrap(inNetBuf, outAppBuf); } catch (SSLException e) { throw e; } final HandshakeStatus handshakeStatus = result.getHandshakeStatus(); handleRenegotiation(handshakeStatus); switch (handshakeStatus) { case NEED_UNWRAP: if (inNetBuf.hasRemaining() && !engine.isInboundDone()) { break; } else { break loop; } case NEED_WRAP: wrapNonAppData(ctx, channel); break; case NEED_TASK: runDelegatedTasks(); break; case FINISHED: setHandshakeSuccess(channel); needsWrap = true; break loop; case NOT_HANDSHAKING: needsWrap = true; break loop; default: throw new IllegalStateException("Unknown handshake status: " + handshakeStatus); } } } if (needsWrap) { // wrap() acquires pendingUnencryptedWrites first and then // handshakeLock. If handshakeLock is already hold by the // current thread, calling wrap() will lead to a dead lock // i.e. pendingUnencryptedWrites -> handshakeLock vs. // handshakeLock -> pendingUnencryptedLock -> handshakeLock // // There is also a same issue between pendingEncryptedWrites // and pendingUnencryptedWrites. if (!Thread.holdsLock(handshakeLock) && !pendingEncryptedWritesLock.isHeldByCurrentThread()) { wrap(ctx, channel); } } outAppBuf.flip(); if (outAppBuf.hasRemaining()) { ChannelBuffer frame = ChannelBuffers.buffer(outAppBuf.remaining()); frame.writeBytes(outAppBuf.array(), 0, frame.capacity()); return frame; } else { return null; } } catch (SSLException e) { setHandshakeFailure(channel, e); throw e; } finally { bufferPool.release(outAppBuf); } }
private ChannelFuture wrapNonAppData(ChannelHandlerContext ctx, Channel channel) throws SSLException { ChannelFuture future = null; ByteBuffer outNetBuf = bufferPool.acquire(); SSLEngineResult result; try { for (; ; ) { synchronized (handshakeLock) { result = engine.wrap(EMPTY_BUFFER, outNetBuf); } if (result.bytesProduced() > 0) { outNetBuf.flip(); ChannelBuffer msg = ChannelBuffers.buffer(outNetBuf.remaining()); msg.writeBytes(outNetBuf.array(), 0, msg.capacity()); outNetBuf.clear(); future = future(channel); future.addListener( new ChannelFutureListener() { public void operationComplete(ChannelFuture future) throws Exception { if (future.getCause() instanceof ClosedChannelException) { synchronized (ignoreClosedChannelExceptionLock) { ignoreClosedChannelException++; } } } }); write(ctx, future, msg); } final HandshakeStatus handshakeStatus = result.getHandshakeStatus(); handleRenegotiation(handshakeStatus); switch (handshakeStatus) { case FINISHED: setHandshakeSuccess(channel); runDelegatedTasks(); break; case NEED_TASK: runDelegatedTasks(); break; case NEED_UNWRAP: if (!Thread.holdsLock(handshakeLock)) { // unwrap shouldn't be called when this method was // called by unwrap - unwrap will keep running after // this method returns. unwrap(ctx, channel, ChannelBuffers.EMPTY_BUFFER, 0, 0); } break; case NOT_HANDSHAKING: case NEED_WRAP: break; default: throw new IllegalStateException("Unexpected handshake status: " + handshakeStatus); } if (result.bytesProduced() == 0) { break; } } } catch (SSLException e) { setHandshakeFailure(channel, e); throw e; } finally { bufferPool.release(outNetBuf); } if (future == null) { future = succeededFuture(channel); } return future; }
static void setInterestOps(NioSocketChannel channel, ChannelFuture future, int interestOps) { boolean changed = false; try { // interestOps can change at any time and at any thread. // Acquire a lock to avoid possible race condition. synchronized (channel.interestOpsLock) { NioWorker worker = channel.worker; Selector selector = worker.selector; SelectionKey key = channel.socket.keyFor(selector); if (key == null || selector == null) { // Not registered to the worker yet. // Set the rawInterestOps immediately; RegisterTask will pick it up. channel.setRawInterestOpsNow(interestOps); return; } // Override OP_WRITE flag - a user cannot change this flag. interestOps &= ~Channel.OP_WRITE; interestOps |= channel.getRawInterestOps() & Channel.OP_WRITE; switch (CONSTRAINT_LEVEL) { case 0: if (channel.getRawInterestOps() != interestOps) { key.interestOps(interestOps); if (Thread.currentThread() != worker.thread && worker.wakenUp.compareAndSet(false, true)) { selector.wakeup(); } changed = true; } break; case 1: case 2: if (channel.getRawInterestOps() != interestOps) { if (Thread.currentThread() == worker.thread) { key.interestOps(interestOps); changed = true; } else { worker.selectorGuard.readLock().lock(); try { if (worker.wakenUp.compareAndSet(false, true)) { selector.wakeup(); } key.interestOps(interestOps); changed = true; } finally { worker.selectorGuard.readLock().unlock(); } } } break; default: throw new Error(); } } future.setSuccess(); if (changed) { channel.setRawInterestOpsNow(interestOps); fireChannelInterestChanged(channel); } } catch (CancelledKeyException e) { // setInterestOps() was called on a closed channel. ClosedChannelException cce = new ClosedChannelException(); future.setFailure(cce); fireExceptionCaught(channel, cce); } catch (Throwable t) { future.setFailure(t); fireExceptionCaught(channel, t); } }
public void run() { thread = Thread.currentThread(); boolean shutdown = false; Selector selector = this.selector; for (; ; ) { wakenUp.set(false); if (CONSTRAINT_LEVEL != 0) { selectorGuard.writeLock().lock(); // This empty synchronization block prevents the selector // from acquiring its lock. selectorGuard.writeLock().unlock(); } try { selector.select(500); // 'wakenUp.compareAndSet(false, true)' is always evaluated // before calling 'selector.wakeup()' to reduce the wake-up // overhead. (Selector.wakeup() is an expensive operation.) // // However, there is a race condition in this approach. // The race condition is triggered when 'wakenUp' is set to // true too early. // // 'wakenUp' is set to true too early if: // 1) Selector is waken up between 'wakenUp.set(false)' and // 'selector.select(...)'. (BAD) // 2) Selector is waken up between 'selector.select(...)' and // 'if (wakenUp.get()) { ... }'. (OK) // // In the first case, 'wakenUp' is set to true and the // following 'selector.select(...)' will wake up immediately. // Until 'wakenUp' is set to false again in the next round, // 'wakenUp.compareAndSet(false, true)' will fail, and therefore // any attempt to wake up the Selector will fail, too, causing // the following 'selector.select(...)' call to block // unnecessarily. // // To fix this problem, we wake up the selector again if wakenUp // is true immediately after selector.select(...). // It is inefficient in that it wakes up the selector for both // the first case (BAD - wake-up required) and the second case // (OK - no wake-up required). if (wakenUp.get()) { selector.wakeup(); } cancelledKeys = 0; processRegisterTaskQueue(); processWriteTaskQueue(); processSelectedKeys(selector.selectedKeys()); // Exit the loop when there's nothing to handle. // The shutdown flag is used to delay the shutdown of this // loop to avoid excessive Selector creation when // connections are registered in a one-by-one manner instead of // concurrent manner. if (selector.keys().isEmpty()) { if (shutdown || executor instanceof ExecutorService && ((ExecutorService) executor).isShutdown()) { synchronized (startStopLock) { if (registerTaskQueue.isEmpty() && selector.keys().isEmpty()) { started = false; try { selector.close(); } catch (IOException e) { logger.warn("Failed to close a selector.", e); } finally { this.selector = null; } break; } else { shutdown = false; } } } else { // Give one more second. shutdown = true; } } else { shutdown = false; } } catch (Throwable t) { logger.warn("Unexpected exception in the selector loop.", t); // Prevent possible consecutive immediate failures that lead to // excessive CPU consumption. try { Thread.sleep(1000); } catch (InterruptedException e) { // Ignore. } } } }