/* ************************************************************************ # # DivConq # # http://divconq.com/ # # Copyright: # Copyright 2014 eTimeline, LLC. All rights reserved. # # License: # See the license.txt file in the project's top-level directory for details. # # Authors: # * Andy White # ************************************************************************ */ package divconq.work; import java.util.concurrent.locks.StampedLock; import divconq.hub.Hub; import divconq.lang.op.OperationContext; import divconq.log.Logger; public class Worker implements Runnable { protected Thread thread = null; protected boolean stopping = false; protected int slot = 0; protected TaskRun run = null; // we are busy when not null protected int taskThrottle = -1; protected boolean resumeTask = false; protected StampedLock lock = new StampedLock(); public void start(int slot) { this.slot = slot; this.thread = new Thread(this, "WorkPool_" + this.slot); this.thread.setDaemon(true); // TODO consider this option //this.thread.setUncaughtExceptionHandler(eh); this.thread.start(); } // if tr is the current running task then allow it to resume without going all the way to the back of the line // note that if we get here then we are the task (only the task is allowed to resume itself) so no need to // lock the member variables // // return false if we cannot resume public boolean resume(TaskRun tr) { long stamp = this.lock.readLock(); try { if ((this.run != tr) || (this.taskThrottle == 0)) return false; long wstamp = this.lock.tryConvertToWriteLock(stamp); if (wstamp == 0L) { this.lock.unlockRead(stamp); wstamp = this.lock.writeLock(); } stamp = wstamp; // check again for good measure, in case we didn't get write convert if ((this.run != tr) || (this.taskThrottle == 0)) return false; this.resumeTask = true; return true; } finally { this.lock.unlock(stamp); } } @Override public void run() { OperationContext.useHubContext(); Logger.trace("Work pool thread started: " + this.slot); Hub.instance.getWorkPool().incThreadsCreated(); while (!this.stopping) { try { Logger.trace("Work pool thread taking: " + this.slot); TaskRun r = Hub.instance.getWorkPool().take(); r.touch(); // make sure the task does not timeout immediately (especially when debugging) int tthrottle = r.getTask().getThrottle(); // always run at least once even if we get 0 long wstamp = this.lock.writeLock(); try { this.run = r; this.taskThrottle = tthrottle; this.resumeTask = false; // run once } finally { this.lock.unlock(wstamp); } // it is important to record the slot, if we do not then during resume // we may find an older worker still pointing to this run and think it can resume us r.slot = this.slot; // taskThrottle == -1 means unlimited local resumes // taskThrottle == 0 or 1 means don't allow local resumes // taskThrottle > 1 means allow N - 1 number of resumes before putting back into the work pool while (!this.stopping) { long stamp1 = this.lock.writeLock(); try { // -1 has special meaning, do not go under 0 if (this.taskThrottle > 0) this.taskThrottle--; this.resumeTask = false; } finally { this.lock.unlock(stamp1); } //Logger.trace("Work pool thread running: " + this.slot); r.run(); OperationContext.useHubContext(); long stamp2 = this.lock.readLock(); try { // if we have no more resumes then break boolean needbreak = (Thread.currentThread().isInterrupted() || (this.taskThrottle == 0) || !this.resumeTask); if (needbreak) { long wstamp2 = this.lock.tryConvertToWriteLock(stamp2); if (wstamp2 == 0L) { this.lock.unlockRead(stamp2); wstamp2 = this.lock.writeLock(); } stamp2 = wstamp2; needbreak = (Thread.currentThread().isInterrupted() || (this.taskThrottle == 0) || !this.resumeTask); if (needbreak) { this.taskThrottle = 0; this.resumeTask = false; this.run = null; break; } } } finally { this.lock.unlock(stamp2); } } } catch (InterruptedException x) { break; } catch (Exception x) { Logger.warn("Work pool caught error: " + x); } catch (ThreadDeath x) { Logger.warn("Work pool thread died: " + x); throw x; } long wstamp = this.lock.writeLock(); try { this.taskThrottle = 0; this.resumeTask = false; this.run = null; } finally { this.lock.unlock(wstamp); } } // for interrupted flow this.run = null; Logger.trace("Work pool thread stopped: " + this.slot); } protected void dumpDebug() { try { Logger.warn(" Thread Id: " + this.thread.getId()); Logger.warn(" Thread Name: " + this.thread.getName()); Logger.warn(" Thread State: " + this.thread.getState()); Logger.warn(" Thread Stack:"); StackTraceElement[] stack = this.thread.getStackTrace(); for (StackTraceElement e : stack) Logger.warn(" - " + e.toString()); } catch (Exception x) { } } public void checkIfHung() { Thread th = this.thread; // if not alive then toss this worker out // replace/remove my thread if ((th == null) || !th.isAlive()) Hub.instance.getWorkPool().initSlot(this.slot); TaskRun tr = this.run; if ((tr != null) && (tr.isHung())) { Logger.warn("Work pool thread hung: " + this.slot); // TODO remove System.out.println("Overdue: " + tr.isOverdue()); System.out.println("Overdue Time: " + tr.getTask().getDeadlineMS()); System.out.println("Inactive: " + tr.isInactive()); System.out.println("Inactive Time: " + tr.getTask().getTimeoutMS()); tr.kill(); this.dumpDebug(); Hub.instance.getWorkPool().incThreadsHung(); this.stop(); } } public void stop() { this.stopping = true; Thread th = this.thread; if ((th == null) || !th.isAlive()) return; try { th.interrupt(); } catch (Exception x) { } // replace/remove my thread Hub.instance.getWorkPool().initSlot(this.slot); } // public void stopNice() { this.stopping = true; Thread th = this.thread; if ((th == null) || !th.isAlive()) return; // only stop if not busy, if run is null then no work is being done if (this.run != null) return; try { th.interrupt(); } catch (Exception x) { } // replace/remove my thread Hub.instance.getWorkPool().initSlot(this.slot); } }