/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.nifi.io.nio; import java.io.IOException; import java.nio.channels.DatagramChannel; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.UUID; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import org.apache.nifi.io.nio.consumer.StreamConsumerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public final class ChannelDispatcher implements Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(ChannelDispatcher.class); private final Selector serverSocketSelector; private final Selector socketChannelSelector; private final ScheduledExecutorService executor; private final BufferPool emptyBuffers; private final StreamConsumerFactory factory; private final AtomicLong channelReaderFrequencyMilliseconds = new AtomicLong(DEFAULT_CHANNEL_READER_PERIOD_MILLISECONDS); private final long timeout; private final boolean readSingleDatagram; private volatile boolean stop = false; public static final long DEFAULT_CHANNEL_READER_PERIOD_MILLISECONDS = 100L; public ChannelDispatcher(final Selector serverSocketSelector, final Selector socketChannelSelector, final ScheduledExecutorService service, final StreamConsumerFactory factory, final BufferPool buffers, final long timeout, final TimeUnit unit, final boolean readSingleDatagram) { this.serverSocketSelector = serverSocketSelector; this.socketChannelSelector = socketChannelSelector; this.executor = service; this.factory = factory; emptyBuffers = buffers; this.timeout = TimeUnit.MILLISECONDS.convert(timeout, unit); this.readSingleDatagram = readSingleDatagram; } public void setChannelReaderFrequency(final long period, final TimeUnit timeUnit) { channelReaderFrequencyMilliseconds.set(TimeUnit.MILLISECONDS.convert(period, timeUnit)); } @Override public void run() { while (!stop) { try { selectServerSocketKeys(); selectSocketChannelKeys(); } catch (final Exception ex) { LOGGER.warn("Key selection failed: {} Normal during shutdown.", new Object[]{ex}); } } } /* * When serverSocketsChannels are registered with the selector, want each invoke of this method to loop through all * channels' keys. * * @throws IOException if unable to select keys */ private void selectServerSocketKeys() throws IOException { int numSelected = serverSocketSelector.select(timeout); if (numSelected == 0) { return; } // for each registered server socket - see if any connections are waiting to be established final Iterator<SelectionKey> itr = serverSocketSelector.selectedKeys().iterator(); while (itr.hasNext()) { SelectionKey serverSocketkey = itr.next(); final SelectableChannel channel = serverSocketkey.channel(); AbstractChannelReader reader = null; if (serverSocketkey.isValid() && serverSocketkey.isAcceptable()) { final ServerSocketChannel ssChannel = (ServerSocketChannel) serverSocketkey.channel(); final SocketChannel sChannel = ssChannel.accept(); if (sChannel != null) { sChannel.configureBlocking(false); final SelectionKey socketChannelKey = sChannel.register(socketChannelSelector, SelectionKey.OP_READ); final String readerId = sChannel.socket().toString(); reader = new SocketChannelReader(readerId, socketChannelKey, emptyBuffers, factory); final ScheduledFuture<?> readerFuture = executor.scheduleWithFixedDelay(reader, 10L, channelReaderFrequencyMilliseconds.get(), TimeUnit.MILLISECONDS); reader.setScheduledFuture(readerFuture); socketChannelKey.attach(reader); } } itr.remove(); // do this so that the next select operation returns a positive value; otherwise, it will return 0. if (reader != null && LOGGER.isDebugEnabled()) { LOGGER.debug(this + " New Connection established. Server channel: " + channel + " Reader: " + reader); } } } /* * When invoking this method, only want to iterate through the selected keys once. When a key is entered into the selectors * selected key set, select will return a positive value. The next select will return 0 if nothing has changed. Note that * the selected key set is not manually changed via a remove operation. * * @throws IOException if unable to select keys */ private void selectSocketChannelKeys() throws IOException { // once a channel associated with a key in this selector is 'ready', it causes this select to immediately return. // thus, for each trip through the run() we only get hit with one real timeout...the one in selectServerSocketKeys. int numSelected = socketChannelSelector.select(timeout); if (numSelected == 0) { return; } for (SelectionKey socketChannelKey : socketChannelSelector.selectedKeys()) { final SelectableChannel channel = socketChannelKey.channel(); AbstractChannelReader reader = null; // there are 2 kinds of channels in this selector, both which have their own readers and are executed in their own // threads. We will get here whenever a new SocketChannel is created due to an incoming connection. However, // for a DatagramChannel we don't want to create a new reader unless it is a new DatagramChannel. The only // way to tell if it's new is the lack of an attachment. if (channel instanceof DatagramChannel && socketChannelKey.attachment() == null) { reader = new DatagramChannelReader(UUID.randomUUID().toString(), socketChannelKey, emptyBuffers, factory, readSingleDatagram); socketChannelKey.attach(reader); final ScheduledFuture<?> readerFuture = executor.scheduleWithFixedDelay(reader, 10L, channelReaderFrequencyMilliseconds.get(), TimeUnit.MILLISECONDS); reader.setScheduledFuture(readerFuture); } if (reader != null && LOGGER.isDebugEnabled()) { LOGGER.debug(this + " New Connection established. Server channel: " + channel + " Reader: " + reader); } } } public void stop() { stop = true; } }