dramatis 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. data/History.txt +7 -0
  2. data/License.txt +20 -0
  3. data/Manifest.txt +119 -0
  4. data/README.txt +57 -0
  5. data/Rakefile +4 -0
  6. data/config/hoe.rb +70 -0
  7. data/config/requirements.rb +17 -0
  8. data/examples/README.txt +20 -0
  9. data/examples/auction.rb +90 -0
  10. data/examples/bank/bank.rb +7 -0
  11. data/examples/bank/bank_test.rb +7 -0
  12. data/examples/exception.rb +40 -0
  13. data/examples/fib/conservative.rb +50 -0
  14. data/examples/fib/future.rb +5 -0
  15. data/examples/fib/original.rb +33 -0
  16. data/examples/fib/threads.rb +51 -0
  17. data/examples/im/distributed/chat/client.rb +49 -0
  18. data/examples/im/distributed/chat/screen/fox.rb +92 -0
  19. data/examples/im/distributed/chat/screen.rb +11 -0
  20. data/examples/im/distributed/chat/server.rb +72 -0
  21. data/examples/im/distributed/chat.rb +5 -0
  22. data/examples/im/distributed/client.rb +9 -0
  23. data/examples/im/distributed/run.rb +18 -0
  24. data/examples/im/distributed/server.rb +11 -0
  25. data/examples/im/single/chat/client.rb +50 -0
  26. data/examples/im/single/chat/screen/fox.rb +96 -0
  27. data/examples/im/single/chat/screen/wxs.rb +63 -0
  28. data/examples/im/single/chat/screen.rb +11 -0
  29. data/examples/im/single/chat/server.rb +72 -0
  30. data/examples/im/single/chat.rb +5 -0
  31. data/examples/im/single/fox.rb +18 -0
  32. data/examples/im/single/wxchat.rb +19 -0
  33. data/examples/pingpong/actor.rb +33 -0
  34. data/examples/pingpong/actor_rec.rb +34 -0
  35. data/examples/pingpong/pingpong.txt +315 -0
  36. data/examples/pingpong/scala.rb +41 -0
  37. data/examples/pingpong/serial.rb +26 -0
  38. data/examples/pretty.txt +108 -0
  39. data/examples/telephone/.irbrc +2 -0
  40. data/examples/telephone/3esl.txt +21877 -0
  41. data/examples/telephone/fifth/kid.rb +36 -0
  42. data/examples/telephone/fifth/run.rb +26 -0
  43. data/examples/telephone/first/kid.rb +31 -0
  44. data/examples/telephone/first/run.rb +20 -0
  45. data/examples/telephone/fourth/kid.rb +31 -0
  46. data/examples/telephone/fourth/run.rb +26 -0
  47. data/examples/telephone/mangler.rb +53 -0
  48. data/examples/telephone/second/kid.rb +26 -0
  49. data/examples/telephone/second/run.rb +20 -0
  50. data/examples/telephone/seventh/kid.rb +40 -0
  51. data/examples/telephone/seventh/run.rb +35 -0
  52. data/examples/telephone/seventh/test.rb +28 -0
  53. data/examples/telephone/seventh/test2.rb +10 -0
  54. data/examples/telephone/sixth/kid.rb +39 -0
  55. data/examples/telephone/sixth/run.rb +26 -0
  56. data/examples/telephone/third/kid.rb +31 -0
  57. data/examples/telephone/third/run.rb +21 -0
  58. data/lib/dramatis/actor/interface.rb +118 -0
  59. data/lib/dramatis/actor/name/interface.rb +128 -0
  60. data/lib/dramatis/actor/name.rb +44 -0
  61. data/lib/dramatis/actor.rb +96 -0
  62. data/lib/dramatis/deadlock.rb +123 -0
  63. data/lib/dramatis/error/uncaught.rb +19 -0
  64. data/lib/dramatis/error.rb +125 -0
  65. data/lib/dramatis/future/interface.rb +45 -0
  66. data/lib/dramatis/future.rb +32 -0
  67. data/lib/dramatis/runtime/actor/main.rb +3 -0
  68. data/lib/dramatis/runtime/actor.rb +294 -0
  69. data/lib/dramatis/runtime/gate.rb +244 -0
  70. data/lib/dramatis/runtime/scheduler.rb +374 -0
  71. data/lib/dramatis/runtime/task.rb +390 -0
  72. data/lib/dramatis/runtime/thread_pool.rb +149 -0
  73. data/lib/dramatis/runtime/timer.rb +5 -0
  74. data/lib/dramatis/runtime.rb +129 -0
  75. data/lib/dramatis/shoes/runtime.rb +7 -0
  76. data/lib/dramatis/shoes.rb +14 -0
  77. data/lib/dramatis/version.rb +8 -0
  78. data/lib/dramatis.rb +73 -0
  79. data/log/debug.log +0 -0
  80. data/script/destroy +14 -0
  81. data/script/generate +14 -0
  82. data/script/txt2html +74 -0
  83. data/setup.rb +1585 -0
  84. data/spec/dramatis/actor/become_spec.rb +17 -0
  85. data/spec/dramatis/actor/future_spec.rb +189 -0
  86. data/spec/dramatis/actor/name_spec.rb +141 -0
  87. data/spec/dramatis/actor/task_spec.rb +75 -0
  88. data/spec/dramatis/actor_spec.rb +492 -0
  89. data/spec/dramatis/dramatis_spec.rb +23 -0
  90. data/spec/dramatis/exc_spec.rb +78 -0
  91. data/spec/dramatis/runtime/gate_spec.rb +57 -0
  92. data/spec/dramatis/runtime/thread_pool.rb +30 -0
  93. data/spec/dramatis/shoes_spec.rb +11 -0
  94. data/spec/dramatis/simple_spec.rb +32 -0
  95. data/spec/exp_spec.rb +21 -0
  96. data/spec/simple2_spec.rb +36 -0
  97. data/spec/simple_spec.rb +30 -0
  98. data/spec/spec.opts +0 -0
  99. data/spec/spec_helper.rb +26 -0
  100. data/spec/thread_spec.rb +13 -0
  101. data/tasks/deployment.rake +34 -0
  102. data/tasks/environment.rake +7 -0
  103. data/tasks/rspec.rake +21 -0
  104. data/tasks/website.rake +17 -0
  105. data/test/jruby_lm.rb +13 -0
  106. data/test/test.rb +19 -0
  107. data/test/test10.rb +43 -0
  108. data/test/test11.rb +45 -0
  109. data/test/test12.rb +60 -0
  110. data/test/test13.rb +71 -0
  111. data/test/test2.rb +12 -0
  112. data/test/test3.rb +10 -0
  113. data/test/test4.rb +29 -0
  114. data/test/test5.rb +8 -0
  115. data/test/test6.rb +32 -0
  116. data/test/test7.rb +48 -0
  117. data/test/test8.rb +133 -0
  118. data/test/test9.rb +105 -0
  119. data/test/test_exc.rb +22 -0
  120. metadata +180 -0
