root / examples / pretty.txt

1 cd7bfedb Steven
 {notes/an early draft}
2 cd7bfedb Steven
3 f9d99756 Steven
 Exception flow, and pretty ones at that
4 f9d99756 Steven
5 cd7bfedb Steven
  It's funny how important I find pretty exceptions, which means
6 cd7bfedb Steven
7 cd7bfedb Steven
 1) exceptions that can trace back through actor calls
8 cd7bfedb Steven
 2) that remove all the actor runtime dreck
9 cd7bfedb Steven
10 cd7bfedb Steven
 dramatis does (1) and kinda does (2), with the limitation that it only knows the backtrace to the actor call where it is caught (if it is). Oh, I guess I should add
11 cd7bfedb Steven
12 cd7bfedb Steven
 (0) exceptions flow back along synchronous actor calls
13 cd7bfedb Steven
14 cd7bfedb Steven
 since that's kinda of a given.
15 cd7bfedb Steven
16 f9d99756 Steven
 One of the things that makes concurrent programming difficult is the difficulty in figuring out what is going wrong when things go wrong, whcih they inevitably do.
17 f9d99756 Steven
18 f9d99756 Steven
 For example, take the simple little dramatis actor program:
19 f9d99756 Steven
20 f9d99756 Steven
 require 'dramatis/actor'
21 f9d99756 Steven
22 f9d99756 Steven
 class Foo
23 f9d99756 Steven
   include Dramatis::Actor
24 f9d99756 Steven
   def foo that
25 f9d99756 Steven
     return that.bar :fooobar
26 f9d99756 Steven
   end
27 f9d99756 Steven
 end
28 f9d99756 Steven
29 f9d99756 Steven
 class Bar
30 f9d99756 Steven
   include Dramatis::Actor
31 f9d99756 Steven
   def bar method
32 f9d99756 Steven
     send method
33 f9d99756 Steven
   end
34 f9d99756 Steven
   def foobar
35 f9d99756 Steven
     "foobar"
36 f9d99756 Steven
   end
37 f9d99756 Steven
 end
38 f9d99756 Steven
39 f9d99756 Steven
 Foo.new.foo Bar.new
40 f9d99756 Steven
41 f9d99756 Steven
 Since there's a typo in the program, :fooobar isn't defined for bar, and a NameError exception is thrown.
42 f9d99756 Steven
43 f9d99756 Steven
 Now, if this was a normal, serial program, you'd get something like:
44 f9d99756 Steven
45 f9d99756 Steven
 ./exception.rb:17:in `send': undefined method `fooobar' for #<Bar:0x2b928905d498> (NoMethodError)
46 f9d99756 Steven
         from ./exception.rb:17:in `bar'
47 f9d99756 Steven
         from ./exception.rb:10:in `foo'
48 f9d99756 Steven
         from ./exception.rb:24
49 f9d99756 Steven
50 f9d99756 Steven
 The problem isn't with bar, exactly, it's that foo passed it a bad value, but that's easy to see from the stack backtrace.
