package io.vivarium.util.concurrency;
import com.google.common.base.Preconditions;
/**
* The VoidFunctionScheduler can be used to execute a VoidFunction (presumably with side effects) periodically, or when
* requested external input. External requests to execute the VoidFunction will cause the VoidFunction to immediately
* re-execute if they come in during the VoidFunction execution. Periodic re-runs will not exhibit this queue behavior
* by design.
*/
public class VoidFunctionScheduler implements StartableStoppable
{
// Dependencies
private final VoidFunction _function;
private final long _repeatEveryInMS;
// State variables
private boolean _running = false;
private boolean _stopped = false;
private boolean _functionExecuting;
private boolean _functionQueued;
// Helper thread
PeroidicFunctionExecutor _helperThread;
public VoidFunctionScheduler(VoidFunction function, long repeatEveryInMS)
{
_function = function;
_repeatEveryInMS = repeatEveryInMS;
_helperThread = new PeroidicFunctionExecutor();
}
/**
* Starts the scheduler
*/
@Override
public synchronized void start()
{
Preconditions.checkNotNull(_stopped);
_running = true;
_functionExecuting = false;
_functionQueued = false;
_helperThread.start();
}
/**
* Stops the scheduler
*/
@Override
public synchronized void stop()
{
_running = false;
_stopped = true;
_functionExecuting = false;
_functionQueued = false;
}
/**
* This method causes the object to immediately start the function, or queue the function to start as soon as the
* current execution is completed.
*/
public synchronized void execute()
{
startFunctionExecute(true);
}
/**
* This method causes the object to immediately start the function, or if the function is currently executing,
* potentially queue another execution of the function.
*
* @param force
* Whether the function should be forced to execute. If this value is false, and the function is
* currently executing, this method call will be ignore. If this value is true, and function is currently
* executing, another function execution will be queued up to execute as soon as the current execution
* completes. If another function execution is already queued, this parameter has no effect and this
* method call will be ignored.
*/
private synchronized void startFunctionExecute(boolean force)
{
// If the scheduler is not running, exit immediately.
if (!_running)
{
return;
}
// If an external event has triggered this function execution, we want to redo the function execution as soon as
// the current one ends, so queue another function execution.
if (force && _functionExecuting)
{
_functionQueued = true;
}
// Start an function execution if we don't have one executing already. We do this in a thread to keep the
// calling thread from blocking for the whole duration.
if (!_functionExecuting)
{
_functionExecuting = true;
new FunctionThread().start();
}
}
/**
* Marks the function as no longer executing, and consumes the function execution queue, starting another function
* execution if required.
*/
private synchronized void endFunctionExecute()
{
// We're done executing the function.
_functionExecuting = false;
// If an function has been queued while we were executing, immediately start a new function execution.
if (_functionQueued)
{
_functionQueued = false;
startFunctionExecute(false);
}
}
/**
* Triggers enforcement periodically.
*/
private class PeroidicFunctionExecutor extends Thread
{
@Override
public void run()
{
while (_running)
{
try
{
startFunctionExecute(false);
Thread.sleep(_repeatEveryInMS);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
/**
* Executes enforcement in a thread to allow other threads to not block. The startEnforce and endEnforce methods are
* synchronized, so this class allows the startEnforce method to return and prevent the caller from blocking.
*/
private class FunctionThread extends Thread
{
@Override
public void run()
{
_function.execute();
endFunctionExecute();
}
}
}