/*
* JaamSim Discrete Event Simulation
* Copyright (C) 2002-2014 Ausenco Engineering Canada Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jaamsim.events;
import java.util.ArrayList;
/**
* Process is a subclass of Thread that can be managed by the discrete event
* simulation.
*
* This is the basis for all functionality required by startProcess and the
* discrete event model. Each process creates its own thread to run in. These
* threads are managed by the eventManager and when a Process has completed
* running is pooled for reuse.
*
* LOCKING: All state in the Process must be updated from a synchronized block
* using the Process itself as the lock object. Care must be taken to never take
* the eventManager's lock while holding the Process's lock as this can cause a
* deadlock with other threads trying to wake you from the threadPool.
*/
final class Process extends Thread {
// Properties required to manage the pool of available Processes
private static final ArrayList<Process> pool; // storage for all available Processes
private static final int maxPoolSize = 100; // Maximum number of Processes allowed to be pooled at a given time
private static int numProcesses = 0; // Total of all created processes to date (used to name new Processes)
private EventManager eventManager; // The EventManager that is currently managing this Process
private Process nextProcess; // The Process from which the present process was created
private ProcessTarget target; // The entity whose method is to be executed
// These are a very special references that is only safe to use from the currently
// executing Process, they are essentially Threadlocal variables that are only valid
// when activeFlag == true
private EventManager evt;
private boolean hasNext;
private boolean dieFlag;
private boolean activeFlag;
private boolean inUserCallback;
// Initialize the storage for the pooled Processes
static {
pool = new ArrayList<>(maxPoolSize);
}
private Process(String name) {
// Construct a thread with the given name
super(name);
}
/**
* Returns the currently executing Process.
*/
static final Process current() {
try {
return (Process)Thread.currentThread();
}
catch (ClassCastException e) {
throw new ProcessError("Non-process thread called Process.current()");
}
}
/**
* Run method invokes the method on the target with the given arguments.
* A process loops endlessly after it is created executing the method on the
* target set as the entry point. After completion, it calls endProcess and
* will return it to a process pool if space is available, otherwise the resources
* including the backing thread will be released.
*
* This method is called by Process.getProcess()
*/
@Override
public void run() {
while (true) {
waitInPool();
// Process has been woken up, execute the method we have been assigned
ProcessTarget t;
synchronized (this) {
evt = eventManager;
t = target;
target = null;
activeFlag = true;
hasNext = (nextProcess != null);
}
evt.execute(this, t);
// Ensure all state is cleared before returning to the pool
evt = null;
hasNext = false;
setup(null, null, null);
}
}
final boolean hasNext() {
return hasNext;
}
final EventManager evt() {
return evt;
}
// Useful to filter pooled threads when staring at stack traces.
private void waitInPool() {
synchronized (pool) {
// Add ourselves to the pool and wait to be assigned work
pool.add(this);
// Set the present process to sleep, and release its lock
// (done by pool.wait();)
// Note: the try/while(true)/catch construct is needed to avoid
// spurious wake ups allowed as of Java 5. All legitimate wake
// ups are done through the InterruptedException.
try {
while (true) { pool.wait(); }
} catch (InterruptedException e) {}
}
}
/*
* Setup the process state for execution.
*/
private synchronized void setup(EventManager evt, Process next, ProcessTarget targ) {
eventManager = evt;
nextProcess = next;
target = targ;
activeFlag = false;
dieFlag = false;
inUserCallback = false;
}
// Pull a process from the pool and have it attempt to execute events from the
// given eventManager
static void processEvents(EventManager evt) {
Process newProcess = Process.getProcess();
newProcess.setup(evt, null, null);
newProcess.wake();
}
// Set up a new process for the given entity, method, and arguments
// Called from Process.start() and from EventManager.startExternalProcess()
static Process allocate(EventManager eventManager, Process next, ProcessTarget proc) {
Process newProcess = Process.getProcess();
newProcess.setup(eventManager, next, proc);
return newProcess;
}
// Return a process from the pool or create a new one
private static Process getProcess() {
while (true) {
synchronized (pool) {
// If there is an available process in the pool, then use it
if (pool.size() > 0) {
return pool.remove(pool.size() - 1);
}
// If there are no process in the pool, then create a new one and add it to the pool
else {
numProcesses++;
Process temp = new Process("processthread-" + numProcesses);
temp.start(); // Note: Thread.start() calls Process.run which adds the new process to the pool
}
}
// Allow the Process.run method to execute so that it can add the
// new process to the pool
// Note: that the lock on the pool has been dropped, so that the
// Process.run method can grab it.
try { Thread.sleep(10); } catch (InterruptedException e) {}
}
}
/**
* We override this method to prevent user code from breaking the event state machine.
* If user code explicitly interrupted a Process it would likely run event code
* much earlier than intended.
*/
@Override
public void interrupt() {
new Throwable("AUDIT: direct call of Process.interrupt").printStackTrace();
}
/**
* This is the wrapper to allow internal code to advance the state machine by waking
* a Process.
*/
final void wake() {
super.interrupt();
}
synchronized void setNextProcess(Process next) {
nextProcess = next;
}
/**
* Returns true if we woke a next Process, otherwise return false.
*/
synchronized final void wakeNextProcess() {
nextProcess.wake();
nextProcess = null;
hasNext = false;
}
synchronized void kill() {
if (activeFlag)
throw new ProcessError("Cannot terminate an active thread");
dieFlag = true;
this.wake();
}
/**
* This is used to tear down a live threadstack when an error is received from
* the model.
*/
synchronized Process forceKillNext() {
Process ret = nextProcess;
nextProcess = null;
if (ret != null) {
ret.dieFlag = true;
ret.wake();
}
return ret;
}
synchronized boolean shouldDie() {
return dieFlag;
}
synchronized final Process preCapture() {
activeFlag = false;
Process ret = nextProcess;
nextProcess = null;
hasNext = false;
return ret;
}
synchronized final void postCapture() {
activeFlag = true;
hasNext = (nextProcess != null);
}
final void beginCallbacks() {
inUserCallback = true;
}
final void endCallbacks() {
inUserCallback = false;
}
final void checkCallback() {
if (inUserCallback)
throw new ProcessError("Event Control attempted from inside a user callback");
}
}