51 f9d99756 Steven
52 f9d99756 Steven
 In many concurrent programing systems, this gets broken all over the place, as soon as the caller and the callee run in different stacks. In a threaded program, this happens if the caller and callee execute on different threads (which you'd have to arrange by some how passing the value through shared memory yadda yadda yadda.
53 f9d99756 Steven
54 f9d99756 Steven
 It's actually easy to actually do the call in an actor system, like dramatis or erlang. You make the call/send a message and the runtime manages the scheduling (whcih you'd have to do yourself in a thread model.
55 f9d99756 Steven
56 f9d99756 Steven
 Only, thing kinda go south at that point. An exception is still raised in bar, but where does it go? And even if it doesn't go anywhere, what does it look like.
57 f9d99756 Steven
58 f9d99756 Steven
 The answer to the first question is usullay, "nowhere useful" and to the second, ugh:
59 f9d99756 Steven
60 f9d99756 Steven
 ./exception.rb:28:in `bar': undefined local variable or method `fooobar' for #<Bar:0x2ad75510df90> (NameError)
61 f9d99756 Steven
         from ./../lib/dramatis/runtime/actor.rb:146:in `send'
62 f9d99756 Steven
         from ./../lib/dramatis/runtime/actor.rb:146:in `deliver'
63 f9d99756 Steven
         from ./../lib/dramatis/runtime/task.rb:81:in `deliver'
64 f9d99756 Steven
         from ./../lib/dramatis/runtime/scheduler.rb:344:in `deliver'
65 f9d99756 Steven
         from ./../lib/dramatis/runtime/scheduler.rb:257:in `run'
66 f9d99756 Steven
         from ./../lib/dramatis/runtime/thread_pool.rb:136:in `call'
67 f9d99756 Steven
         from ./../lib/dramatis/runtime/thread_pool.rb:136:in `target'
68 f9d99756 Steven
         from ./../lib/dramatis/runtime/thread_pool.rb:127:in `synchronize'
69 f9d99756 Steven
          ... 19 levels...
70 f9d99756 Steven
         from ./../lib/dramatis/runtime/actor.rb:116:in `common_send'
71 f9d99756 Steven
         from ./../lib/dramatis/runtime/actor.rb:93:in `actor_send'
72 f9d99756 Steven
         from ./../lib/dramatis/actor.rb:35:in `new'
73 f9d99756 Steven
         from ./exception.rb:35
74 f9d99756 Steven
75 f9d99756 Steven
 We see where the exception occured, but there's nothing else in that long backtrace that is helps us figure out what's going. Even the top of the stackframe isn't helping us because it's not even the beginnging of the call that caused the problem: it's the first actor call. This is because the stracktrace is giving the thread context of the thread running the bar method, which is allocated from a pool of threads (as you see).
76 f9d99756 Steven
77 f9d99756 Steven
 In this little example, it's not hard to figure out the control flow that cuased the problem, but in even the simplest real concurrent programs, this rapidly becomes infeasible.
78 f9d99756 Steven
79 f9d99756 Steven
 Note that in this case, it's particularly annoying because this isn't a particularly concurrent program: all the actor calls are blocking calls, so why can't we just have normal exception semantics?
80 f9d99756 Steven
81 f9d99756 Steven
 Well, actually, we do. If we change our call:
82 f9d99756 Steven
83 f9d99756 Steven
 Foo.new.foo Bar.new
84 f9d99756 Steven
85 f9d99756 Steven
 to
86 f9d99756 Steven
87 f9d99756 Steven
 begin
88 f9d99756 Steven
   Foo.new.foo Bar.new
89 f9d99756 Steven
 rescue NameError => ne
90 f9d99756 Steven
   puts "hey, I got a #{ne}"
91 f9d99756 Steven
   puts "it happened here: " + ne.backtrace.join("\n")
92 f9d99756 Steven
 end
93 f9d99756 Steven
94 f9d99756 Steven
 we see that we actually can trap the error, even though it occured in the context of another actor, on another thread. For "normal" blocking calls, exceptions are routed just like they would be in a non-concurrent world. Other types of continuations have different semantics. In the somehwat pathalogical case, where there is no continuation, the exception is delivered to the running actor or flagged and ignored.
95 f9d99756 Steven
96 f9d99756 Steven
 However, what about the stacktrace? Fortunately, dramatis filters the backtraces: it removes its own overhead and it stiches together the pieces along the actro call chain. What do we actually see?
97 f9d99756 Steven
98 f9d99756 Steven
99 f9d99756 Steven
 hey, I got a undefined local variable or method `fooobar' for #<Bar:0x2ae257f29180>
100 f9d99756 Steven
 it happened here: ./exception.rb:27:in `bar'
101 f9d99756 Steven
 ./exception.rb:18:in `foo'
102 f9d99756 Steven
 ./exception.rb:36
103 f9d99756 Steven
104 f9d99756 Steven
 which is identical to the serial case.
105 f9d99756 Steven
106 f9d99756 Steven
 It's not perfect: currently, dramatis only knows the call chain if you let the exception propagate back along it. But that's fixable (prfobably).
107 f9d99756 Steven
108 f9d99756 Steven
 Progress, not perfection.