@@ -0,0 +1,315 @@
1
+ h1. PingPong tutorial
2
+
3
+ h2. PingPong
4
+
5
+ This example creates two actors that send messages back and forth between each other, a bit like a ping pong ball. It's adapted from the "Scala Example":http://lamp.epfl.ch/~phaller/doc/ActorsTutorial.html. The code for the final versions is in source:examples/pingpong. There are three versions there: the serial (@source:examples/pingpong/serial.rb@) and the actor (@source:examples/pingpong/actor.rb@) versions which we develop here and, for reference, a version closer to the orignal Scala example (@source:examples/pingpong/scala.rb@).
6
+
7
+ For our example, we'll have two objects, @ping@ and @pong@ that pass a token back and forth a fixed number of times. We'll make the token, our "ball", be the number of volleys left to perform.
8
+
9
+ h2. Serial PingPong
10
+
11
+ We'll start with a simple non-concurrent version. We'll need a class with a @pingpong@ method. This method will take a count representing the number of volleys left to play and a reference to the partner that it is volleying with. With a little extra code so that we can see what is going on, it looks like this:
12
+
13
+ <pre><code class="ruby">
14
+ def pingpong count, partner
15
+ if count == 0
16
+ puts "#{@name}: done"
17
+ else
18
+ if count % 500 == 0 or count % 500 == 1
19
+ puts "#{@name}: pingpong #{count}"
20
+ end
21
+ partner.pingpong count-1, self
22
+ end
23
+ end
24
+ </code></pre>
25
+
26
+ That's really all there is to it. All we need is the class wrapper and few lines to actually create a couple of objects and start the ball rolling (or volleying, as the case may be):
27
+
28
+ <pre><code class="ruby">
29
+ class PingPong
30
+
31
+ def initialize name
32
+ @name = name
33
+ end
34
+
35
+ def pingpong count, partner
36
+ if count == 0
37
+ puts "#{@name}: done"
38
+ else
39
+ if count % 500 == 0 || count % 500 == 1
40
+ puts "#{@name}: pingpong #{count}"
41
+ end
42
+ partner.pingpong count-1, self
43
+ end
44
+ end
45
+
46
+ end
47
+
48
+ ping = PingPong.new "ping"
49
+ pong = PingPong.new "pong"
50
+
51
+ ping.pingpong ARGV[0].to_i, pong
52
+ </code></pre>
53
+
54
+ To see what happens, I can run a thousand volleys on my machine:
55
+ <pre><code>
56
+ $ ruby serial.rb 1000
57
+ ping: pingpong 1000
58
+ pong: pingpong 501
59
+ ping: pingpong 500
60
+ pong: pingpong 1
61
+ ping: done
62
+ $
63
+ </code></pre>
64
+ Great. If I get a little adventurous and try ten thousand volleys, something bad happens though:
65
+ <pre><code>
66
+ $ ruby serial.rb 10000
67
+ ping: pingpong 10000
68
+ pong: pingpong 9501
69
+ ping: pingpong 9500
70
+ pong: pingpong 9001
71
+ ping: pingpong 9000
72
+ pong: pingpong 8501
73
+ ping: pingpong 8500
74
+ pong: pingpong 8001
75
+ ping: pingpong 8000
76
+ pong: pingpong 7501
77
+ ping: pingpong 7500
78
+ pong: pingpong 7001
79
+ ping: pingpong 7000
80
+ pong: pingpong 6501
81
+ ping: pingpong 6500
82
+ pong: pingpong 6001
83
+ ping: pingpong 6000
84
+ Exception `SystemStackError' at serial.rb:13 - stack level too deep
85
+ serial.rb:13:in `pingpong': stack level too deep (SystemStackError)
86
+ from serial.rb:16:in `pingpong'
87
+ from serial.rb:25
88
+ $
89
+ </code></pre>
90
+ The trouble is that our little loop that looks like it's just passing data back and forth is actually doing so recursively. Although our @pingpong@ method doesn't return a useful value, we still have to call it recursively: that's the only way we have of calling methods. Most serial languages have no native way of expressing this volleying kind of communication between objects. It is possible to do this with _continuations_ but most languages/implementations, including ones we care about a lot like python and jruby, do not provide them. It's certainly possible to write a message passing layer, but it's a fair amount of code.
91
+
92
+ Lets put that aside for now and look at making @pingpong@ into an actor program using dramatis. To make a normal class into an actor, first we need to mixin a dramatis class:
93
+ <pre><code class="ruby">
94
+ class PingPong
95
+ include Dramtis::Actor
96
+ ...
97
+ </code></pre>
98
+ for which we need a require statement to bring in the library:
99
+ <pre><code class="ruby">
100
+ require 'dramatis/actor'
101
+ </code></pre>
102
+ That will get us started. Our objects are now actors.
103
+
104
+ h2. A little background on Actors.
105
+
106
+ Before we rush ahead, we need to consider what we've done, what actors are.
107
+
108
+ Actors are concurrent objects: they are part object, part thread, a kind of chimera. They look in many ways like normal objects: they have state (data members), methods (member functions), and they can have methods called on them.
109
+
110
+ They also are, abstractly, threads. Rather than have whatever thread is running when a method call is made on an actor execute that method recursively and immediately, each actor has its own thread and only that thread is allowed to execute methods for that actor. This implies that an actor can only be executing one method at a time, so there can be no races or conflicts among methods of a single actor. Note that this thread is abstract and that different actor implementations implement it in different ways (dramatis, for example, does not create a thread per actor).
111
+
112
+ h2. _rpc_ continuations
113
+
114
+ In dramatis, when an actor makes a call on another actor (often phrased in actor parlance as _sending a message_ in the same way that Ruby and Smalltalk are send messages between objects), rather than executing the method itself, on its thread, it creates a _task_, a combination of a reference to the called actor (which we call the _actor name_), the method to be called, any arguments to the method, and a _continuation_. The continuation is a representation of where the results of the method call should be sent. In the case of a normal call, like we're familiar with in non-concurrent programs, the continuation indicates a message should be sent back to the caller such that the result is delivered as the result of the method call. We call this style of method call a remote procedure call, or _rpc_, where _remote_ means on another actor.
115
+
116
+ One other aspect of actors, of their threads and methods, is that once begun, a method cannot be interrupted. If another task is scheduled on an executing actor, it cannot be executed until the current method runs to completion.
117
+
118
+ However, continuation passing provides a nice syntactic shortcut that does look a little bit like a method not running to completion. When an rpc call is made, a normal method call, on another actor, the calling thread is, in a way, waiting for the called thread. Lets look at an example. If @actor_1@ executes the code
119
+ <pre><code class="ruby">
120
+ def a_method
121
+ ...
122
+ x = actor_2.another_method
123
+ puts x
124
+ ...
125
+ end
126
+ </code></pre>
127
+ it clearly needs a value for @x@ before it can execute the @puts@ to print the value. We said before that calling a method on another actor runs on that other thread, which holds in this case: @actor_1@'s thread cannot run @another_method@: the method must run on @actor_2@'s thread. It may not be possible to run the method immediately: @actor_2@ may be in the middle of another task and may have other tasks queued to execute.
128
+
129
+ The semantics of the rpc protocol translates fairly easily into two tasks: @actor_1@ creates a task for the @another_method@ call and includes as the continuation of that task information that runtime can use to get the returned value to the right place in the call stack. When @actor_2@ completes @another_method@, it calls that continuation which results in a new task, targeted on @actor_1@, which, when the runtime executes it, will cause the call on @actor_1@ to complete and the return value from @another_method@ to be assigned to @x@.
130
+
131
+ h2. Selective reception
132
+
133
+ In effect, we've taken our @a_method@ and broken it in two, the part before the call to @another_method@ and the part after. When @actor_1@ calls @another_method@, it effectively finishes the first half. The continuation it sends to @actor_2@ effectively says, "run the second half". Since @actor_1@ has finished the first half of the method, it is finished with the current task. It can therefore execute another task, including the task that will be created by @actor_2@ when it finishes @another_method@.
134
+
135
+ What if there are other calls that have been made on @actor_1@? Can they run? Can they run before the result from @actor_2@ has been returned?
136
+
137
+ This is an area that different actor systems vary on, the ability to selectively block tasks. Dramatis does provide this ability. As mentioned, actor methods are uninterruptible: that uninterruptibility is key to controlling concurrency conflicts in actor systems. If all other methods could execute when an rpc call was made on another actor, abstractly, the uninterruptible nature of the method execution is lost. Steps that occur before the call and after no longer appear atomic and without this atomicity, rpcs become much less useful.
138
+
139
+ Many actor systems, including dramatis, provide selective receives. That is, they allow an actor to indicate that certain calls are acceptable or unacceptable at at any point in time. Dramatis uses this _gating_ behavior to implement consistent rpcs.
140
+
141
+ When an actor makes an rpc call on another actor, dramatis automatically restricts the set of tasks that the caller will accept. In general, it will only allow the task that will return the desired value to execute. Any other tasks that were queued at the time of the call or that are received before the target actor returns a value are deferred. In this way, dramatis maintains the atomity of methods even when rpcs involving multiple messages are used.
142
+
143
+ Dramatis also provides gating features so that actors can identity other methods that can be executed even while an rpc is pending.
144
+
145
+ Not all actor systems use implicit continuations as dramatis does. In many of these, the caller of an actor method must explicitly pass its name in the argument list and the target actor must explicitly send the result back. The effective is similar.
146
+
147
+ dramatis has other continuation types, as will be shown below. dramatis continuations are similar to native language continuations such as those found in Ruby, but have some extensions (they are concurrent) and limitations (often they cannot be called multiple times.) dramatis does not use native language continuations.
148
+
149
+ h2. Concurrent PingPong
150
+
151
+ So, with some background on actors, let return to our example. When we mixed in @Dramatis::Actor@, what changed in our program? First, lets look at the line that created our actors:
152
+ <pre><code class="ruby">
153
+ ping = PingPong.new "ping"
154
+ pong = PingPong.new "pong"
155
+ </code></pre>actor
156
+ Since these objects are actors, @new@ no longer returns a reference to the object. Instead, it returns a @Dramatis::Actor::Name@, a proxy for the actor. In most cases this proxy object, which we call the _actor name_, acts like a native object reference with the addition of actor semantics. So when we call
157
+ <pre><code class="ruby">
158
+ ping.pingpong ARGV[0].to_i, pong
159
+ </code></pre>
160
+ we are not making a call on the native object on the caller's thread, but are asking the runtime to create and schedule a task. Note that in the absence of any indication otherwise, these are rpc calls and thus act from the point of view of the caller virtually identically to the non-concurrent case.
161
+
162
+ The runtime will, at some point in the future, run the @pingpong@ method on @ping@ which will result in @ping@ executing
163
+ <pre><code class="ruby">
164
+ partner.pingpong count-1, self
165
+ </code></pre>
166
+ At this point, @partner@ will be @pong@, so dramatis will create another task, this time targeted at @pong@ and, at some point in the future, execute it.
167
+
168
+ h2. _pass by value_
169
+
170
+ Another actor issue comes in to play at this step. Actor systems are generally _pass by value_. That is, they send object values or copies, rather than references to objects. Nothing is shared between the caller and the callee. In pure actor systems, there are only values (which include actor names) and actors so nothing except actor state is mutable and actors are internally serial.
171
+
172
+ In this sense, dramatis is not a pure actor system. Since it's only a library on top of a non-actor language and virtual machine, this is pretty much guaranteed: to make a pure actor system would generally require changing either one or both. In addition to immutable values like numbers, dramatis programs have all the multiple objects found in non-concurrent program. dramatis provides mechanisms for for managing concurrency but cannot guarantee that shared objects will not have concurrent conflicts if they are used.
173
+
174
+ At this time, dramatis does not specify whether actor method call arguments will be copied or not. Thus some care is required when considering objects passed to actor methods.
175
+
176
+ One philosophy of dramatis is to balance concurrency issues with divergence from serial programming and at this point, it's unclear whether always copying method arguments is always a good idea.
177
+
178
+ Back on our example:
179
+ <pre><code class="ruby">
180
+ partner.pingpong count-1, self
181
+ </code></pre>
182
+ In the case of our count, there's really no difference here. But what about @self@? Generally @self@ in an actor method works as it normally does in a serial program. Only when an actor name is used do actor semantics enter the picture. Thus, an actor class can still call all its internal methods as it normally would without invoking actor semantics.
183
+
184
+ An exception to this occurs when passing @self@ references to actor objects as arguments to an actor method or as its return value. In these cases, the runtime automatically converts the @self@ reference to an actor name. This is a special case of _pass by value_, where the normal way to martial an actor is to convert a reference to an actor to an actor name. This case is handled specially by dramatis because it's a common pattern and simplifies coding.
185
+
186
+ So, in our example, when @ping@ calls @pong@ with @self@ as a parameter, dramatis substitutes @ping@'s actor name for @self@. An actor can get its own name by calling @actor.name@.
187
+
188
+ h2. deadlock
189
+
190
+ Finally, @pong@ will execute @pingpong@ and, if the count hasn't reached zero, will call @pingpong@ back on @ping@.
191
+
192
+ We can try to run it now but we get an error:
193
+ <pre><code>
194
+ $ ./actor.rb 100
195
+ ./actor.rb:22:in `pingpong': Dramatis::Deadlock (Dramatis::Deadlock)
196
+ from ./actor.rb:22:in `pingpong'
197
+ from ./actor.rb:31
198
+ $
199
+ </code></pre>
200
+ What's the problem? Starting at the top of the stack (the last line in the backtrace), we see where we kick off the volley by sending a @pingpong@ to our actor named @ping@ from our main program. @ping@ dutifully sends a @pingpong@ to @pong@ in the next stack frame. This works fine. Now @pong@ tries to volley back to @ping@ and something, perhaps unexpected, happens. dramatis is telling us that a deadlock has occurred while executing this code. (For those that may have noticed, the backtrace returned by dramatis represents the actor calls across threads; a raw (and much longer and messy) backtrace is also available).
201
+
202
+ The issue here is that we're trying to send a @pingpong@ back from @pong@ to @ping@, but @ping@ is still waiting to hear back from @pong@. It isn't busy: it doesn't have any messages to process. But as we mentioned above, it called @pong@ with an rpc call which set itself up only to receive the result from @pong@, and @pong@ hasn't returned it. Instead @pong@ is trying to make a new @pingpong@ call. This is corecursion, just as we had in our nonconcurrent case, and by default, dramatis does not allow it.
203
+
204
+ Before we fix this, we'll mention in passing that dramatis can be made to allow recursion and corecursion, what we call _call threading_, by default. Adding
205
+ <pre><code class="ruby">
206
+ actor.enable_call_threading
207
+ </code></pre>
208
+ in the actor before a recursive or corecursive call results in the entire call chain enabling calls back through any actors that are waiting for rpc returns from this call. This makes dramatis rpc calls effectively the same as the non-concurrent case and was developed to facilitate easing the development of concurrent program from serial programs.
209
+
210
+ h2. release continuations
211
+
212
+ The right way to fix this is to notice that we don't really need to recurse here at all. Our actors don't look at the result of the @pingpong@ method (which is just as well, since the method doesn't return anything useful).
213
+
214
+ What we need is a way to call a method but not wait around for the results (if you're paying close attention, we're being fast and loose with terminology here: actors don't wait, in most senses.) All actor implementations have this. In Erlang OTP it's called @cast@.
215
+
216
+ In dramatis, we make this non-waiting call by writing
217
+ <pre><code class="ruby">
218
+ class PingPong
219
+ release( partner ).pingpong count-1, self
220
+ </code></pre>
221
+ @release@ (or @Dramatis.release@ if you haven't used @include Dramatis@) takes an actor name and returns a new name. This new name acts slightly differently than the original name. It _releases_, if you will, the task created by the call. That is, it doesn't ask the task to return value and the method call returns immediately. Another way of looking at is that rather than providing the current continuation, it provides a nil continuation.
222
+
223
+ If we make this single change to our program and rerun it, we get:
224
+ <pre><code>
225
+ $ ./actor.rb 1000
226
+ ping: pingpong 1000
227
+ pong: pingpong 501
228
+ ping: pingpong 500
229
+ pong: pingpong 1
230
+ ping: done
231
+ $
232
+ </code></pre>
233
+ which is identical to the serial case. Moreover, if we try our big version, it works wonderfully:
234
+ <pre><code>
235
+ $ ./actor.rb 10000
236
+ ping: pingpong 10000
237
+ pong: pingpong 9501
238
+ ping: pingpong 9500
239
+ pong: pingpong 9001
240
+ ping: pingpong 9000
241
+ pong: pingpong 8501
242
+ ping: pingpong 8500
243
+ pong: pingpong 8001
244
+ ping: pingpong 8000
245
+ pong: pingpong 7501
246
+ ping: pingpong 7500
247
+ pong: pingpong 7001
248
+ ping: pingpong 7000
249
+ pong: pingpong 6501
250
+ ping: pingpong 6500
251
+ pong: pingpong 6001
252
+ ping: pingpong 6000
253
+ pong: pingpong 5501
254
+ ping: pingpong 5500
255
+ pong: pingpong 5001
256
+ ping: pingpong 5000
257
+ pong: pingpong 4501
258
+ ping: pingpong 4500
259
+ pong: pingpong 4001
260
+ ping: pingpong 4000
261
+ pong: pingpong 3501
262
+ ping: pingpong 3500
263
+ pong: pingpong 3001
264
+ ping: pingpong 3000
265
+ pong: pingpong 2501
266
+ ping: pingpong 2500
267
+ pong: pingpong 2001
268
+ ping: pingpong 2000
269
+ pong: pingpong 1501
270
+ ping: pingpong 1500
271
+ pong: pingpong 1001
272
+ ping: pingpong 1000
273
+ pong: pingpong 501
274
+ ping: pingpong 500
275
+ pong: pingpong 1
276
+ ping: done
277
+ $
278
+ </code></pre>
279
+ The reason it works now is because we are no longer calling @pingpong@ recursively. @ping@ calls @pong@ and then returns, going inactive until it gets a request from @pong@. Similarly, @pong@ calls @ping@ and then returns. This style of data-flow programming is dead-simple in actors and fairly complex in serial languages.
280
+
281
+ h2. Concurrent I/O
282
+
283
+ Finally, we could wonder, are we getting any other benefits from using actors here? We have a nice data flow model, but what about concurrency? We know in theory that the actors are running on different threads, but can we demonstrate that in a measurable way?
284
+
285
+ One useful feature of concurrency in actors is concurrent I/O: for example, fetching a number of web pages concurrently. That's a little complex for our example, but we can simulate it. Lets say that at each volley, our actors wanted to perform some time consuming I/O. To simulate this, we'll put a short sleep in our @pingpong@ method, right after we @pingpong@ our partner:
286
+ <pre><code class="ruby">
287
+ sleep 0.001
288
+ </code></pre>
289
+ (We want it to be short because we execute it a lot). We then execute both versions:
290
+ <pre><code>
291
+ $ time ./serial.rb 1000
292
+ ping: pingpong 1000
293
+ pong: pingpong 501
294
+ ping: pingpong 500
295
+ pong: pingpong 1
296
+ ping: done
297
+
298
+ real 0m11.291s
299
+ user 0m0.000s
300
+ sys 0m0.000s
301
+ $ time ./actor.rb 1000
302
+ ping: pingpong 1000
303
+ pong: pingpong 501
304
+ ping: pingpong 500
305
+ pong: pingpong 1
306
+ ping: done
307
+
308
+ real 0m5.060s
309
+ user 0m0.020s
310
+ sys 0m0.000s
311
+ $
312
+ </code></pre>
313
+ The actor version takes half the time of the serial version. This is because @ping@ and @pong@ get to overlap their sleep in the actor version. This can't be done in the serial version. This is analogous to saying, in a serial program, you can't fetch two web pages at the same time without resorting to some form of manual thread management or asynchronous I/O.
314
+
315
+ That's it. A concurrent actor program and you've seen the most important dramatis objects. Other, more advanced, features to explore are futures, available via @Dramatis.future@ and advanced task gating, available via methods of @Dramatis::Actor::Interface@.
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # cf. http://lamp.epfl.ch/~phaller/doc/ActorsTutorial.html
4
+
5
+ $:.push File.join( File.dirname(__FILE__), "..", "..", "lib" )
6
+
7
+ require 'dramatis/actor'
8
+
9
+ class Ping
10
+ include Dramatis::Actor
11
+ def initialize times, pong
12
+ @pings_left = times
13
+ release( pong ).ping actor.name
14
+ end
15
+ def pong caller
16
+ if @pings_left % 1000 == 0
17
+ puts "Ping: pong"
18
+ end
19
+ if @pings_left > 0
20
+ @pings_left -= 1
21
+ release( caller ).ping actor.name
22
+ end
23
+ end
24
+ end
25
+
26
+ class Pong
27
+ include Dramatis::Actor
28
+ def initialize
29
+ @pong_count = 0
30
+ end
31
+ def ping caller
32
+ if @pong_count % 1000 == 0
33
+ puts "Pong: ping #{@pong_count}"
34
+ end
35
+ @pong_count += 1
36
+ release( caller ).pong actor.name
37
+ end
38
+ end
39
+
40
+ pong = Pong.new
41
+ ping = Ping.new 10000, pong
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ class PingPong
4
+
5
+ def initialize name
6
+ @name = name
7
+ end
8
+
9
+ def pingpong count, partner
10
+ if count == 0
11
+ puts "#{@name}: done"
12
+ else
13
+ if count % 500 == 0 or count % 500 == 1
14
+ puts "#{@name}: pingpong #{count}"
15
+ end
16
+ partner.pingpong count-1, self
17
+ # sleep 0.001
18
+ end
19
+ end
20
+
21
+ end
22
+
23
+ ping = PingPong.new "ping"
24
+ pong = PingPong.new "pong"
25
+
26
+ ping.pingpong ARGV[0].to_i, pong
@@ -0,0 +1,108 @@
1
+ {notes/an early draft}
2
+
3
+ Exception flow, and pretty ones at that
4
+
5
+ It's funny how important I find pretty exceptions, which means
6
+
7
+ 1) exceptions that can trace back through actor calls
8
+ 2) that remove all the actor runtime dreck
9
+
10
+ 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
+
12
+ (0) exceptions flow back along synchronous actor calls
13
+
14
+ since that's kinda of a given.
15
+
16
+ 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
+
18
+ For example, take the simple little dramatis actor program:
19
+
20
+ require 'dramatis/actor'
21
+
22
+ class Foo
23
+ include Dramatis::Actor
24
+ def foo that
25
+ return that.bar :fooobar
26
+ end
27
+ end
28
+
29
+ class Bar
30
+ include Dramatis::Actor
31
+ def bar method
32
+ send method
33
+ end
34
+ def foobar
35
+ "foobar"
36
+ end
37
+ end
38
+
39
+ Foo.new.foo Bar.new
40
+
41
+ Since there's a typo in the program, :fooobar isn't defined for bar, and a NameError exception is thrown.
42
+
43
+ Now, if this was a normal, serial program, you'd get something like:
44
+
45
+ ./exception.rb:17:in `send': undefined method `fooobar' for #<Bar:0x2b928905d498> (NoMethodError)
46
+ from ./exception.rb:17:in `bar'
47
+ from ./exception.rb:10:in `foo'
48
+ from ./exception.rb:24
49
+
50
+ 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
+
52
+ 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
+
54
+ 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
+
56
+ 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
+
58
+ The answer to the first question is usullay, "nowhere useful" and to the second, ugh:
59
+
60
+ ./exception.rb:28:in `bar': undefined local variable or method `fooobar' for #<Bar:0x2ad75510df90> (NameError)
61
+ from ./../lib/dramatis/runtime/actor.rb:146:in `send'
62
+ from ./../lib/dramatis/runtime/actor.rb:146:in `deliver'
63
+ from ./../lib/dramatis/runtime/task.rb:81:in `deliver'
64
+ from ./../lib/dramatis/runtime/scheduler.rb:344:in `deliver'
65
+ from ./../lib/dramatis/runtime/scheduler.rb:257:in `run'
66
+ from ./../lib/dramatis/runtime/thread_pool.rb:136:in `call'
67
+ from ./../lib/dramatis/runtime/thread_pool.rb:136:in `target'
68
+ from ./../lib/dramatis/runtime/thread_pool.rb:127:in `synchronize'
69
+ ... 19 levels...
70
+ from ./../lib/dramatis/runtime/actor.rb:116:in `common_send'
71
+ from ./../lib/dramatis/runtime/actor.rb:93:in `actor_send'
72
+ from ./../lib/dramatis/actor.rb:35:in `new'
73
+ from ./exception.rb:35
74
+
75
+ 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
+
77
+ 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
+
79
+ 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
+
81
+ Well, actually, we do. If we change our call:
82
+
83
+ Foo.new.foo Bar.new
84
+
85
+ to
86
+
87
+ begin
88
+ Foo.new.foo Bar.new
89
+ rescue NameError => ne
90
+ puts "hey, I got a #{ne}"
91
+ puts "it happened here: " + ne.backtrace.join("\n")
92
+ end
93
+
94
+ 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
+
96
+ 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
+
98
+
99
+ hey, I got a undefined local variable or method `fooobar' for #<Bar:0x2ae257f29180>
100
+ it happened here: ./exception.rb:27:in `bar'
101
+ ./exception.rb:18:in `foo'
102
+ ./exception.rb:36
103
+
104
+ which is identical to the serial case.
105
+
106
+ 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
+
108
+ Progress, not perfection.
@@ -0,0 +1,2 @@
1
+ load File.join( "..", "..", ".irbrc" )
2
+ require 'kid'