org.eclipse.jetty.io.ManagedSelector Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ehcache Show documentation
Show all versions of ehcache Show documentation
Ehcache is an open source, standards-based cache used to boost performance,
offload the database and simplify scalability. Ehcache is robust, proven and full-featured and
this has made it the most widely-used Java-based cache.
//
// ========================================================================
// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.io;
import java.io.Closeable;
import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.DumpableCollection;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.ExecutionStrategy;
import org.eclipse.jetty.util.thread.Scheduler;
import org.eclipse.jetty.util.thread.strategy.EatWhatYouKill;
/**
* {@link ManagedSelector} wraps a {@link Selector} simplifying non-blocking operations on channels.
* {@link ManagedSelector} runs the select loop, which waits on {@link Selector#select()} until events
* happen for registered channels. When events happen, it notifies the {@link EndPoint} associated
* with the channel.
*/
public class ManagedSelector extends ContainerLifeCycle implements Dumpable
{
private static final Logger LOG = Log.getLogger(ManagedSelector.class);
private final AtomicBoolean _started = new AtomicBoolean(false);
private boolean _selecting = false;
private final SelectorManager _selectorManager;
private final int _id;
private final ExecutionStrategy _strategy;
private Selector _selector;
private Deque _updates = new ArrayDeque<>();
private Deque _updateable = new ArrayDeque<>();
public ManagedSelector(SelectorManager selectorManager, int id)
{
_selectorManager = selectorManager;
_id = id;
SelectorProducer producer = new SelectorProducer();
Executor executor = selectorManager.getExecutor();
_strategy = new EatWhatYouKill(producer,executor);
addBean(_strategy,true);
setStopTimeout(5000);
}
public Selector getSelector()
{
return _selector;
}
@Override
protected void doStart() throws Exception
{
super.doStart();
_selector = _selectorManager.newSelector();
// The producer used by the strategies will never
// be idle (either produces a task or blocks).
// The normal strategy obtains the produced task, schedules
// a new thread to produce more, runs the task and then exits.
_selectorManager.execute(_strategy::produce);
// Set started only if we really are started
submit(s->_started.set(true));
}
public int size()
{
Selector s = _selector;
if (s == null)
return 0;
return s.keys().size();
}
@Override
protected void doStop() throws Exception
{
// doStop might be called for a failed managedSelector,
// We do not want to wait twice, so we only stop once for each start
if (_started.compareAndSet(true,false))
{
// Close connections, but only wait a single selector cycle for it to take effect
CloseConnections close_connections = new CloseConnections();
submit(close_connections);
close_connections._complete.await();
// Wait for any remaining endpoints to be closed and the selector to be stopped
StopSelector stop_selector = new StopSelector();
submit(stop_selector);
stop_selector._stopped.await();
}
super.doStop();
}
/**
* Submit an {@link SelectorUpdate} to be acted on between calls to {@link Selector#select()}
* @param update The selector update to apply at next wakeup
*/
public void submit(SelectorUpdate update)
{
if (LOG.isDebugEnabled())
LOG.debug("Queued change {} on {}", update, this);
Selector selector = null;
synchronized(ManagedSelector.this)
{
_updates.offer(update);
if (_selecting)
{
selector = _selector;
// To avoid the extra select wakeup.
_selecting = false;
}
}
if (selector != null)
{
if (LOG.isDebugEnabled())
LOG.debug("wakeup on submit {}", this);
selector.wakeup();
}
}
private void execute(Runnable task)
{
try
{
_selectorManager.execute(task);
}
catch (RejectedExecutionException x)
{
if (task instanceof Closeable)
closeNoExceptions((Closeable)task);
}
}
private void processConnect(SelectionKey key, final Connect connect)
{
SelectableChannel channel = key.channel();
try
{
key.attach(connect.attachment);
boolean connected = _selectorManager.doFinishConnect(channel);
if (LOG.isDebugEnabled())
LOG.debug("Connected {} {}", connected, channel);
if (connected)
{
if (connect.timeout.cancel())
{
key.interestOps(0);
execute(new CreateEndPoint(connect,key));
}
else
{
throw new SocketTimeoutException("Concurrent Connect Timeout");
}
}
else
{
throw new ConnectException();
}
}
catch (Throwable x)
{
connect.failed(x);
}
}
private static void closeNoExceptions(Closeable closeable)
{
try
{
if (closeable != null)
closeable.close();
}
catch (Throwable x)
{
LOG.ignore(x);
}
}
private void createEndPoint(SelectableChannel channel, SelectionKey selectionKey) throws IOException
{
EndPoint endPoint = _selectorManager.newEndPoint(channel, this, selectionKey);
Connection connection = _selectorManager.newConnection(channel, endPoint, selectionKey.attachment());
endPoint.setConnection(connection);
selectionKey.attach(endPoint);
endPoint.onOpen();
_selectorManager.endPointOpened(endPoint);
_selectorManager.connectionOpened(connection);
if (LOG.isDebugEnabled())
LOG.debug("Created {}", endPoint);
}
public void destroyEndPoint(final EndPoint endPoint)
{
execute(new DestroyEndPoint(endPoint));
}
private int getActionSize()
{
synchronized(ManagedSelector.this)
{
return _updates.size();
}
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
List keys;
List updates;
Selector selector = _selector;
if (selector != null && selector.isOpen())
{
DumpKeys dump = new DumpKeys();
String updatesAt = DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(ZonedDateTime.now());
synchronized(ManagedSelector.this)
{
updates = new ArrayList<>(_updates);
_updates.addFirst(dump);
_selecting = false;
}
if (LOG.isDebugEnabled())
LOG.debug("wakeup on dump {}", this);
selector.wakeup();
keys = dump.get(5, TimeUnit.SECONDS);
String keysAt = DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(ZonedDateTime.now());
if (keys==null)
keys = Collections.singletonList("No dump keys retrieved");
dumpBeans(out, indent, Arrays.asList(new DumpableCollection("updates @ "+updatesAt, updates),
new DumpableCollection("keys @ "+keysAt, keys)));
}
else
{
dumpBeans(out, indent);
}
}
@Override
public String toString()
{
Selector selector = _selector;
return String.format("%s id=%s keys=%d selected=%d updates=%d",
super.toString(),
_id,
selector != null && selector.isOpen() ? selector.keys().size() : -1,
selector != null && selector.isOpen() ? selector.selectedKeys().size() : -1,
getActionSize());
}
/**
* A {@link Selectable} is an {@link EndPoint} that wish to be
* notified of non-blocking events by the {@link ManagedSelector}.
*/
public interface Selectable
{
/**
* Callback method invoked when a read or write events has been
* detected by the {@link ManagedSelector} for this endpoint.
*
* @return a job that may block or null
*/
Runnable onSelected();
/**
* Callback method invoked when all the keys selected by the
* {@link ManagedSelector} for this endpoint have been processed.
*/
void updateKey();
}
private class SelectorProducer implements ExecutionStrategy.Producer
{
private Set _keys = Collections.emptySet();
private Iterator _cursor = Collections.emptyIterator();
@Override
public Runnable produce()
{
while (true)
{
Runnable task = processSelected();
if (task != null)
return task;
processUpdates();
updateKeys();
if (!select())
return null;
}
}
private void processUpdates()
{
synchronized(ManagedSelector.this)
{
Deque updates = _updates;
_updates = _updateable;
_updateable = updates;
}
if (LOG.isDebugEnabled())
LOG.debug("updateable {}",_updateable.size());
for (SelectorUpdate update : _updateable)
{
if (_selector==null)
break;
try
{
if (LOG.isDebugEnabled())
LOG.debug("update {}",update);
update.update(_selector);
}
catch(Throwable th)
{
LOG.warn(th);
}
}
_updateable.clear();
Selector selector;
int updates;
synchronized(ManagedSelector.this)
{
updates = _updates.size();
_selecting = updates==0;
selector = _selecting?null:_selector;
}
if (LOG.isDebugEnabled())
LOG.debug("updates {}",updates);
if (selector != null)
{
if (LOG.isDebugEnabled())
LOG.debug("wakeup on updates {}", this);
selector.wakeup();
}
}
private boolean select()
{
try
{
Selector selector = _selector;
if (selector != null && selector.isOpen())
{
if (LOG.isDebugEnabled())
LOG.debug("Selector {} waiting with {} keys", selector, selector.keys().size());
int selected = selector.select();
if (selected == 0)
{
if (LOG.isDebugEnabled())
LOG.debug("Selector {} woken with none selected", selector);
selected = selector.selectNow();
}
if (LOG.isDebugEnabled())
LOG.debug("Selector {} woken up from select, {}/{}/{} selected", selector, selected, selector.selectedKeys().size(), selector.keys().size());
int updates;
synchronized(ManagedSelector.this)
{
// finished selecting
_selecting = false;
updates = _updates.size();
}
_keys = selector.selectedKeys();
_cursor = _keys.isEmpty() ? Collections.emptyIterator() : _keys.iterator();
if (LOG.isDebugEnabled())
LOG.debug("Selector {} processing {} keys, {} updates", selector, _keys.size(), updates);
return true;
}
}
catch (Throwable x)
{
closeNoExceptions(_selector);
_selector = null;
if (isRunning())
LOG.warn(x);
else
LOG.debug(x);
}
return false;
}
private Runnable processSelected()
{
while (_cursor.hasNext())
{
SelectionKey key = _cursor.next();
if (key.isValid())
{
Object attachment = key.attachment();
if (LOG.isDebugEnabled())
LOG.debug("selected {} {} {} ",key.readyOps(),key,attachment);
try
{
if (attachment instanceof Selectable)
{
// Try to produce a task
Runnable task = ((Selectable)attachment).onSelected();
if (task != null)
return task;
}
else if (key.isConnectable())
{
processConnect(key, (Connect)attachment);
}
else
{
throw new IllegalStateException("key=" + key + ", att=" + attachment + ", iOps=" + key.interestOps() + ", rOps=" + key.readyOps());
}
}
catch (CancelledKeyException x)
{
LOG.debug("Ignoring cancelled key for channel {}", key.channel());
if (attachment instanceof EndPoint)
closeNoExceptions((EndPoint)attachment);
}
catch (Throwable x)
{
LOG.warn("Could not process key for channel " + key.channel(), x);
if (attachment instanceof EndPoint)
closeNoExceptions((EndPoint)attachment);
}
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("Selector loop ignoring invalid key for channel {}", key.channel());
Object attachment = key.attachment();
if (attachment instanceof EndPoint)
closeNoExceptions((EndPoint)attachment);
}
}
return null;
}
private void updateKeys()
{
// Do update keys for only previously selected keys.
// This will update only those keys whose selection did not cause an
// updateKeys update to be submitted.
for (SelectionKey key : _keys)
{
Object attachment = key.attachment();
if (attachment instanceof Selectable)
((Selectable)attachment).updateKey();
}
_keys.clear();
}
@Override
public String toString()
{
return String.format("%s@%x", getClass().getSimpleName(), hashCode());
}
}
/**
* A selector update to be done when the selector has been woken.
*/
public interface SelectorUpdate
{
public void update(Selector selector);
}
private static class DumpKeys implements SelectorUpdate
{
private CountDownLatch latch = new CountDownLatch(1);
private List keys;
@Override
public void update(Selector selector)
{
Set selector_keys = selector.keys();
List list = new ArrayList<>(selector_keys.size()+1);
list.add(selector + " keys=" + selector_keys.size());
for (SelectionKey key : selector_keys)
{
if (key==null)
continue;
try
{
list.add(String.format("SelectionKey@%x{i=%d}->%s", key.hashCode(), key.interestOps(), key.attachment()));
}
catch (Throwable x)
{
list.add(String.format("SelectionKey@%x[%s]->%s", key.hashCode(), x, key.attachment()));
}
}
keys = list;
latch.countDown();
}
public List get(long timeout, TimeUnit unit)
{
try
{
latch.await(timeout, unit);
}
catch (InterruptedException x)
{
LOG.ignore(x);
}
return keys;
}
}
class Acceptor implements SelectorUpdate, Selectable, Closeable
{
private final SelectableChannel _channel;
private SelectionKey _key;
public Acceptor(SelectableChannel channel)
{
this._channel = channel;
}
@Override
public void update(Selector selector)
{
try
{
if (_key==null)
{
_key = _channel.register(selector, SelectionKey.OP_ACCEPT, this);
}
if (LOG.isDebugEnabled())
LOG.debug("{} acceptor={}", this, _key);
}
catch (Throwable x)
{
closeNoExceptions(_channel);
LOG.warn(x);
}
}
@Override
public Runnable onSelected()
{
SelectableChannel server = _key.channel();
SelectableChannel channel = null;
try
{
while(true)
{
channel = _selectorManager.doAccept(server);
if (channel==null)
break;
_selectorManager.accepted(channel);
}
}
catch (Throwable x)
{
closeNoExceptions(channel);
LOG.warn("Accept failed for channel " + channel, x);
}
return null;
}
@Override
public void updateKey()
{
}
@Override
public void close() throws IOException
{
SelectionKey key = _key;
_key = null;
if (key!=null && key.isValid())
key.cancel();
}
}
class Accept implements SelectorUpdate, Runnable, Closeable
{
private final SelectableChannel channel;
private final Object attachment;
private SelectionKey key;
Accept(SelectableChannel channel, Object attachment)
{
this.channel = channel;
this.attachment = attachment;
}
@Override
public void close()
{
LOG.debug("closed accept of {}", channel);
closeNoExceptions(channel);
}
@Override
public void update(Selector selector)
{
try
{
key = channel.register(selector, 0, attachment);
execute(this);
}
catch (Throwable x)
{
closeNoExceptions(channel);
LOG.debug(x);
}
}
@Override
public void run()
{
try
{
createEndPoint(channel, key);
}
catch (Throwable x)
{
LOG.debug(x);
failed(x);
}
}
protected void failed(Throwable failure)
{
closeNoExceptions(channel);
LOG.warn(String.valueOf(failure));
LOG.debug(failure);
}
}
class Connect implements SelectorUpdate, Runnable
{
private final AtomicBoolean failed = new AtomicBoolean();
private final SelectableChannel channel;
private final Object attachment;
private final Scheduler.Task timeout;
Connect(SelectableChannel channel, Object attachment)
{
this.channel = channel;
this.attachment = attachment;
this.timeout = ManagedSelector.this._selectorManager.getScheduler().schedule(this, ManagedSelector.this._selectorManager.getConnectTimeout(), TimeUnit.MILLISECONDS);
}
@Override
public void update(Selector selector)
{
try
{
channel.register(selector, SelectionKey.OP_CONNECT, this);
}
catch (Throwable x)
{
failed(x);
}
}
@Override
public void run()
{
if (_selectorManager.isConnectionPending(channel))
{
if (LOG.isDebugEnabled())
LOG.debug("Channel {} timed out while connecting, closing it", channel);
failed(new SocketTimeoutException("Connect Timeout"));
}
}
public void failed(Throwable failure)
{
if (failed.compareAndSet(false, true))
{
timeout.cancel();
closeNoExceptions(channel);
ManagedSelector.this._selectorManager.connectionFailed(channel, failure, attachment);
}
}
@Override
public String toString()
{
return String.format("Connect@%x{%s,%s}",hashCode(),channel,attachment);
}
}
private class CloseConnections implements SelectorUpdate
{
final Set _closed;
final CountDownLatch _noEndPoints = new CountDownLatch(1);
final CountDownLatch _complete = new CountDownLatch(1);
public CloseConnections()
{
this(null);
}
public CloseConnections(Set closed)
{
_closed = closed;
}
@Override
public void update(Selector selector)
{
if (LOG.isDebugEnabled())
LOG.debug("Closing {} connections on {}", selector.keys().size(), ManagedSelector.this);
boolean zero = true;
for (SelectionKey key : selector.keys())
{
if (key!=null && key.isValid())
{
Closeable closeable = null;
Object attachment = key.attachment();
if (attachment instanceof EndPoint)
{
EndPoint endp = (EndPoint)attachment;
if (!endp.isOutputShutdown())
zero = false;
Connection connection = endp.getConnection();
if (connection != null)
closeable = connection;
else
closeable = endp;
}
if (closeable!=null)
{
if (_closed==null)
{
closeNoExceptions(closeable);
}
else if (!_closed.contains(closeable))
{
_closed.add(closeable);
closeNoExceptions(closeable);
}
}
}
}
if (zero)
_noEndPoints.countDown();
_complete.countDown();
}
}
private class StopSelector implements SelectorUpdate
{
CountDownLatch _stopped = new CountDownLatch(1);
@Override
public void update(Selector selector)
{
for (SelectionKey key : selector.keys())
{
if (key!=null && key.isValid())
{
Object attachment = key.attachment();
if (attachment instanceof EndPoint)
closeNoExceptions((EndPoint)attachment);
}
}
_selector = null;
closeNoExceptions(selector);
_stopped.countDown();
}
}
private final class CreateEndPoint implements Runnable
{
private final Connect _connect;
private final SelectionKey _key;
private CreateEndPoint(Connect connect, SelectionKey key)
{
_connect = connect;
_key = key;
}
@Override
public void run()
{
try
{
createEndPoint(_connect.channel,_key);
}
catch(Throwable failure)
{
closeNoExceptions(_connect.channel);
LOG.warn(String.valueOf(failure));
LOG.debug(failure);
_connect.failed(failure);
}
}
@Override
public String toString()
{
return String.format("CreateEndPoint@%x{%s,%s}",hashCode(),_connect,_key);
}
}
private class DestroyEndPoint implements Runnable, Closeable
{
private final EndPoint endPoint;
public DestroyEndPoint(EndPoint endPoint)
{
this.endPoint = endPoint;
}
@Override
public void run()
{
if (LOG.isDebugEnabled())
LOG.debug("Destroyed {}", endPoint);
Connection connection = endPoint.getConnection();
if (connection != null)
_selectorManager.connectionClosed(connection);
_selectorManager.endPointClosed(endPoint);
}
@Override
public void close()
{
run();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy