// **********************************************************************
//
// Copyright (c) 2003-2010 ZeroC, Inc. All rights reserved.
//
// This copy of Ice is licensed to you under the terms described in the
// ICE_LICENSE file included in this distribution.
//
// **********************************************************************
package IceInternal;
public final class Selector
{
static final class TimeoutException extends Exception
{
}
public
Selector(Instance instance)
{
_instance = instance;
try
{
_selector = java.nio.channels.Selector.open();
}
catch(java.io.IOException ex)
{
Ice.SyscallException sys = new Ice.SyscallException();
sys.initCause(ex);
throw sys;
}
//
// The Selector holds a Set representing the selected keys. The
// Set reference doesn't change, so we obtain it once here.
//
_keys = _selector.selectedKeys();
}
public void
destroy()
{
try
{
_selector.close();
}
catch(java.io.IOException ex)
{
}
_selector = null;
}
public void
initialize(EventHandler handler)
{
updateImpl(handler);
}
public void
update(EventHandler handler, int remove, int add)
{
int previous = handler._registered;
handler._registered = handler._registered & ~remove;
handler._registered = handler._registered | add;
if(previous == handler._registered)
{
return;
}
updateImpl(handler);
if(handler.hasMoreData() && (handler._disabled & SocketOperation.Read) == 0)
{
if((add & SocketOperation.Read) != 0)
{
_pendingHandlers.add(handler);
}
if((remove & SocketOperation.Read) != 0)
{
_pendingHandlers.remove(handler);
}
}
}
public void
enable(EventHandler handler, int status)
{
if((handler._disabled & status) == 0)
{
return;
}
handler._disabled = handler._disabled & ~status;
if((handler._registered & status) != 0)
{
updateImpl(handler);
if((status & SocketOperation.Read) != 0 && handler.hasMoreData())
{
// Add back the pending handler if reads are enabled.
_pendingHandlers.add(handler);
}
}
}
public void
disable(EventHandler handler, int status)
{
if((handler._disabled & status) != 0)
{
return;
}
handler._disabled = handler._disabled | status;
if((handler._registered & status) != 0)
{
updateImpl(handler);
if((status & SocketOperation.Read) != 0 && handler.hasMoreData())
{
// Remove the pending handler if reads are disabled.
_pendingHandlers.remove(handler);
}
}
}
public void
finish(EventHandler handler)
{
handler._registered = 0;
if(handler._key != null)
{
handler._key.cancel();
handler._key = null;
}
_changes.remove(handler);
_pendingHandlers.remove(handler);
}
public void
startSelect()
{
assert(_changes.isEmpty());
//
// Don't set _selecting = true if there are pending handlers, select() won't block
// and will just call selectNow().
//
if(_pendingHandlers.isEmpty())
{
_selecting = true;
}
}
public void
finishSelect(java.util.List<EventHandler> handlers, long timeout)
{
_selecting = false;
handlers.clear();
if(!_changes.isEmpty())
{
for(EventHandler h : _changes)
{
updateImpl(h);
}
_changes.clear();
}
else if(_keys.isEmpty() && _pendingHandlers.isEmpty() && timeout <= 0)
{
//
// This is necessary to prevent a busy loop in case of a spurious wake-up which
// sometime occurs in the client thread pool when the communicator is destroyed.
// If there are too many successive spurious wake-ups, we log an error.
//
try
{
Thread.sleep(1);
}
catch(java.lang.InterruptedException ex)
{
}
if(++_spuriousWakeUp > 100)
{
_instance.initializationData().logger.error("spurious selector wake up");
}
return;
}
_spuriousWakeUp = 0;
for(java.nio.channels.SelectionKey key : _keys)
{
EventHandler handler = (EventHandler)key.attachment();
try
{
//
// It's important to check for interestOps here because the event handler
// registration might have changed above when _changes was processed. We
// don't want to return event handlers which aren't interested anymore in
// a given operation.
//
handler._ready = fromJavaOps(key.readyOps() & key.interestOps());
if(handler.hasMoreData() && _pendingHandlers.remove(handler))
{
handler._ready |= SocketOperation.Read;
}
handlers.add(handler);
}
catch(java.nio.channels.CancelledKeyException ex)
{
assert(handler._registered == 0);
}
}
_keys.clear();
for(EventHandler handler : _pendingHandlers)
{
if(handler.hasMoreData())
{
handler._ready = SocketOperation.Read;
handlers.add(handler);
}
}
_pendingHandlers.clear();
}
public void
select(long timeout)
throws TimeoutException
{
while(true)
{
try
{
//
// Only block if _selecting = true, otherwise we call selectNow() to retrieve new
// ready handlers and process handlers from _pendingHandlers.
//
if(_selecting)
{
if(timeout > 0)
{
long before = IceInternal.Time.currentMonotonicTimeMillis();
if(_selector.select(timeout * 1000) == 0)
{
if(IceInternal.Time.currentMonotonicTimeMillis() - before >= timeout * 1000)
{
throw new TimeoutException();
}
}
}
else
{
_selector.select();
}
}
else
{
_selector.selectNow();
}
}
catch(java.nio.channels.CancelledKeyException ex)
{
// This sometime occurs on Mac OS X, ignore.
continue;
}
catch(java.io.IOException ex)
{
//
// Pressing Ctrl-C causes select() to raise an
// IOException, which seems like a JDK bug. We trap
// for that special case here and ignore it.
// Hopefully we're not masking something important!
//
if(Network.interrupted(ex))
{
continue;
}
try
{
String s = "fatal error: selector failed:\n" + ex.getCause().getMessage();
_instance.initializationData().logger.error(s);
}
finally
{
Runtime.getRuntime().halt(1);
}
}
break;
}
}
public void
hasMoreData(EventHandler handler)
{
assert(!_selecting && handler.hasMoreData());
//
// Only add the handler if read is still registered and enabled.
//
if((handler._registered & ~handler._disabled & SocketOperation.Read) != 0)
{
_pendingHandlers.add(handler);
}
}
private void
updateImpl(EventHandler handler)
{
if(_selecting)
{
//
// Queue the change since we can't change the selection key interest ops while a select
// operation is in progress (it could block depending on the underlying implementaiton
// of the Java selector).
//
if(_changes.isEmpty())
{
_selector.wakeup();
}
_changes.add(handler);
return;
}
int ops = toJavaOps(handler, handler._registered & ~handler._disabled);
if(handler._key == null)
{
if(handler._registered != 0)
{
try
{
handler._key = handler.fd().register(_selector, ops, handler);
}
catch(java.nio.channels.ClosedChannelException ex)
{
assert(false);
}
}
}
else
{
handler._key.interestOps(ops);
}
}
int
toJavaOps(EventHandler handler, int o)
{
int op = 0;
if((o & SocketOperation.Read) != 0)
{
if((handler.fd().validOps() & java.nio.channels.SelectionKey.OP_READ) != 0)
{
op |= java.nio.channels.SelectionKey.OP_READ;
}
else
{
op |= java.nio.channels.SelectionKey.OP_ACCEPT;
}
}
if((o & SocketOperation.Write) != 0)
{
op |= java.nio.channels.SelectionKey.OP_WRITE;
}
if((o & SocketOperation.Connect) != 0)
{
op |= java.nio.channels.SelectionKey.OP_CONNECT;
}
return op;
}
int
fromJavaOps(int o)
{
int op = 0;
if((o & (java.nio.channels.SelectionKey.OP_READ | java.nio.channels.SelectionKey.OP_ACCEPT)) != 0)
{
op |= SocketOperation.Read;
}
if((o & java.nio.channels.SelectionKey.OP_WRITE) != 0)
{
op |= SocketOperation.Write;
}
if((o & java.nio.channels.SelectionKey.OP_CONNECT) != 0)
{
op |= SocketOperation.Connect;
}
return op;
}
final private Instance _instance;
private java.nio.channels.Selector _selector;
private java.util.Set<java.nio.channels.SelectionKey> _keys;
private java.util.HashSet<EventHandler> _changes = new java.util.HashSet<EventHandler>();
private java.util.HashSet<EventHandler> _pendingHandlers = new java.util.HashSet<EventHandler>();
private boolean _selecting;
private int _spuriousWakeUp;
}