PageRenderTime 42ms CodeModel.GetById 11ms app.highlight 25ms RepoModel.GetById 1ms app.codeStats 0ms

/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
  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}