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