/*==========================================================================*\
| $Id: QueueDescriptor.java,v 1.8 2011/12/25 21:18:24 stedwar2 Exp $
|*-------------------------------------------------------------------------*|
| Copyright (C) 2008-2011 Virginia Tech
|
| This file is part of Web-CAT.
|
| Web-CAT is free software; you can redistribute it and/or modify
| it under the terms of the GNU Affero General Public License as published
| by the Free Software Foundation; either version 3 of the License, or
| (at your option) any later version.
|
| Web-CAT 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 General Public License for more details.
|
| You should have received a copy of the GNU Affero General Public License
| along with Web-CAT; if not, see <http://www.gnu.org/licenses/>.
\*==========================================================================*/
package org.webcat.jobqueue;
import java.util.HashMap;
import java.util.Map;
import org.apache.log4j.Logger;
import org.webcat.core.Application;
import org.webcat.woextensions.WCEC;
import org.webcat.woextensions.WCFetchSpecification;
import com.webobjects.eoaccess.EOGeneralAdaptorException;
import com.webobjects.eocontrol.*;
import com.webobjects.foundation.*;
import er.extensions.eof.ERXDefaultEditingContextDelegate;
import er.extensions.eof.ERXEC;
import er.extensions.eof.ERXQ;
import er.extensions.eof.ERXS;
// -------------------------------------------------------------------------
/**
* Represents and identifies a single database-backed queue of jobs that
* descends from {@link JobBase}. All queues that support parallel processing
* over a cluster of servers should have a QueueDescriptor and be stored
* in the database, which is the sole arbiter for concurrency control
* among clustered servers. All workers across an arbitrary number of
* servers then operate on the same shared queue of jobs stored in the
* database.
*
* @author Stephen Edwards
* @author Last changed by $Author: stedwar2 $
* @version $Revision: 1.8 $, $Date: 2011/12/25 21:18:24 $
*/
public class QueueDescriptor
extends _QueueDescriptor
{
/**
* Determines the rate at which past data points in the (exponential)
* moving average for job processing times "decay". A value of 20
* means the "half life" of the most recent job processing time is
* approximately 20 jobs. See the "S_t,alternate" and "EMA_today"
* formulae under exponential moving averages on
* http://en.wikipedia.org/wiki/Rolling_average. The decay factor
* is 1/alpha.
*/
public static final double MOVING_AVERAGE_DECAY_FACTOR = 20.0;
//~ Constructors ..........................................................
// ----------------------------------------------------------
/**
* Creates a new QueueDescriptor object.
*/
public QueueDescriptor()
{
super();
}
// ----------------------------------------------------------
/**
* Registers a queue in the database, if it has not already been
* registered, and returns the associated descriptor.
* @param context The editing context to use.
* @param theJobEntityName The name of the {@link JobBase} subclass used
* to hold the queue's contents in the database.
* @return The registered descriptor.
*/
public static QueueDescriptor descriptorFor(
EOEditingContext context, String theJobEntityName)
{
QueueDescriptor result = (QueueDescriptor)JobQueue.registerDescriptor(
context,
QueueDescriptor.ENTITY_NAME,
new NSDictionary<String, String>(
theJobEntityName,
QueueDescriptor.JOB_ENTITY_NAME_KEY),
new NSDictionary<String, Long>(
new Long(0),
QueueDescriptor.NEWEST_ENTRY_ID_KEY));
// Make sure a dispenser has been created for this queue
dispenserFor(result);
return result;
}
// ----------------------------------------------------------
/**
* Retrieve a managed descriptor for a given job queue, registering
* the queue if necessary.
* @param theJobEntityName The name of the {@link JobBase} subclass used
* to hold the queue's contents in the database.
* @return The managed descriptor.
*/
public static ManagedQueueDescriptor managedDescriptorFor(
EOEditingContext context, String theJobEntityName)
{
return new ManagedQueueDescriptor(
descriptorFor(context, theJobEntityName));
}
// ----------------------------------------------------------
/**
* Registers a queue in the database, if it has not already been
* registered.
* @param theJobEntityName The name of the {@link JobBase} subclass used
* to hold the queue's contents in the database.
*/
public static void registerQueue(String theJobEntityName)
{
EOEditingContext ec = queueContext();
synchronized (ec)
{
ec.lock();
try
{
QueueDescriptor qd = descriptorFor(ec, theJobEntityName);
if (!registeredDescriptors.contains(qd))
{
registeredDescriptors.add(qd);
}
log.debug("registerQueue(): registered objects = " +
ec.registeredObjects());
}
finally
{
ec.unlock();
}
}
}
//~ Methods ...............................................................
// ----------------------------------------------------------
public int pendingJobCount(EOEditingContext ec)
{
EOFetchSpecification fetchSpec = new WCFetchSpecification<JobBase>(
jobEntityName(),
ERXQ.and(
ERXQ.isFalse(JobBase.IS_CANCELLED_KEY),
ERXQ.isTrue(JobBase.IS_READY_KEY)),
null);
NSArray<?> jobs = ec.objectsWithFetchSpecification(fetchSpec);
if (log.isDebugEnabled())
{
log.debug("pendingJobCount(): " + jobs.count() + " "
+ jobEntityName() + " jobs are ready");
}
return jobs.count();
}
// ----------------------------------------------------------
/* package */ static void waitForNextJob(QueueDescriptor descriptor)
{
TokenDispenser dispenser = dispenserFor(descriptor);
int lockCount = 0;
try
{
if (descriptor.editingContext() instanceof ERXEC)
{
ERXEC ec = (ERXEC)descriptor.editingContext();
while (ec.lockCount() > 0)
{
ec.unlock();
lockCount++;
}
}
dispenser.getJobToken();
}
finally
{
while (lockCount > 0)
{
descriptor.editingContext().lock();
lockCount--;
}
}
}
// ----------------------------------------------------------
/* package */ static void newJobIsReadyOn(QueueDescriptor descriptor)
{
dispenserFor(descriptor).depositToken();
}
// ----------------------------------------------------------
private static TokenDispenser dispenserFor(QueueDescriptor descriptor)
{
Number id = descriptor.id();
assert id != null;
TokenDispenser dispenser = null;
synchronized (dispensers)
{
dispenser = dispensers.get(id);
if (dispenser == null)
{
int initialTokenCount = 0;
String name = null;
EOEditingContext ec = WCEC.newEditingContext();
try
{
ec.lock();
QueueDescriptor qd = descriptor.localInstance(ec);
initialTokenCount = qd.pendingJobCount(ec);
name = qd.jobEntityName();
}
finally
{
ec.unlock();
ec.dispose();
}
dispenser = new TokenDispenser(name, initialTokenCount);
dispensers.put(id, dispenser);
}
}
return dispenser;
}
// ----------------------------------------------------------
// Used by QueueDelegate
private static EOEditingContext queueContext()
{
return _ec;
}
// ----------------------------------------------------------
/**
* This class needs to be public as an implementation side-effect so that
* WebObjects' NSSelector can access the delegate methods.
*/
public static class QueueDelegate
extends ERXDefaultEditingContextDelegate
{
// ----------------------------------------------------------
public QueueDelegate()
{
// nothing to do
}
// ----------------------------------------------------------
public void editingContextDidMergeChanges(EOEditingContext context)
{
if (log.isDebugEnabled())
{
log.debug(this + "editingContextDidMergeChanges(" + context
+ ")");
}
if (jobContext == null)
{
jobContext = WCEC.newEditingContext();
}
synchronized (dispensers)
{
try
{
jobContext.lock();
if (log.isDebugEnabled())
{
log.debug("dispenser map = " + dispensers);
}
for (Number id : dispensers.keySet())
{
QueueDescriptor descriptor =
forId(jobContext, id.intValue());
if (log.isDebugEnabled())
{
log.debug("Updating queue info for " +
descriptor.jobEntityName());
}
dispensers.get(id).ensureAtLeastNTokens(
descriptor.pendingJobCount(jobContext));
}
}
finally
{
jobContext.unlock();
}
}
}
private EOEditingContext jobContext;
}
//~ Instance/static variables .............................................
private static EOEditingContext _ec;
public static class QueueEC extends WCEC
{
// ----------------------------------------------------------
/**
* Creates a new object.
* @param os the parent object store
*/
protected QueueEC(EOObjectStore os)
{
super(os);
}
// ----------------------------------------------------------
@Override
protected NSDictionary _objectBasedChangeInfoForGIDInfo(
NSDictionary info)
{
NSDictionary result = super._objectBasedChangeInfoForGIDInfo(info);
if (captureInfo)
{
captureInfo = false;
// log.debug("_objectBasedChangeInfoForGIDInfo() = " + result);
if (result != null)
{
@SuppressWarnings("unchecked")
NSArray<EOEnterpriseObject> updates =
(NSArray<EOEnterpriseObject>)result
.valueForKey("updated");
if (updates != null && updates.count() > 0)
{
synchronized (dispensers)
{
for (EOEnterpriseObject eo : updates)
{
if (eo instanceof QueueDescriptor)
{
QueueDescriptor descriptor =
(QueueDescriptor)eo;
log.debug(
"QueueEC: Updating queue info for "
+ descriptor.jobEntityName());
dispensers.get(descriptor.id())
.ensureAtLeastNTokens(
descriptor.pendingJobCount(this));
}
}
}
}
}
}
return result;
}
// ----------------------------------------------------------
@Override
public void _processObjectStoreChanges(NSDictionary arg0)
{
captureInfo = true;
if (log.isDebugEnabled())
{
log.debug("QueueEC: _processObjectStoreChanges(): "
+ arg0);
try
{
log.debug(" known = " + registeredObjects());
}
catch (Exception e)
{
log.debug(" known = <exception printing list>");
}
}
super._processObjectStoreChanges(arg0);
}
//~ Instance/static variables .........................................
private boolean captureInfo = false;
}
static {
// _ec = Application.newPeerEditingContext();
_ec = new WCEC.WCECFactory() {
protected EOEditingContext _createEditingContext(
EOObjectStore parent)
{
return new QueueEC(parent == null
? EOEditingContext.defaultParentObjectStore()
: parent);
}
}._newEditingContext();
// _ec.setDelegate(new QueueDelegate());
}
// Accessed by inner QueueDelegate
/* package */ static Map<Number, TokenDispenser> dispensers =
new HashMap<Number, TokenDispenser>();
// Keep all registered descriptors loaded in _ec in this array so
// that the EC can detect changes to them, without them being
// garbage-collected.
private static final NSMutableArray<QueueDescriptor> registeredDescriptors
= new NSMutableArray<QueueDescriptor>();
static Logger log = Logger.getLogger(QueueDescriptor.class);
}