/*
* Minha.pt: middleware testing platform.
* Copyright (c) 2011-2014, Universidade do Minho.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package pt.minha.models.local.lang;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pt.minha.api.Host;
import pt.minha.api.sim.Calibration;
import pt.minha.kernel.simulation.Event;
import pt.minha.kernel.simulation.Timeline;
import pt.minha.kernel.timer.IntervalTimer;
import pt.minha.kernel.timer.TimerProvider;
import pt.minha.models.global.SimulationThreadDeath;
import pt.minha.models.local.SimulationProcess;
public class SimulationThread extends Thread implements Closeable {
private static IntervalTimer timer = TimerProvider.open();
private pt.minha.models.fake.java.lang.Thread fakeThread;
private long id;
private SimulationProcess process;
private Calibration nc;
private long time;
private boolean realTime = false;
private boolean blocked, rt, dead, started, interruptible, interrupted;
private boolean parked, permit;
private WakeUpEvent wakeup;
private Condition wakeupCond;
private ReentrantLock lock;
private Runnable runnable;
public long totalCPU;
private Logger logger;
// fake_join
private List<Event> waitingOnJoin = new ArrayList<Event>();
public SimulationThread(boolean rt, SimulationProcess host, Runnable runnable, pt.minha.models.fake.java.lang.Thread fakeThread) {
if (host==null)
host = ((SimulationThread) Thread.currentThread()).process;
setContextClassLoader(this.getClass().getClassLoader());
this.rt = rt;
this.process = host;
this.nc = process.getNetwork().getConfig();
this.runnable = runnable;
this.fakeThread = fakeThread;
this.id=host.getNextThreadId();
this.wakeup = new WakeUpEvent();
this.lock = new ReentrantLock();
this.wakeupCond = lock.newCondition();
blocked = true;
setDaemon(true);
logger = LoggerFactory.getLogger("pt.minha.thread."+host.getNetwork().getLocalAddress().getHostAddress().replace(".", "_")+"."+id);
}
/**
* Schedule thread start-up. The thread itself will only run in the context of
* a simulation event.
*/
@Override
public void start() {
super.start();
started = true;
SimulationThread.stopTime(0);
getWakeup().schedule(0);
SimulationThread.startTime(0);
}
public void simulationStart(long delay) {
super.start();
started = true;
getWakeup().schedule(delay);
}
public static SimulationThread currentSimulationThread() {
return (SimulationThread)Thread.currentThread();
}
public Object getFakeThread() {
return this.fakeThread;
}
public SimulationProcess getProcess() {
return process;
}
public Host getHost() {
return process.getHost();
}
public Timeline getTimeline() {
return process.getCPU().getTimeline();
}
public void run() {
process.addThread(this);
lock.lock();
while(blocked && !dead)
wakeupCond.awaitUninterruptibly();
lock.unlock();
try {
if (dead)
return;
if (rt) startTime(0);
runnable.run();
} catch(SimulationThreadDeath d) {
if (fake_isDaemon())
logger.info("Daemon thread killed");
else
logger.warn("Non-daemon thread killed.");
} catch (Throwable e) {
pt.minha.models.fake.java.lang.Thread.UncaughtExceptionHandler handler = fakeThread.getUncaughtExceptionHandler();
try {
if (handler != null) {
handler.uncaughtException(fakeThread, e);
e = null;
}
else{
if(this.process.getDefaultHandler()!=null) {
this.process.getDefaultHandler().uncaughtException(fakeThread, e);
e = null;
}
}
} catch(Throwable t) {
e = t;
}
if (e != null)
logger.warn("Uncaught exception", e);
} finally {
lock.lock();
blocked = true;
wakeupCond.signal();
dead = true;
lock.unlock();
process.removeThread(this);
for(Event e: waitingOnJoin) {
e.schedule(0);
}
waitingOnJoin.clear();
}
}
public void fake_join(long timeout) throws InterruptedException {
fake_join(timeout, 0);
}
public void fake_join(long millis, long nanos) throws InterruptedException {
try {
long now = SimulationThread.stopTime(0);
if (dead)
return;
SimulationThread current = SimulationThread.currentSimulationThread();
long timeout = millis*1000000+nanos;
long target = 0;
if (timeout > 0) {
target = now + timeout;
}
waitingOnJoin.add(current.getWakeup());
while(!dead) {
if (timeout > 0) {
long delta = target - getTimeline().getTime();
if (delta <= 0)
break;
current.getWakeup().schedule(target);
}
if (current.pause(true, false))
break;
}
if (!dead)
waitingOnJoin.remove(current.getWakeup());
if (current.getInterruptedStatus(true))
throw new InterruptedException();
} finally {
SimulationThread.startTime(0);
}
}
/**
* This method gets called by the time-line thread. It should be blocked while
* a simulation thread is executing on behalf of an event.
*/
private void wakeup() {
lock.lock();
blocked = false;
interruptible = false;
wakeupCond.signal();
while(!blocked && !dead)
wakeupCond.awaitUninterruptibly();
lock.unlock();
}
public void fake_interrupt() {
interrupted = true;
try {
lock.lock();
if (interruptible)
wakeup();
} finally {
lock.unlock();
}
}
public boolean getInterruptedStatus(boolean clear) {
boolean r = interrupted;
if (clear)
interrupted = false;
return r;
}
/**
* Generic method to suspend the thread. Typically, the threads
* wake-up event will have been scheduled in the future or have
* been placed in some queue from where it will be scheduled.
*
* This method also provides support for thread interruption.
* Note however that it doesn't check, on entry, whether the
* thread is already interrupted. If no blocking is desired
* in such situation, it should be checked before entering
* pause.
*
* @param interruptible makes the thread wake-up on interrupt
* @param clear if true, will clear interrupted state on exit
* @return interrupted state
*/
public boolean pause(boolean interruptible, boolean clear) {
lock.lock();
blocked = true;
this.interruptible = interruptible;
wakeupCond.signal();
while(blocked && !dead)
wakeupCond.awaitUninterruptibly();
lock.unlock();
if (dead)
throw new SimulationThreadDeath();
return getInterruptedStatus(clear);
}
public Event getWakeup() {
return wakeup;
}
private void resync() {
lock.lock();
blocked = true;
wakeupCond.signal();
while(blocked && !dead)
wakeupCond.awaitUninterruptibly();
lock.unlock();
if (dead)
throw new SimulationThreadDeath();
}
/**
* Acquire CPU resource and start executing real code. Acquiring the CPU
* might advance the simulation time, thus blocking the calling thread.
* @throws InterruptedException
*/
public static void startTime(long overhead) {
((SimulationThread) currentThread()).doStartTime(overhead);
}
private void doStartTime(long overhead) {
if (realTime)
throw new RuntimeException("restarting time");
process.getCPU().acquire(getWakeup());
resync();
realTime = true;
time = timer.getTime() - overhead;
}
/**
* Stop executing real code and free CPU resource. Stopping real code will advance
* the simulation time, thus blocking the calling thread.
* @throws InterruptedException
*/
public static long stopTime(long overhead) {
return ((SimulationThread) currentThread()).doStopTime(overhead);
}
private long doStopTime(long overhead) {
long delta = nc.getCPUDelay(timer.getTime() - time) + overhead;
if (delta<0)
delta = 1;
if (!realTime)
throw new RuntimeException("restopping time");
realTime = false;
totalCPU += delta;
process.getCPU().release(delta, getWakeup());
resync();
return getTimeline().getTime();
}
public boolean idle(long delta, boolean interruptible, boolean clear) {
if (realTime)
throw new RuntimeException("time not stopped");
getWakeup().schedule(delta);
return pause(interruptible, clear);
}
private class WakeUpEvent extends Event {
public WakeUpEvent() {
super(process.getTimeline());
}
public void run() {
wakeup();
}
public String toString() {
return super.toString()+"->"+SimulationThread.this;
}
}
public boolean fake_isAlive() {
return started && !dead;
}
public long fake_getId() {
return id;
}
public boolean fake_isDaemon() {
return fakeThread.isDaemon();
}
public StackTraceElement[] fake_getStackTrace() {
return this.getStackTrace();
}
public void park(Object blocker, long nanos) {
if (permit)
permit = false;
else {
fakeThread.parkBlocker = blocker;
stopTime(0);
if (nanos>0)
getWakeup().schedule(nanos);
parked = true;
pause(true, false);
parked = false;
startTime(0);
}
}
public void unpark(pt.minha.models.fake.java.lang.Thread target) {
SimulationThread st = target.getSimulationThread();
if (st.parked) {
stopTime(0);
st.getWakeup().schedule(0);
startTime(0);
} else
st.permit = true;
}
public String toString() {
return "SimulationThread@"+process.getNetwork().getLocalAddress().getHostAddress()+"["+fakeThread.getName()+"]";
}
@Override
public void close() throws IOException {
lock.lock();
dead = true;
wakeupCond.signalAll();
lock.unlock();
}
/*
public void m() {
realCode();
SimulationThread.stopTime(0);
// One event starts here
simStuff();
Event e=getWakeup();
e.schedule(someTime);
simStuff();
pause(); // ends here
// One event starts here
simStuff();
Event e=getWakeup();
e.schedule(someTime);
simStuff();
pause(); // ends here
// Another event
simStuff();
SimulationThread.startTime(0);
realCode();
}
*/
}