// **********************************************************************
//
// 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 ThreadPool
{
final class ShutdownWorkItem implements ThreadPoolWorkItem
{
public void execute(ThreadPoolCurrent current)
{
current.ioCompleted();
try
{
_instance.objectAdapterFactory().shutdown();
}
catch(Ice.CommunicatorDestroyedException ex)
{
}
}
}
static final class FinishedWorkItem implements ThreadPoolWorkItem
{
public
FinishedWorkItem(EventHandler handler)
{
_handler = handler;
}
public void execute(ThreadPoolCurrent current)
{
_handler.finished(current);
}
private final EventHandler _handler;
}
static final class JoinThreadWorkItem implements ThreadPoolWorkItem
{
public
JoinThreadWorkItem(EventHandlerThread thread)
{
_thread = thread;
}
public void execute(ThreadPoolCurrent current)
{
// No call to ioCompleted, this shouldn't block (and we don't want to cause
// a new thread to be started).
_thread.join();
}
private final EventHandlerThread _thread;
}
//
// Exception raised by the thread pool work queue when the thread pool
// is destroyed.
//
static final class DestroyedException extends RuntimeException
{
}
public
ThreadPool(Instance instance, String prefix, int timeout)
{
_instance = instance;
_destroyed = false;
_prefix = prefix;
_selector = new Selector(instance);
_threadIndex = 0;
_inUse = 0;
_inUseIO = 0;
_promote = true;
_serialize = _instance.initializationData().properties.getPropertyAsInt(_prefix + ".Serialize") > 0;
_serverIdleTime = timeout;
Ice.Properties properties = _instance.initializationData().properties;
String programName = properties.getProperty("Ice.ProgramName");
if(programName.length() > 0)
{
_threadPrefix = programName + "-" + _prefix;
}
else
{
_threadPrefix = _prefix;
}
int nProcessors = Runtime.getRuntime().availableProcessors();
//
// We use just one thread as the default. This is the fastest
// possible setting, still allows one level of nesting, and
// doesn't require to make the servants thread safe.
//
int size = properties.getPropertyAsIntWithDefault(_prefix + ".Size", 1);
if(size < 1)
{
String s = _prefix + ".Size < 1; Size adjusted to 1";
_instance.initializationData().logger.warning(s);
size = 1;
}
int sizeMax = properties.getPropertyAsIntWithDefault(_prefix + ".SizeMax", size);
if(sizeMax == -1)
{
sizeMax = nProcessors;
}
if(sizeMax < size)
{
String s = _prefix + ".SizeMax < " + _prefix + ".Size; SizeMax adjusted to Size (" + size + ")";
_instance.initializationData().logger.warning(s);
sizeMax = size;
}
int sizeWarn = properties.getPropertyAsInt(_prefix + ".SizeWarn");
if(sizeWarn != 0 && sizeWarn < size)
{
String s = _prefix + ".SizeWarn < " + _prefix + ".Size; adjusted SizeWarn to Size (" + size + ")";
_instance.initializationData().logger.warning(s);
sizeWarn = size;
}
else if(sizeWarn > sizeMax)
{
String s = _prefix + ".SizeWarn > " + _prefix + ".SizeMax; adjusted SizeWarn to SizeMax (" + sizeMax + ")";
_instance.initializationData().logger.warning(s);
sizeWarn = sizeMax;
}
int threadIdleTime = properties.getPropertyAsIntWithDefault(_prefix + ".ThreadIdleTime", 60);
if(threadIdleTime < 0)
{
String s = _prefix + ".ThreadIdleTime < 0; ThreadIdleTime adjusted to 0";
_instance.initializationData().logger.warning(s);
threadIdleTime = 0;
}
_size = size;
_sizeMax = sizeMax;
_sizeWarn = sizeWarn;
_sizeIO = Math.min(sizeMax, nProcessors);
_threadIdleTime = threadIdleTime;
int stackSize = properties.getPropertyAsInt( _prefix + ".StackSize");
if(stackSize < 0)
{
String s = _prefix + ".StackSize < 0; Size adjusted to JRE default";
_instance.initializationData().logger.warning(s);
stackSize = 0;
}
_stackSize = stackSize;
boolean hasPriority = properties.getProperty(_prefix + ".ThreadPriority").length() > 0;
int priority = properties.getPropertyAsInt(_prefix + ".ThreadPriority");
if(!hasPriority)
{
hasPriority = properties.getProperty("Ice.ThreadPriority").length() > 0;
priority = properties.getPropertyAsInt("Ice.ThreadPriority");
}
_hasPriority = hasPriority;
_priority = priority;
_workQueue = new ThreadPoolWorkQueue(this, _instance, _selector);
_nextHandler = _handlers.iterator();
if(_instance.traceLevels().threadPool >= 1)
{
String s = "creating " + _prefix + ": Size = " + _size + ", SizeMax = " + _sizeMax + ", SizeWarn = " +
_sizeWarn;
_instance.initializationData().logger.trace(_instance.traceLevels().threadPoolCat, s);
}
try
{
for(int i = 0; i < _size; i++)
{
EventHandlerThread thread = new EventHandlerThread(_threadPrefix + "-" + _threadIndex++);
_threads.add(thread);
if(_hasPriority)
{
thread.start(_priority);
}
else
{
thread.start(java.lang.Thread.NORM_PRIORITY);
}
}
}
catch(RuntimeException ex)
{
String s = "cannot create thread for `" + _prefix + "':\n" + Ex.toString(ex);
_instance.initializationData().logger.error(s);
destroy();
joinWithAllThreads();
throw ex;
}
}
protected synchronized void
finalize()
throws Throwable
{
IceUtilInternal.Assert.FinalizerAssert(_destroyed);
}
public synchronized void
destroy()
{
assert(!_destroyed);
_destroyed = true;
_workQueue.destroy();
}
public synchronized void
initialize(EventHandler handler)
{
assert(!_destroyed);
_selector.initialize(handler);
}
public void
register(EventHandler handler, int op)
{
update(handler, SocketOperation.None, op);
}
public synchronized void
update(EventHandler handler, int remove, int add)
{
assert(!_destroyed);
_selector.update(handler, remove, add);
}
public void
unregister(EventHandler handler, int op)
{
update(handler, op, SocketOperation.None);
}
public synchronized void
finish(EventHandler handler)
{
assert(!_destroyed);
_selector.finish(handler);
_workQueue.queue(new FinishedWorkItem(handler));
}
public void
execute(ThreadPoolWorkItem workItem)
{
_workQueue.queue(workItem);
}
public void
joinWithAllThreads()
{
//
// _threads is immutable after destroy() has been called,
// therefore no synchronization is needed. (Synchronization
// wouldn't be possible here anyway, because otherwise the
// other threads would never terminate.)
//
for(EventHandlerThread thread : _threads)
{
thread.join();
}
//
// Destroy the selector
//
_workQueue.close();
_selector.destroy();
}
public String
prefix()
{
return _prefix;
}
private void
run(EventHandlerThread thread)
{
ThreadPoolCurrent current = new ThreadPoolCurrent(_instance, this);
boolean select = false;
while(true)
{
if(current._handler != null)
{
try
{
current._handler.message(current);
}
catch(DestroyedException ex)
{
return;
}
catch(java.lang.Exception ex)
{
String s = "exception in `" + _prefix + "':\n" + Ex.toString(ex);
s += "\nevent handler: " + current._handler.toString();
_instance.initializationData().logger.error(s);
}
}
else if(select)
{
try
{
_selector.select(_serverIdleTime);
}
catch(Selector.TimeoutException ex)
{
synchronized(this)
{
if(!_destroyed && _inUse == 0)
{
_workQueue.queue(new ShutdownWorkItem()); // Select timed-out.
}
continue;
}
}
}
synchronized(this)
{
if(current._handler == null)
{
if(select)
{
_selector.finishSelect(_handlers, _serverIdleTime);
_nextHandler = _handlers.iterator();
select = false;
}
else if(!current._leader && followerWait(thread, current))
{
return; // Wait timed-out.
}
}
else if(_sizeMax > 1)
{
if(!current._ioCompleted)
{
//
// The handler didn't call ioCompleted() so we take care of decreasing
// the IO thread count now.
//
--_inUseIO;
if((current.operation & SocketOperation.Read) != 0 && current._handler.hasMoreData())
{
_selector.hasMoreData(current._handler);
}
}
else
{
//
// If the handler called ioCompleted(), we re-enable the handler in
// case it was disabled and we decrease the number of thread in use.
//
_selector.enable(current._handler, current.operation);
assert(_inUse > 0);
--_inUse;
}
if(!current._leader && followerWait(thread, current))
{
return; // Wait timed-out.
}
}
else if(!current._ioCompleted &&
(current.operation & SocketOperation.Read) != 0 && current._handler.hasMoreData())
{
_selector.hasMoreData(current._handler);
}
//
// Get the next ready handler.
//
if(_nextHandler.hasNext())
{
current._ioCompleted = false;
current._handler = _nextHandler.next();
current.operation = current._handler._ready;
}
else
{
current._handler = null;
}
if(current._handler == null)
{
//
// If there are no more ready handlers and there are still threads busy performing
// IO, we give up leadership and promote another follower (which will perform the
// select() only once all the IOs are completed). Otherwise, if there's no more
// threads peforming IOs, it's time to do another select().
//
if(_inUseIO > 0)
{
promoteFollower(current);
}
else
{
_selector.startSelect();
select = true;
}
}
else if(_sizeMax > 1)
{
//
// Increment the IO thread count and if there's still threads available
// to perform IO and more handlers ready, we promote a follower.
//
++_inUseIO;
if(_nextHandler.hasNext() && _inUseIO < _sizeIO)
{
promoteFollower(current);
}
}
}
}
}
void
ioCompleted(ThreadPoolCurrent current)
{
current._ioCompleted = true; // Set the IO completed flag to specify that ioCompleted() has been called.
if(_sizeMax > 1)
{
synchronized(this)
{
--_inUseIO;
if((current.operation & SocketOperation.Read) != 0 && current._handler.hasMoreData())
{
_selector.hasMoreData(current._handler);
}
if(_serialize && !_destroyed)
{
_selector.disable(current._handler, current.operation);
}
if(current._leader)
{
//
// If this thread is still the leader, it's time to promote a new leader.
//
promoteFollower(current);
}
else if(_promote && (_nextHandler.hasNext() || _inUseIO == 0))
{
notify();
}
assert(_inUse >= 0);
++_inUse;
if(_inUse == _sizeWarn)
{
String s = "thread pool `" + _prefix + "' is running low on threads\n"
+ "Size=" + _size + ", " + "SizeMax=" + _sizeMax + ", " + "SizeWarn=" + _sizeWarn;
_instance.initializationData().logger.warning(s);
}
if(!_destroyed)
{
assert(_inUse <= _threads.size());
if(_inUse < _sizeMax && _inUse == _threads.size())
{
if(_instance.traceLevels().threadPool >= 1)
{
String s = "growing " + _prefix + ": Size=" + (_threads.size() + 1);
_instance.initializationData().logger.trace(_instance.traceLevels().threadPoolCat, s);
}
try
{
EventHandlerThread thread = new EventHandlerThread(_threadPrefix + "-" + _threadIndex++);
_threads.add(thread);
if(_hasPriority)
{
thread.start(_priority);
}
else
{
thread.start(java.lang.Thread.NORM_PRIORITY);
}
}
catch(RuntimeException ex)
{
String s = "cannot create thread for `" + _prefix + "':\n" + Ex.toString(ex);
_instance.initializationData().logger.error(s);
}
}
}
}
}
else if((current.operation & SocketOperation.Read) != 0 && current._handler.hasMoreData())
{
synchronized(this)
{
_selector.hasMoreData(current._handler);
}
}
}
private synchronized void
promoteFollower(ThreadPoolCurrent current)
{
assert(!_promote && current._leader);
_promote = true;
if(_inUseIO < _sizeIO && (_nextHandler.hasNext() || _inUseIO == 0))
{
notify();
}
current._leader = false;
}
private synchronized boolean
followerWait(EventHandlerThread thread, ThreadPoolCurrent current)
{
assert(!current._leader);
//
// It's important to clear the handler before waiting to make sure that
// resources for the handler are released now if it's finished. We also
// clear the per-thread stream.
//
current._handler = null;
current.stream.reset();
//
// Wait to be promoted and for all the IO threads to be done.
//
while(!_promote || _inUseIO == _sizeIO || !_nextHandler.hasNext() && _inUseIO > 0)
{
try
{
if(_threadIdleTime > 0)
{
long before = IceInternal.Time.currentMonotonicTimeMillis();
wait(_threadIdleTime * 1000);
if(IceInternal.Time.currentMonotonicTimeMillis() - before >= _threadIdleTime * 1000)
{
if(!_destroyed && (!_promote || _inUseIO == _sizeIO ||
(!_nextHandler.hasNext() && _inUseIO > 0)))
{
if(_instance.traceLevels().threadPool >= 1)
{
String s = "shrinking " + _prefix + ": Size=" + (_threads.size() - 1);
_instance.initializationData().logger.trace(_instance.traceLevels().threadPoolCat, s);
}
assert(_threads.size() > 1); // Can only be called by a waiting follower thread.
_threads.remove(thread);
_workQueue.queue(new JoinThreadWorkItem(thread));
return true;
}
}
}
else
{
wait();
}
}
catch(InterruptedException ex)
{
}
}
current._leader = true; // The current thread has become the leader.
_promote = false;
return false;
}
private final Instance _instance;
private final ThreadPoolWorkQueue _workQueue;
private boolean _destroyed;
private final String _prefix;
private final String _threadPrefix;
private final Selector _selector;
private final class EventHandlerThread implements Runnable
{
EventHandlerThread(String name)
{
_name = name;
}
public void
join()
{
while(true)
{
try
{
_thread.join();
break;
}
catch(InterruptedException ex)
{
}
}
}
public void
start(int priority)
{
_thread = new Thread(null, this, _name, _stackSize);
_thread.setPriority(priority);
_thread.start();
}
public void
run()
{
if(_instance.initializationData().threadHook != null)
{
try
{
_instance.initializationData().threadHook.start();
}
catch(java.lang.Exception ex)
{
String s = "thread hook start() method raised an unexpected exception in `";
s += _prefix + "' thread " + _name + ":\n" + Ex.toString(ex);
_instance.initializationData().logger.error(s);
}
}
try
{
ThreadPool.this.run(this);
}
catch(java.lang.Exception ex)
{
String s = "exception in `" + _prefix + "' thread " + _name + ":\n" + Ex.toString(ex);
_instance.initializationData().logger.error(s);
}
if(_instance.initializationData().threadHook != null)
{
try
{
_instance.initializationData().threadHook.stop();
}
catch(java.lang.Exception ex)
{
String s = "thread hook stop() method raised an unexpected exception in `";
s += _prefix + "' thread " + _name + ":\n" + Ex.toString(ex);
_instance.initializationData().logger.error(s);
}
}
}
final private String _name;
private Thread _thread;
}
private final int _size; // Number of threads that are pre-created.
private final int _sizeIO; // Number of threads that can concurrently perform IO.
private final int _sizeMax; // Maximum number of threads.
private final int _sizeWarn; // If _inUse reaches _sizeWarn, a "low on threads" warning will be printed.
private final boolean _serialize; // True if requests need to be serialized over the connection.
private final int _priority;
private final boolean _hasPriority;
private final long _serverIdleTime;
private final long _threadIdleTime;
private final int _stackSize;
private java.util.List<EventHandlerThread> _threads = new java.util.ArrayList<EventHandlerThread>();
private int _threadIndex; // For assigning thread names.
private int _inUse; // Number of threads that are currently in use.
private int _inUseIO; // Number of threads that are currently performing IO.
private java.util.List<EventHandler> _handlers = new java.util.ArrayList<EventHandler>();
private java.util.Iterator<EventHandler> _nextHandler;
private boolean _promote;
}