/* * Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.hazelcast.internal.networking.nio; import com.hazelcast.internal.metrics.Probe; import com.hazelcast.internal.networking.ChannelConnection; import com.hazelcast.internal.networking.ChannelInboundHandler; import com.hazelcast.internal.networking.ChannelInitializer; import com.hazelcast.internal.networking.ChannelReader; import com.hazelcast.internal.networking.InitResult; import com.hazelcast.internal.networking.nio.iobalancer.IOBalancer; import com.hazelcast.internal.util.counters.SwCounter; import com.hazelcast.logging.ILogger; import java.io.EOFException; import java.io.IOException; import java.nio.ByteBuffer; import static com.hazelcast.internal.util.counters.SwCounter.newSwCounter; import static java.lang.System.currentTimeMillis; import static java.nio.channels.SelectionKey.OP_READ; /** * A {@link ChannelReader} tailored for non blocking IO. * * When the {@link NioThread} receives a read event from the {@link java.nio.channels.Selector}, then the * {@link #handle()} is called to read out the data from the socket into a bytebuffer and hand it over to the * {@link ChannelInboundHandler} to get processed. */ public final class NioChannelReader extends AbstractHandler implements ChannelReader { protected ByteBuffer inputBuffer; @Probe(name = "bytesRead") private final SwCounter bytesRead = newSwCounter(); @Probe(name = "normalFramesRead") private final SwCounter normalFramesRead = newSwCounter(); @Probe(name = "priorityFramesRead") private final SwCounter priorityFramesRead = newSwCounter(); private final ChannelInitializer initializer; private ChannelInboundHandler inboundHandler; private volatile long lastReadTime; private long bytesReadLastPublish; private long normalFramesReadLastPublish; private long priorityFramesReadLastPublish; private long handleCountLastPublish; public NioChannelReader( ChannelConnection connection, NioThread ioThread, ILogger logger, IOBalancer balancer, ChannelInitializer initializer) { super(connection, ioThread, OP_READ, logger, balancer); this.initializer = initializer; } @Override public long getLoad() { switch (LOAD_TYPE) { case 0: return handleCount.get(); case 1: return bytesRead.get(); case 2: return normalFramesRead.get() + priorityFramesRead.get(); default: throw new RuntimeException(); } } @Probe(name = "idleTimeMs") private long idleTimeMs() { return Math.max(currentTimeMillis() - lastReadTime, 0); } @Override public SwCounter getNormalFramesReadCounter() { return normalFramesRead; } @Override public SwCounter getPriorityFramesReadCounter() { return priorityFramesRead; } @Override public long lastReadTimeMillis() { return lastReadTime; } @Override public void init() { ioThread.addTaskAndWakeup(new Runnable() { @Override public void run() { try { getSelectionKey(); } catch (Throwable t) { onFailure(t); } } }); } /** * Migrates this handler to a new NioThread. * The migration logic is rather simple: * <p><ul> * <li>Submit a de-registration task to a current NioThread</li> * <li>The de-registration task submits a registration task to the new NioThread</li> * </ul></p> * * @param newOwner target NioThread this handler migrates to */ @Override public void requestMigration(NioThread newOwner) { ioThread.addTaskAndWakeup(new StartMigrationTask(newOwner)); } @Override public void handle() throws Exception { handleCount.inc(); // we are going to set the timestamp even if the channel is going to fail reading. In that case // the connection is going to be closed anyway. lastReadTime = currentTimeMillis(); if (inboundHandler == null) { InitResult<ChannelInboundHandler> init = initializer.initInbound(connection, this); if (init == null) { // when using SSL, we can read 0 bytes since data read from socket can be handshake frames. return; } this.inboundHandler = init.getHandler(); this.inputBuffer = init.getByteBuffer(); } int readBytes = channel.read(inputBuffer); if (readBytes <= 0) { if (readBytes == -1) { throw new EOFException("Remote socket closed!"); } return; } bytesRead.inc(readBytes); inputBuffer.flip(); inboundHandler.onRead(inputBuffer); if (inputBuffer.hasRemaining()) { inputBuffer.compact(); } else { inputBuffer.clear(); } } @Override public void publish() { if (Thread.currentThread() != ioThread) { return; } ioThread.bytesTransceived += bytesRead.get() - bytesReadLastPublish; ioThread.framesTransceived += normalFramesRead.get() - normalFramesReadLastPublish; ioThread.priorityFramesTransceived += priorityFramesRead.get() - priorityFramesReadLastPublish; ioThread.handleCount += handleCount.get() - handleCountLastPublish; bytesReadLastPublish = bytesRead.get(); normalFramesReadLastPublish = normalFramesRead.get(); priorityFramesReadLastPublish = priorityFramesRead.get(); handleCountLastPublish = handleCount.get(); } @Override public void close() { ioThread.addTaskAndWakeup(new Runnable() { @Override public void run() { if (ioThread != Thread.currentThread()) { // the NioChannelReader has migrated to a different IOThread after the close got called. // so we need to send the task to the right ioThread. Otherwise multiple ioThreads could be accessing // the same channel. ioThread.addTaskAndWakeup(this); return; } try { channel.closeInbound(); } catch (IOException e) { logger.finest("Error while closing inbound", e); } } }); } @Override public String toString() { return connection + ".channelReader"; } private class StartMigrationTask implements Runnable { private final NioThread newOwner; StartMigrationTask(NioThread newOwner) { this.newOwner = newOwner; } @Override public void run() { // if there is no change, we are done if (ioThread == newOwner) { return; } publish(); try { startMigration(newOwner); } catch (Throwable t) { onFailure(t); } } } }