/*
* This file is part of the HyperGraphDB source distribution. This is copyrighted
* software. For permitted uses, licensing options and redistribution, please see
* the LicensingInformation file at the root level of the distribution.
*
* Copyright (c) 2005-2010 Kobrix Software, Inc. All rights reserved.
*/
package org.hypergraphdb.util;
import java.util.LinkedList;
import java.util.concurrent.Semaphore;
/**
* <p>
* This a simple queue that runs as a thread and executes passed
* in action object (i.e. <code>Runnable</code> instances) in a
* sequence. It can be configured with a maximum size. When this maximum
* size is reached and a new element is added to it, it will not return
* until some percentage of the accumulated actions has been completed.
* </p>
*
* @author Borislav Iordanov
*/
public class ActionQueueThread extends Thread
{
public static final int DEFAULT_NON_BLOCKING_SIZE = 100000;
public static final int DEFAULT_FREE_PERCENT_ON_BLOCK = 50;
/**
* Thread can be paused every n actions.
*/
public static final int PAUSE_GRANULARITY_ACTIONS = 100;
private HGLogger logger = new HGLogger();
/**
* The current list of actions to execute.
*/
private LinkedList<Runnable> actionList = new LinkedList<Runnable>();
/**
* The max size of the action queue before it block a calling thread.
*/
private int nonBlockingSize = DEFAULT_NON_BLOCKING_SIZE;
/**
* The percentage of actions of a filled up queue that must be
* completed before we unblock a calling thread.
*/
// 2012.02.01 hilpold Bugfix private double freeFactor = 1.0 - 1.0/DEFAULT_FREE_PERCENT_ON_BLOCK;
//
private double freeFactor = 1.0 - DEFAULT_FREE_PERCENT_ON_BLOCK / 100.0;
/**
* A flag indicating whether the thread is currently running. Set by clients
* to cause the main loop to exit.
*/
private boolean running = false;
/**
* The total number of actions executed by this thread, whether or not
* the actions have terminated with an exception.
*/
private long completedCount = 0;
/**
* The thread can be paused and resumed at the granularity of PAUSE_GRANULARITY_ACTIONS
* actions.
*/
private Semaphore pauseMutex = new Semaphore(1);
/**
* <p>Default constructor. Unnamed action queue with a default
* max size.
* </p>
*/
public ActionQueueThread()
{
}
/**
* <p>Constructs an <code>ActionQueue</code> with a specific thread
* name and a default max size.
* </p>
*
* @param name The name of the action queue thread.
*/
public ActionQueueThread(String name)
{
super(name);
}
/**
* <p>Constructs an <code>ActionQueue</code> with a specific thread
* name and max size.
* </p>
*
* @param name The name of the action queue thread.
* @param maxSizeBeforeBlock The maximum number of actions waiting in the queue
* before this <code>ActionQueue</code> blocks a calling thread.
* @param completPercentUponBlocking The amount, in percent, of action to execute
* from a filled up queue before we return to the blocked thread.
*/
public ActionQueueThread(String name,
int maxSizeBeforeBlock,
int completePercentUponBlocking)
{
this(name);
this.nonBlockingSize = maxSizeBeforeBlock;
// 2012.02.01 hilpold BugFix freefactor calculation wrong, values were too high.
// Observed low processor load on long tasks.
// this.freeFactor = 1.0 - 1.0/completePercentUponBlocking;
this.freeFactor = 1.0 - completePercentUponBlocking / 100.0;
}
public void run()
{
int pauseActionCounter = 0;
for (running = true; running; )
{
Runnable action = null;
synchronized (actionList)
{
while (actionList.isEmpty() && running)
try { actionList.wait(1000); }
catch (InterruptedException ex) { running = false; }
if (actionList.isEmpty())
break;
action = (Runnable)actionList.removeFirst();
}
try
{
if (pauseActionCounter == 0) {
// acquiring mutex is slow
pauseMutex.acquire();
}
action.run();
pauseActionCounter ++;
}
catch (InterruptedException ex)
{
break;
}
catch (Throwable t)
{
logger.exception(t);
}
finally
{
completedCount++;
if (pauseActionCounter == PAUSE_GRANULARITY_ACTIONS) {
pauseMutex.release();
pauseActionCounter = 0;
}
}
}
//
// Complete pending actions after thread stopped.
//
while (!actionList.isEmpty())
{
Runnable action = (Runnable)actionList.removeFirst();
action.run();
}
}
/**
* <p>Suspend the execution of actions until the <code>resumeActions</code> method is
* called. Blocks until all current actions (@see PAUSE_GRANULARITY_ACTIONS)
* complete execution.</p>
*/
public void pauseActions()
{
try
{
pauseMutex.acquire();
}
catch (InterruptedException ex) { }
}
/**
* <p>Resume action processing previously paused by a call to <code>pauseActions</code>.</p>
*/
public void resumeActions()
{
pauseMutex.release();
}
public void addAction(Runnable action)
{
//
// Make sure we don't store too many elements in the update list.
// Block while update list is larger than the allowed maximum
// non-blocking size.
//
if (actionList.size() > nonBlockingSize)
{
// this.setPriority(Thread.NORM_PRIORITY + 2);
while (actionList.size() > nonBlockingSize * freeFactor)
{
try
{
//2012.02.06 hilpold decreaesed sleep after profiling Thread.sleep(50);
//2012.02.06 11:30 Thread.sleep(SLEEP_TIME);
//2012.02.06 12:00 Thread.yield();
Thread.sleep(0, 100);
}
catch (InterruptedException ex)
{
break;
}
}
}
synchronized (actionList)
{
actionList.addLast(action);
//2012.02.06 actionList.notify();
actionList.notifyAll();
}
}
/**
* <p>Put an action in front of the queue so that it's executed
* next or close to next. This method will not block even
* if the size of the accumulated actions exceeds the blocking
* threshold </p>
*/
public void prependAction(Runnable action)
{
synchronized (actionList)
{
actionList.addFirst(action);
actionList.notify();
}
}
/**
* <p>Complete all scheduled actions at the time of this call. Since other
* threads may keep adding actions, this method makes sure that only
* the actions in the queue at the time of the call are waited upon.
* </p>
*
*/
public void completeAll()
{
long currentCompleted = completedCount;
// ActionList.size() may be 0, but we may still an action currently running so we
// wait for at least one action to complete.
int size = Math.max(1, actionList.size());
while (completedCount - currentCompleted < size)
{
// if the thread is waiting or it's terminated, we have no business staying in here...
if (getState() != Thread.State.BLOCKED && getState() != Thread.State.RUNNABLE)
break;
try { Thread.sleep(50); }
catch (InterruptedException ex) { break; }
}
}
/**
* <p>Clear all actions. The currently executing actions will still complete,
* but all others will be removed.</p>
*/
public void clearAll()
{
synchronized (actionList)
{
actionList.clear();
}
}
/**
* <p>Return the total number of actions executed by this thread, whether or not
* the actions have terminated with an exception.</p>
*/
public long getCompletedCount()
{
return this.completedCount;
}
public boolean isRunning()
{
return running;
}
public void stopRunning()
{
running = false;
synchronized (actionList)
{
actionList.notify();
}
}
}