package org.jacorb.poa;
/*
* JacORB - a free Java ORB
*
* Copyright (C) 1997-2014 Gerald Brose / The JacORB Team.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
import java.util.HashSet;
import java.util.LinkedList;
import org.jacorb.config.Configuration;
import org.jacorb.config.ConfigurationException;
import org.jacorb.poa.except.POAInternalError;
import org.slf4j.Logger;
/**
* This class provides and manages a pool of ready started threads for
* request processing.
*
* @author Gerald Brose
* @author Reimo Tiedemann
* @see org.jacorb.poa.RequestProcessor
*/
public abstract class RPPoolManager
{
private RPPoolManagerListener pmListener;
// the current for (un)registering the invocation contexts
private final Current current;
/**
* <code>pool</code> is the set of currently available (inactive) request processors
*/
private final LinkedList<RequestProcessor> pool;
/**
* <code>activeProcessors</code> is the set of currently active processors
*/
private final HashSet<RequestProcessor> activeProcessors;
/**
* <code>numberOfProcessors</code> represents the current number of used <b>AND</b> unused request
* processors in the pools (active/inactive)
*/
private int numberOfProcessors;
private int numberOfWaiters;
/**
* <code>max_pool_size</code> is the maximum size of the pool. This is effectively its
* burst size
*/
private final int max_pool_size;
/**
* <code>min_pool_size</code> is the minimum number of request processors. This is the
* permanent number of processors held in the pool.
*/
private final int min_pool_size;
// a flag for delay the pool initialization
private boolean inUse = false;
private final Configuration configuration;
private final Logger logger;
/**
* Used to add a timeout to the time it will wait for a requestprocessor.
*/
private final int poolThreadTimeout;
protected RPPoolManager(Current _current, int min, int max, int pt,
Logger _logger, Configuration _configuration)
{
current = _current;
max_pool_size = max;
min_pool_size = min;
poolThreadTimeout = pt;
logger = _logger;
configuration = _configuration;
numberOfProcessors = 0;
numberOfWaiters = 0;
pool = new LinkedList<RequestProcessor>();
activeProcessors = new HashSet<RequestProcessor>();
}
private void init()
{
if (inUse)
{
return;
}
for (int i = 0; i < min_pool_size; i++)
{
addProcessor();
}
inUse = true;
}
private void addProcessor()
{
final RequestProcessor rp = new RequestProcessor(this);
try
{
rp.configure(this.configuration);
}
catch (ConfigurationException ex)
{
throw new RuntimeException (ex.toString());
}
current._addContext(rp, rp);
rp.setDaemon(true);
pool.addFirst(rp);
++numberOfProcessors;
rp.start();
}
protected synchronized void addRPPoolManagerListener(RPPoolManagerListener listener)
{
pmListener = EventMulticaster.add(pmListener, listener);
}
/**
* invoked by clients to indicate that they won't use this poolManager anymore.
*/
abstract void destroy();
/**
* shutdown this poolManager. clients should invoke {@link #destroy()} instead.
*/
protected synchronized void destroy(boolean really)
{
if (!inUse)
{
return;
}
// wait until all active processors complete
while (!activeProcessors.isEmpty())
{
try
{
wait();
}
catch (InterruptedException ex)
{
// ignore
}
}
RequestProcessor[] rps = pool.toArray(new RequestProcessor[pool.size()]);
for (int i=0; i<rps.length; i++)
{
if (rps[i].isActive())
{
throw new POAInternalError("error: request processor is active (RequestProcessorPM.destroy)");
}
pool.remove(rps[i]);
--numberOfProcessors;
current._removeContext(rps[i]);
rps[i].end();
}
inUse = false;
}
/**
* returns the number of unused processors contained in the pool
*/
protected int getPoolCount()
{
return pool.size();
}
/**
* returns the size of the processor pool (used and unused processors)
*/
protected synchronized int getPoolSize()
{
return numberOfProcessors;
}
/**
* returns a processor from pool, the first call causes
* the initialization of the processor pool,
* if no processor available the number of processors
* will increased until the max_pool_size is reached,
* this method blocks if no processor available and the
* max_pool_size is reached until a processor will released
*/
protected synchronized RequestProcessor getProcessor()
{
init();
if (pool.isEmpty() && (numberOfProcessors < max_pool_size || max_pool_size < 1))
{
addProcessor();
}
int timeout = poolThreadTimeout;
while (pool.isEmpty())
{
warnPoolIsEmpty();
long start = System.currentTimeMillis();
try
{
numberOfWaiters++;
wait(timeout);
}
catch (InterruptedException e)
{
}
finally {
numberOfWaiters--;
}
if (timeout > 0)
{
// Timeout configured, woken up and the timeout has expired but nothing to run
// it with.
if (((System.currentTimeMillis() - start) >= timeout) && pool.isEmpty ())
{
// A timeout has been configured, we have finished waiting still no processors.
// Throw an exception
throw new org.omg.CORBA.TIMEOUT ("No request processor available to handle request");
}
// If we have woken up before the timeout and the pool is still empty - have another
// go and go back to sleep.
else if ((System.currentTimeMillis() - start) < timeout && pool.isEmpty())
{
// Need to reset timeout so we finish waiting.
timeout -= (System.currentTimeMillis() - start);
}
}
// BZ946: There are some corner cases with request processor sizing.
//
// If min==max then the corner cases do not occur.
//
// In the default situation of e.g. min=5, max=20 then its
// possible that when we reach the maximum number of processors,
// use 1, and then go to release it, it will not get placed back
// in the pool (as 19 > 5). In fact all 15 of the 'burst'
// processors will be ended.
//
// If we are oscillating between min and max size then its possible that this thread
// may be looping on isEmpty. A RequestProcessor finishes and frees itself by calling
// releaseProcessor. As per above this this processor may just be released and not added
// to the pool. Therefore the pool is still isEmpty. However if another thread now tries
// to get a processor it will find the pool isEmpty and there is room to create a processor
// Therefore the pool size increases and this thread keeps looping. While it might eventually
// break out of the loop adding the below test breaks this pathological condition.
if (pool.isEmpty() && (numberOfProcessors < max_pool_size || max_pool_size < 1))
{
addProcessor();
}
}
RequestProcessor requestProcessor = pool.removeFirst();
activeProcessors.add (requestProcessor);
// notify a pool manager listener
if (pmListener != null)
{
pmListener.processorRemovedFromPool(requestProcessor, pool.size(), numberOfProcessors);
}
return requestProcessor;
}
protected void warnPoolIsEmpty()
{
if (logger.isWarnEnabled())
{
logger.warn("Thread pool exhausted, consider increasing "
+ "jacorb.poa.thread_pool_max (currently: "
+ max_pool_size + ")");
}
}
/**
* gives a processor back into the pool if the number of
* available processors is smaller than min_pool_size,
* otherwise the processor will terminate
*/
protected synchronized void releaseProcessor(RequestProcessor rp)
{
activeProcessors.remove (rp);
if (pool.size() < min_pool_size || pool.size() < numberOfWaiters)
{
pool.addFirst(rp);
}
else
{
numberOfProcessors--;
current._removeContext(rp);
rp.end();
}
// notify a pool manager listener
if (pmListener != null)
{
pmListener.processorAddedToPool(rp, pool.size(), numberOfProcessors);
}
// notify whoever is waiting for the release of active processors
notifyAll();
}
protected synchronized void removeRPPoolManagerListener(RPPoolManagerListener listener)
{
pmListener = EventMulticaster.remove(pmListener, listener);
}
}