/src/kilim/Task.java

http://github.com/kilim/kilim · Java · 706 lines · 419 code · 78 blank · 209 comment · 70 complexity · a6cd0d1a6c147933504253c6c89af727 MD5 · raw file

  1. /* Copyright (c) 2006, Sriram Srinivasan
  2. *
  3. * You may distribute this software under the terms of the license
  4. * specified in the file "License"
  5. */
  6. package kilim;
  7. import java.lang.reflect.InvocationTargetException;
  8. import java.lang.reflect.Method;
  9. import java.util.LinkedList;
  10. import java.util.concurrent.atomic.AtomicBoolean;
  11. import java.util.concurrent.atomic.AtomicInteger;
  12. /**
  13. * A base class for tasks. A task is a lightweight thread (it contains its own
  14. * stack in the form of a fiber). A concrete subclass of Task must provide a
  15. * pausable execute method.
  16. *
  17. */
  18. public abstract class Task<TT> implements Runnable, EventSubscriber, Fiber.Worker {
  19. static PauseReason yieldReason = new YieldReason();
  20. /**
  21. * Task id, automatically generated
  22. */
  23. public final int id;
  24. static final AtomicInteger idSource = new AtomicInteger();
  25. /**
  26. * The stack manager in charge of rewinding and unwinding the stack when
  27. * Task.pause() is called.
  28. */
  29. protected Fiber fiber;
  30. /**
  31. * The reason for pausing (duh) and performs the role of a await condition
  32. * in CCS. This object is responsible for resuming the task.
  33. *
  34. * @see kilim.PauseReason
  35. */
  36. protected PauseReason pauseReason;
  37. /**
  38. * running = true when it is put on the schdulers run Q (by Task.resume()).
  39. * The Task.runExecute() method is called at some point; 'running' remains
  40. * true until the end of runExecute (where it is reset), at which point a
  41. * fresh decision is made whether the task needs to continue running.
  42. */
  43. protected AtomicBoolean running = new AtomicBoolean(false);
  44. protected volatile boolean done = false;
  45. /**
  46. * The thread in which to resume this task. Ideally, we shouldn't have any
  47. * preferences, but using locks in pausable methods will require the task to
  48. * be pinned to a thread.
  49. *
  50. * @see kilim.ReentrantLock
  51. */
  52. volatile int preferredResumeThread = -1;
  53. private int tid;
  54. /**
  55. * @see Task#preferredResumeThread
  56. */
  57. int numActivePins;
  58. /**
  59. * @see #informOnExit(Mailbox)
  60. */
  61. private LinkedList<Mailbox<ExitMsg<TT>>> exitMBs;
  62. /**
  63. * The object responsible for handing this task to a thread when the task is
  64. * runnable.
  65. */
  66. protected Scheduler scheduler;
  67. public volatile Object exitResult = "OK";
  68. // new timer service
  69. public kilim.timerservice.Timer timer;
  70. // for debugging Task.resume race conditions
  71. private static boolean debugRunning = false;
  72. public Task() {
  73. id = idSource.incrementAndGet();
  74. fiber = new Fiber(this);
  75. timer = new kilim.timerservice.Timer(this);
  76. }
  77. Task(boolean dummy) { id = idSource.incrementAndGet(); }
  78. public int id() {
  79. return id;
  80. }
  81. public synchronized Task<TT> setScheduler(Scheduler s) {
  82. // if (running) {
  83. // throw new
  84. // AssertionError("Attempt to change task's scheduler while it is running");
  85. // }
  86. scheduler = s;
  87. return this;
  88. }
  89. public synchronized Scheduler getScheduler() {
  90. return scheduler;
  91. }
  92. public void resumeOnScheduler(Scheduler s) throws Pausable {
  93. if (scheduler == s)
  94. return;
  95. scheduler = s;
  96. Task.yield();
  97. }
  98. /**
  99. * Used to start the task; the task doesn't resume on its own. Custom
  100. * schedulers must be set (@see #setScheduler(Scheduler)) before start() is
  101. * called.
  102. *
  103. * @return
  104. */
  105. public Task<TT> start() {
  106. if (scheduler == null) {
  107. setScheduler(Scheduler.getDefaultScheduler());
  108. }
  109. resume();
  110. return this;
  111. }
  112. private static Fiber.MethodRef runnerInfo = new Fiber.MethodRef("kilim.Task","run");
  113. Fiber.MethodRef getRunnerInfo() {
  114. return runnerInfo;
  115. }
  116. /*
  117. * fix https://github.com/kilim/kilim/issues/40
  118. * ie, merge https://github.com/hyleeon/kilim/tree/fix-invoke
  119. * specifically https://github.com/hyleeon/kilim/commit/3e14940a59c1df1e07a6a56f060b012866f20b57
  120. * which is also https://github.com/kilim/kilim/pull/42
  121. *
  122. * When we called a Pausable-Method
  123. * such as ExCatch.pausableInvokeCatch() -> ExCatch.pausableInvokeCatch0()) by Task.invoke()
  124. * The stack will like:
  125. * at kilim.test.ex.ExCatch.pausableInvokeCatch0(ExCatch.java:178)
  126. * at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  127. * at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
  128. * at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  129. * at java.lang.reflect.Method.invoke(Method.java:606)at kilim.Task.invoke(Task.java:285)
  130. * at kilim.test.ex.ExCatch.pausableInvokeCatch(ExCatch.java:166)
  131. * at kilim.test.ex.ExCatch.test(ExCatch.java:36)
  132. * at kilim.test.ex.ExCatch.execute(ExCatch.java:26)
  133. * at kilim.Task._runExecute(Task.java:442)
  134. * at kilim.WorkerThread.run(WorkerThread.java:32)
  135. * If method pausableInvokeCatch0 try-catch a exception, we will call Fiber.upEx() to re-calculate
  136. * the stack size by this method. But we should discount "sun.reflect.*" and "java.lang.reflect.Method.invoke"
  137. */
  138. private static boolean skipInvoke(String klass,String name) {
  139. return klass.startsWith("sun.reflect.")
  140. | klass.startsWith("jdk.internal.reflect.")
  141. | ("java.lang.reflect.Method".equals(klass) & "invoke".equals(name));
  142. }
  143. /**
  144. * The generated code calls Fiber.upEx, which in turn calls this to find out
  145. * out where the current method is w.r.t the closest _runExecute method.
  146. *
  147. * @return the number of stack frames above _runExecute(), not including
  148. * this method
  149. */
  150. public static int getStackDepth(Task task) {
  151. Fiber.MethodRef mr = task.getRunnerInfo();
  152. StackTraceElement[] stes;
  153. stes = new Exception().getStackTrace();
  154. int len = stes.length;
  155. int num = 0;
  156. for (int i = 0; i < len; i++) {
  157. StackTraceElement ste = stes[i];
  158. String name = ste.getMethodName();
  159. String klass = ste.getClassName();
  160. // ignore synthetic shim methods from SAM weaver - they don't get stack state allocated
  161. // fixme: should any other synthetic methods be skipped ?
  162. // fixme: could other vendors be using the same name for synthetic methods that shouldn't be skipped ?
  163. if (ste.getLineNumber() < 0 & Constants.Util.isSamShim(name))
  164. continue;
  165. if (skipInvoke(klass,name))
  166. continue;
  167. num++;
  168. if (name.equals(mr.methodname) & klass.equals(mr.classname)) {
  169. // discounting WorkerThread.run, Task._runExecute, and
  170. // Scheduler.getStackDepth
  171. // and convert count to index
  172. return num-2;
  173. }
  174. }
  175. throw new AssertionError("Expected task to be run by WorkerThread");
  176. }
  177. boolean checkTimeout() {
  178. return timer.getExecutionTime()==-2;
  179. }
  180. public void onEvent(EventPublisher ep, Event e) {
  181. if (e==kilim.timerservice.Timer.timedOut)
  182. timer.setLiteral(-2);
  183. boolean sched = resume();
  184. }
  185. public Thread getExecutionThread() {
  186. return Thread.currentThread();
  187. }
  188. /**
  189. * Add itself to scheduler if it is neither already running nor done.
  190. *
  191. * @return True if it scheduled itself.
  192. */
  193. public boolean resume() {
  194. if (scheduler == null)
  195. return false;
  196. boolean doSchedule = false;
  197. // We don't check pauseReason while resuming (to verify whether
  198. // it is worth returning to a pause state. The code at the top of stack
  199. // will be doing that anyway.
  200. if (!done)
  201. if (running.compareAndSet(/* expected */false, /* update */true))
  202. doSchedule = true;
  203. else
  204. if (debugRunning) System.out.println("Task.pause.running: " + this);
  205. if (doSchedule) {
  206. if (preferredResumeThread == -1)
  207. scheduler.schedule(this);
  208. else
  209. scheduler.schedule(preferredResumeThread, this);
  210. }
  211. return doSchedule;
  212. }
  213. public synchronized void informOnExit(Mailbox<ExitMsg<TT>> exit) {
  214. if (done) {
  215. exit.putnb(new ExitMsg(this, exitResult));
  216. return;
  217. }
  218. if (exitMBs == null) {
  219. exitMBs = new LinkedList();
  220. }
  221. exitMBs.add(exit);
  222. }
  223. /**
  224. * This is a placeholder that doesn't do anything useful. Weave replaces the
  225. * call in the bytecode from invokestateic Task.getCurrentTask to load fiber
  226. * getfield task
  227. */
  228. public static Task getCurrentTask() throws Pausable {
  229. return null;
  230. }
  231. /**
  232. * Analogous to System.exit, except an Object can be used as the exit value
  233. */
  234. public static void exit(Object aExitValue) throws Pausable {
  235. }
  236. public static void exit(Object aExitValue, Fiber f) {
  237. assert f.pc == 0 : "f.pc != 0";
  238. f.task.setPauseReason(new TaskDoneReason(aExitValue));
  239. f.togglePause();
  240. }
  241. /**
  242. * Exit the task with a throwable indicating an error condition. The value
  243. * is conveyed through the exit mailslot (see informOnExit). All exceptions
  244. * trapped by the task scheduler also set the error result.
  245. */
  246. public static void errorExit(Throwable ex) throws Pausable {
  247. }
  248. public static void errorExit(Throwable ex, Fiber f) {
  249. assert f.pc == 0 : "fc.pc != 0";
  250. f.task.setPauseReason(new TaskDoneReason(ex));
  251. f.togglePause();
  252. }
  253. public static void errNotWoven() {
  254. System.err.println("############################################################");
  255. System.err.println("Task has either not been woven or the classpath is incorrect");
  256. System.err.println("############################################################");
  257. Thread.dumpStack();
  258. System.exit(0);
  259. }
  260. public static void errNotWoven(Task t) {
  261. System.err.println("############################################################");
  262. System.err.println("Task " + t.getClass()
  263. + " has either not been woven or the classpath is incorrect");
  264. System.err.println("############################################################");
  265. Thread.dumpStack();
  266. System.exit(0);
  267. }
  268. static class ArgState extends kilim.State {
  269. Object mthd;
  270. Object obj;
  271. Object[] fargs;
  272. }
  273. /**
  274. * Invoke a pausable method via reflection. Equivalent to Method.invoke().
  275. *
  276. * @param method
  277. * : The method to be invoked. (Implementation note: the corresponding woven
  278. * method is invoked instead).
  279. * @param obj
  280. * : The object on which the method is invoked. Can be null if the method is
  281. * static.
  282. * @param args
  283. * : Arguments to the method
  284. * @return
  285. * @throws Pausable
  286. * @throws IllegalAccessException
  287. * @throws IllegalArgumentException
  288. * @throws InvocationTargetException
  289. */
  290. public static Object invoke(Method method, Object obj, Object... args)
  291. throws Pausable,
  292. IllegalAccessException,
  293. IllegalArgumentException,
  294. InvocationTargetException {
  295. Fiber f = getCurrentTask().fiber;
  296. Object[] fargs;
  297. if (f.pc == 0) {
  298. method = getWovenMethod(method);
  299. // Normal invocation.
  300. if (args == null) {
  301. fargs = new Object[1];
  302. } else {
  303. fargs = new Object[args.length + 1]; // for fiber
  304. System.arraycopy(args, 0, fargs, 0, args.length);
  305. }
  306. fargs[fargs.length - 1] = f;
  307. } else {
  308. // Resuming from a previous yield
  309. ArgState as = (ArgState) f.getState();
  310. method = (Method) as.mthd;
  311. obj = as.obj;
  312. fargs = as.fargs;
  313. }
  314. f.down();
  315. Object ret = method.invoke(obj, fargs);
  316. switch (f.up()) {
  317. case Fiber.NOT_PAUSING__NO_STATE:
  318. case Fiber.NOT_PAUSING__HAS_STATE:
  319. return ret;
  320. case Fiber.PAUSING__NO_STATE:
  321. ArgState as = new ArgState();
  322. as.obj = obj;
  323. as.fargs = fargs;
  324. as.pc = 1;
  325. as.mthd = method;
  326. f.setState(as);
  327. return null;
  328. case Fiber.PAUSING__HAS_STATE:
  329. return null;
  330. }
  331. throw new IllegalAccessException("Internal Error");
  332. }
  333. // Given a method corresp. to "f(int)", return the equivalent woven method
  334. // for "f(int, kilim.Fiber)"
  335. private static Method getWovenMethod(Method m) {
  336. Class<?>[] ptypes = m.getParameterTypes();
  337. if (!(ptypes.length > 0 && ptypes[ptypes.length - 1].getName().equals("kilim.Fiber"))) {
  338. // The last param is not "Fiber", so m is not woven.
  339. // Get the woven method corresponding to m(..., Fiber)
  340. boolean found = false;
  341. LOOP: for (Method wm : m.getDeclaringClass().getDeclaredMethods()) {
  342. if (wm != m && wm.getName().equals(m.getName())) {
  343. // names match. Check if the wm has the exact parameter
  344. // types as m, plus a fiber.
  345. Class<?>[] wptypes = wm.getParameterTypes();
  346. if (wptypes.length != ptypes.length + 1
  347. || !(wptypes[wptypes.length - 1].getName().equals("kilim.Fiber")))
  348. continue LOOP;
  349. for (int i = 0; i < ptypes.length; i++) {
  350. if (ptypes[i] != wptypes[i])
  351. continue LOOP;
  352. }
  353. m = wm;
  354. found = true;
  355. break;
  356. }
  357. }
  358. if (!found) {
  359. throw new IllegalArgumentException("Found no pausable method corresponding to supplied method: "
  360. + m);
  361. }
  362. }
  363. return m;
  364. }
  365. /**
  366. * @param millis
  367. * to sleep. Like thread.sleep, except it doesn't throw an interrupt, and it
  368. * doesn't hog the java thread.
  369. */
  370. public static void sleep(final long millis) throws Pausable {
  371. // create a temp mailbox, and wait on it.
  372. final Mailbox<Integer> sleepmb = new Mailbox<Integer>(1); // TODO: will
  373. // need a
  374. // better
  375. // mechanism
  376. // for
  377. // monitoring
  378. // later on.
  379. sleepmb.get(millis);
  380. }
  381. public static void shutdown() {
  382. }
  383. /**
  384. * Yield cooperatively to the next task waiting to use the thread.
  385. */
  386. public static void yield() throws Pausable {
  387. errNotWoven();
  388. }
  389. public static void yield(Fiber f) {
  390. if (f.task instanceof Continuation.FakeTask) { f.togglePause(); return; }
  391. if (f.pc == 0) {
  392. f.task.setPauseReason(yieldReason);
  393. } else {
  394. f.task.setPauseReason(null);
  395. }
  396. f.togglePause();
  397. f.task.checkKill();
  398. }
  399. /**
  400. * Ask the current task to pause with a reason object, that is responsible
  401. * for resuming the task when the reason (for pausing) is not valid any
  402. * more.
  403. *
  404. * @param pauseReason
  405. * the reason
  406. */
  407. public static void pause(PauseReason pauseReason) throws Pausable {
  408. errNotWoven();
  409. }
  410. public static void pause(PauseReason pauseReason, Fiber f) {
  411. if (f.pc == 0) {
  412. f.task.setPauseReason(pauseReason);
  413. } else {
  414. f.task.setPauseReason(null);
  415. }
  416. f.togglePause();
  417. f.task.checkKill();
  418. }
  419. /*
  420. * This is the fiber counterpart to the execute() method that allows us to
  421. * detec when a subclass has not been woven.
  422. *
  423. * If the subclass has not been woven, it won't have an execute method of
  424. * the following form, and this method will be called instead.
  425. */
  426. public void execute() throws Pausable, Exception {
  427. errNotWoven(this);
  428. }
  429. public void execute(Fiber f) throws Exception {
  430. errNotWoven(this);
  431. }
  432. public String toString() {
  433. return "" + id + "(running=" + running + ",pr=" + pauseReason + ")";
  434. }
  435. public String dump() {
  436. synchronized (this) {
  437. return "" + id + "(running=" + running + ", pr=" + pauseReason
  438. + ")";
  439. }
  440. }
  441. public void prePin() throws Pausable {
  442. if (scheduler.isPinnable()) return;
  443. scheduler = scheduler.getPinnable();
  444. yield();
  445. }
  446. void checkPin() {
  447. if (! scheduler.isPinnable())
  448. throw new AssertionError("attempt to pin and unpinnable scheduler - must call `prePin()` first");
  449. }
  450. public void pinToThread() {
  451. checkPin();
  452. numActivePins++;
  453. }
  454. public void unpinFromThread() {
  455. numActivePins--;
  456. }
  457. final protected void setPauseReason(PauseReason pr) {
  458. pauseReason = pr;
  459. }
  460. public final PauseReason getPauseReason() {
  461. return pauseReason;
  462. }
  463. public boolean isDone() {
  464. return done;
  465. }
  466. protected void setTid(int tid) {
  467. this.tid = tid;
  468. }
  469. /** return the thread ID that the task is currently running on, valid only during execute */
  470. public int getTid() {
  471. return tid;
  472. }
  473. /**
  474. * Called by WorkerThread, it is the wrapper that performs pre and post
  475. * execute processing (in addition to calling the execute(fiber) method of
  476. * the task.
  477. */
  478. public void run() throws NotPausable {
  479. Scheduler.setCurrentTask(this);
  480. Fiber f = fiber;
  481. boolean isDone = false;
  482. try {
  483. assert (preferredResumeThread == -1 || preferredResumeThread == tid) : "Resumed "
  484. + id + " in incorrect thread. ";
  485. // start execute. fiber is wound to the beginning.
  486. execute(f.begin());
  487. // execute() done. Check fiber if it is pausing and reset it.
  488. isDone = f.end() || (pauseReason instanceof TaskDoneReason);
  489. } catch (Throwable th) {
  490. getScheduler().log(this,th);
  491. // Definitely done
  492. setPauseReason(new TaskDoneReason(th));
  493. isDone = true;
  494. }
  495. if (isDone) {
  496. // inform on exit
  497. if (numActivePins > 0) {
  498. throw new AssertionError("Task ended but has active locks");
  499. }
  500. if (pauseReason instanceof TaskDoneReason) {
  501. exitResult = ((TaskDoneReason)pauseReason).exitObj;
  502. }
  503. preferredResumeThread = -1;
  504. synchronized(this){
  505. done = true;
  506. if (exitMBs != null) {
  507. ExitMsg msg = new ExitMsg(this, exitResult);
  508. for (Mailbox<ExitMsg<TT>> exitMB: exitMBs) {
  509. exitMB.putnb(msg);
  510. }
  511. }
  512. }
  513. } else {
  514. if (tid >= 0) { // it is null for generators
  515. if (numActivePins > 0) {
  516. preferredResumeThread = tid;
  517. } else {
  518. assert numActivePins == 0 : "numActivePins == "
  519. + numActivePins;
  520. preferredResumeThread = -1;
  521. }
  522. }
  523. PauseReason pr = this.pauseReason;
  524. running.set(false);
  525. // The task has been in "running" mode until now, and may have
  526. // missed
  527. // notifications to the pauseReason object (that is, it would have
  528. // resisted calls to resume(). If the pauseReason is not valid any
  529. // more, we'll resume.
  530. if (!pr.isValid(this)) {
  531. // NOTE: At this point, another event could trigger resumption
  532. // before the following resume() can kick in. Additionally,
  533. // it is possible that the task could process all pending
  534. // events, so the following call to resume() may be spurious.
  535. // Cell/Mailbox's get/put watch out for spurious resumptions.
  536. resume();
  537. }
  538. }
  539. }
  540. public ExitMsg<TT> joinb() {
  541. Mailbox<ExitMsg<TT>> mb = new Mailbox();
  542. informOnExit(mb);
  543. return mb.getb();
  544. }
  545. public ExitMsg<TT> join() throws Pausable {
  546. Mailbox<ExitMsg<TT>> mb = new Mailbox();
  547. informOnExit(mb);
  548. return mb.get();
  549. }
  550. @Override
  551. public boolean equals(Object obj) {
  552. return obj == this;
  553. }
  554. @Override
  555. public int hashCode() {
  556. return id;
  557. }
  558. public void checkKill() {
  559. }
  560. public boolean getState() {
  561. return running.get();
  562. }
  563. public static class Spawn<TT> extends Task<TT> {
  564. Pausable.Spawn<TT> body;
  565. public Spawn() {}
  566. public Spawn(Pausable.Spawn<TT> body) { this.body = body; }
  567. public void execute() throws Pausable, Exception {
  568. TT val = body.execute();
  569. exit(val);
  570. }
  571. }
  572. public static class Fork extends Task {
  573. Pausable.Fork body;
  574. public Fork(Pausable.Fork body) { this.body = body; }
  575. public void execute() throws Pausable, Exception {
  576. body.execute();
  577. }
  578. }
  579. public static class Invoke<TT> extends Task<TT> {
  580. Method method;
  581. Object obj;
  582. Object [] args;
  583. public Invoke(Method method,Object obj,Object...args) {
  584. this.method = method;
  585. this.obj = obj;
  586. this.args = args;
  587. }
  588. public void execute() throws Pausable, Exception {
  589. Object val = Task.invoke(method,obj,args);
  590. exit(val);
  591. }
  592. }
  593. /**
  594. * Wraps the given object or lambda expression in a Task and starts that task.
  595. * Beware of inadvertent sharing when multiple lambdas are created in the same context
  596. *
  597. * @param body the lambda to delegate to
  598. * @return the spawned task.
  599. */
  600. public static Task fork(final Pausable.Fork body) {
  601. return new Fork(body).start();
  602. }
  603. /**
  604. * Wraps the given object or lambda expression in a Task and starts that task.
  605. * Beware of inadvertent sharing when multiple lambdas are created in the same context
  606. *
  607. * @param body the lambda to delegate to
  608. * @return the spawned task.
  609. */
  610. public static <TT> Spawn<TT> spawn(final Pausable.Spawn<TT> body) {
  611. Spawn<TT> spawn = new Spawn(body);
  612. spawn.start();
  613. return spawn;
  614. }
  615. public static Invoke spawn(Method method,Object obj,Object... args) {
  616. Invoke spawn = new Invoke(method,obj,args);
  617. spawn.start();
  618. return spawn;
  619. }
  620. /**
  621. * idledown the default scheduler
  622. * @see Scheduler#idledown()
  623. */
  624. public static void idledown() {
  625. if (Scheduler.defaultScheduler != null)
  626. Scheduler.defaultScheduler.idledown();
  627. }
  628. }