/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
package com.liferay.portal.kernel.nio.intraband.nonblocking;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.nio.intraband.BaseIntraband;
import com.liferay.portal.kernel.nio.intraband.ChannelContext;
import com.liferay.portal.kernel.nio.intraband.Datagram;
import com.liferay.portal.kernel.nio.intraband.RegistrationReference;
import com.liferay.portal.kernel.util.NamedThreadFactory;
import java.io.IOException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.Channel;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.GatheringByteChannel;
import java.nio.channels.ScatteringByteChannel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ThreadFactory;
/**
* @author Shuyang Zhou
*/
public class SelectorIntraband extends BaseIntraband {
public SelectorIntraband(long defaultTimeout) throws IOException {
super(defaultTimeout);
pollingThread.start();
}
@Override
public void close() throws InterruptedException, IOException {
selector.close();
pollingThread.interrupt();
pollingThread.join(defaultTimeout);
super.close();
}
@Override
public RegistrationReference registerChannel(Channel channel)
throws IOException {
if (channel == null) {
throw new NullPointerException("Channel is null");
}
if (!(channel instanceof GatheringByteChannel)) {
throw new IllegalArgumentException(
"Channel is not of type GatheringByteChannel");
}
if (!(channel instanceof ScatteringByteChannel)) {
throw new IllegalArgumentException(
"Channel is not of type ScatteringByteChannel");
}
if (!(channel instanceof SelectableChannel)) {
throw new IllegalArgumentException(
"Channel is not of type SelectableChannel");
}
SelectableChannel selectableChannel = (SelectableChannel)channel;
if ((selectableChannel.validOps() & SelectionKey.OP_READ) == 0) {
throw new IllegalArgumentException(
"Channel is not valid for reading");
}
if ((selectableChannel.validOps() & SelectionKey.OP_WRITE) == 0) {
throw new IllegalArgumentException(
"Channel is not valid for writing");
}
ensureOpen();
selectableChannel.configureBlocking(false);
FutureTask<RegistrationReference> registerFutureTask = new FutureTask<>(
new RegisterCallable(selectableChannel, selectableChannel));
registerQueue.offer(registerFutureTask);
selector.wakeup();
try {
return registerFutureTask.get();
}
catch (Exception e) {
throw new IOException(e);
}
}
@Override
public RegistrationReference registerChannel(
ScatteringByteChannel scatteringByteChannel,
GatheringByteChannel gatheringByteChannel)
throws IOException {
if (scatteringByteChannel == null) {
throw new NullPointerException("Scattering byte channel is null");
}
if (gatheringByteChannel == null) {
throw new NullPointerException("Gathering byte channel is null");
}
if (!(scatteringByteChannel instanceof SelectableChannel)) {
throw new IllegalArgumentException(
"Scattering byte channel is not of type SelectableChannel");
}
if (!(gatheringByteChannel instanceof SelectableChannel)) {
throw new IllegalArgumentException(
"Gathering byte channel is not of type SelectableChannel");
}
SelectableChannel readSelectableChannel =
(SelectableChannel)scatteringByteChannel;
SelectableChannel writeSelectableChannel =
(SelectableChannel)gatheringByteChannel;
if ((readSelectableChannel.validOps() & SelectionKey.OP_READ) == 0) {
throw new IllegalArgumentException(
"Scattering byte channel is not valid for reading");
}
if ((writeSelectableChannel.validOps() & SelectionKey.OP_WRITE) == 0) {
throw new IllegalArgumentException(
"Gathering byte channel is not valid for writing");
}
ensureOpen();
readSelectableChannel.configureBlocking(false);
writeSelectableChannel.configureBlocking(false);
FutureTask<RegistrationReference> registerFutureTask = new FutureTask<>(
new RegisterCallable(
readSelectableChannel, writeSelectableChannel));
registerQueue.offer(registerFutureTask);
selector.wakeup();
try {
return registerFutureTask.get();
}
catch (Exception e) {
throw new IOException(e);
}
}
@Override
protected void doSendDatagram(
RegistrationReference registrationReference, Datagram datagram) {
SelectionKeyRegistrationReference selectionKeyRegistrationReference =
(SelectionKeyRegistrationReference)registrationReference;
SelectionKey writeSelectionKey =
selectionKeyRegistrationReference.writeSelectionKey;
ChannelContext channelContext =
(ChannelContext)writeSelectionKey.attachment();
Queue<Datagram> sendingQueue = channelContext.getSendingQueue();
sendingQueue.offer(datagram);
synchronized (writeSelectionKey) {
int ops = writeSelectionKey.interestOps();
if ((ops & SelectionKey.OP_WRITE) == 0) {
ops |= SelectionKey.OP_WRITE;
writeSelectionKey.interestOps(ops);
selector.wakeup();
}
}
}
protected void registerChannels() {
FutureTask<RegistrationReference> registerFuturetask = null;
synchronized (selector) {
while ((registerFuturetask = registerQueue.poll()) != null) {
registerFuturetask.run();
}
}
}
protected static final ThreadFactory threadFactory = new NamedThreadFactory(
SelectorIntraband.class + ".threadFactory", Thread.NORM_PRIORITY,
SelectorIntraband.class.getClassLoader());
protected final Thread pollingThread = threadFactory.newThread(
new PollingJob());
protected final Queue<FutureTask<RegistrationReference>> registerQueue =
new ConcurrentLinkedQueue<>();
protected final Selector selector = Selector.open();
protected class RegisterCallable
implements Callable<RegistrationReference> {
public RegisterCallable(
SelectableChannel readSelectableChannel,
SelectableChannel writeSelectableChannel) {
_readSelectableChannel = readSelectableChannel;
_writeSelectableChannel = writeSelectableChannel;
}
@Override
public RegistrationReference call() throws Exception {
if (_readSelectableChannel == _writeSelectableChannel) {
// Register channel with zero interest, no dispatch will happen
// before channel context is ready. This ensures thread safe
// publication for ChannelContext#_registrationReference.
SelectionKey selectionKey = _readSelectableChannel.register(
selector, 0);
SelectionKeyRegistrationReference
selectionKeyRegistrationReference =
new SelectionKeyRegistrationReference(
SelectorIntraband.this, selectionKey, selectionKey);
ChannelContext channelContext = new ChannelContext(
new ConcurrentLinkedQueue<Datagram>());
channelContext.setRegistrationReference(
selectionKeyRegistrationReference);
selectionKey.attach(channelContext);
// Alter interest ops after preparing the channel context
selectionKey.interestOps(SelectionKey.OP_READ);
return selectionKeyRegistrationReference;
}
else {
// Register channels with zero interest, no dispatch will happen
// before channel contexts are ready. This ensures thread safe
// publication for ChannelContext#_registrationReference.
SelectionKey readSelectionKey = _readSelectableChannel.register(
selector, 0);
SelectionKey writeSelectionKey =
_writeSelectableChannel.register(selector, 0);
SelectionKeyRegistrationReference
selectionKeyRegistrationReference =
new SelectionKeyRegistrationReference(
SelectorIntraband.this, readSelectionKey,
writeSelectionKey);
ChannelContext channelContext = new ChannelContext(
new ConcurrentLinkedQueue<Datagram>());
channelContext.setRegistrationReference(
selectionKeyRegistrationReference);
readSelectionKey.attach(channelContext);
writeSelectionKey.attach(channelContext);
// Alter interest ops after ChannelContexts preparation
readSelectionKey.interestOps(SelectionKey.OP_READ);
return selectionKeyRegistrationReference;
}
}
private final SelectableChannel _readSelectableChannel;
private final SelectableChannel _writeSelectableChannel;
}
private void _processReading(SelectionKey selectionKey) {
ScatteringByteChannel scatteringByteChannel =
(ScatteringByteChannel)selectionKey.channel();
ChannelContext channelContext =
(ChannelContext)selectionKey.attachment();
handleReading(scatteringByteChannel, channelContext);
}
private void _processWriting(SelectionKey selectionKey) {
GatheringByteChannel gatheringByteChannel =
(GatheringByteChannel)selectionKey.channel();
ChannelContext channelContext =
(ChannelContext)selectionKey.attachment();
Queue<Datagram> sendingQueue = channelContext.getSendingQueue();
if (channelContext.getWritingDatagram() == null) {
channelContext.setWritingDatagram(sendingQueue.poll());
}
boolean backOff = false;
if (channelContext.getWritingDatagram() != null) {
if (handleWriting(gatheringByteChannel, channelContext)) {
if (sendingQueue.isEmpty()) {
backOff = true;
}
}
}
else {
backOff = true;
}
if (backOff) {
// Channel is still writable, but there is nothing to send, back off
// to prevent unnecessary busy spinning.
int ops = selectionKey.interestOps();
ops &= ~SelectionKey.OP_WRITE;
synchronized (selectionKey) {
if (sendingQueue.isEmpty()) {
selectionKey.interestOps(ops);
}
}
}
}
private static final Log _log = LogFactoryUtil.getLog(
SelectorIntraband.class);
private class PollingJob implements Runnable {
@Override
public void run() {
try {
try {
while (true) {
int readyCount = selector.select();
if (readyCount > 0) {
Set<SelectionKey> selectionKeys =
selector.selectedKeys();
Iterator<SelectionKey> iterator =
selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
try {
if (selectionKey.isReadable()) {
_processReading(selectionKey);
}
if (selectionKey.isWritable()) {
_processWriting(selectionKey);
}
}
catch (CancelledKeyException cke) {
// Concurrent cancelling, move to next key
}
}
}
else if (!selector.isOpen()) {
break;
}
registerChannels();
cleanUpTimeoutResponseWaitingDatagrams();
}
}
finally {
selector.close();
}
}
catch (ClosedSelectorException cse) {
if (_log.isInfoEnabled()) {
Thread currentThread = Thread.currentThread();
_log.info(
currentThread.getName() +
" exiting gracefully on selector closure");
}
}
catch (Throwable t) {
Thread currentThread = Thread.currentThread();
_log.error(
currentThread.getName() + " exiting exceptionally", t);
}
// Flush out pending register requests to unblock their invokers,
// this will cause them to receive a ClosedSelectorException
registerChannels();
responseWaitingMap.clear();
timeoutMap.clear();
}
}
}