From: David Masover Date: 2008-07-27T09:31:15+09:00 Subject: Re: DSL/thread design question On Saturday 26 July 2008 17:10:24 Steven Parkes wrote: > > I am writing something which I am calling "suits" for now -- the idea > > being "something you make with threads". It's a lightweight > > actor model. > > You might want to look at dramatis: http://dramatis.mischance.net/ Haven't seen that; I'll take a more in-depth look later. Keep in mind, I don't care about implementation at this point, but design. The whole reason I'm using Ruby instead of Erlang for this is beautiful syntax. (I find Erlang ugly.) That's why, for example, I'm using one thread per actor -- it's simpler to write right now (no need to worry about blocking operations...) and the interface shouldn't change much later, if I have to switch to a threadpool. > > s.push(1,2,3,4,5) # asynchronous, so ignoring the > > return value > > release( s ).push(1,2,3,4,5) => nil What does "release" do, in this context? And why not make it a method on the wrapped object? > > s.join # reap the thread > > There's no equivalent for this right now ... among other things, each actor > doesn't have a unique thread; they're shared as necessary. But there should > probably be a way for actors to exit a la Erlang's exit ... or is GC enough? > I'm a little unsure on this. I would much rather use GC, if it would work. I'm not sure how to make GC work here, though -- and certainly not for one thread/actor. > > My motivation is to at least make multithreaded Ruby an > > attractive option, so > > that there is a motivation to remove the Python-esque Global > > Interpreter > > JRuby doesn't have a GIL. But my results of using threads on JRuby are still > a little strange (but it may be my fault). However, Java threads are probably heavier than YARV threads. Just a guess. Right now, Suits only work on Ruby 1.9 -- though I'd like to port to 1.8, there were some problems with those threads. I don't remember what they were, though. > > So, should this be synchronous by default? (Right now, it's > > not.) > > In my past work with actors, making things async by default was painful > (but, then, I didn't have lambdas, which help). I haven't made async the > default in dramatis: I prefer to start from something close to serial and > incrementally expose concurrency. One of the more powerful features of Erlang, I thought, was how easy it made concurrency -- how it was almost a natural feature. So I'm still torn. I do want to know how "async by default" was painful, though. > > Second, for asynchronous calls, since I'm using a Queue, > > things arrive > > in-order. > > Yeah; you're relying on that in your example. Otherwise, you can't guarantee > that the #push has executed before the #[] runs. True. What I'm wondering is if it would be better to call s.push(...).now s[...] > > And finally, what about exception handling? > > Right now, dramatis tries to deliver exceptions to the caller where it can, > e.g., on blocking/rpc-style calls. I'm really not sure this is a good idea, Well, I think the problem with this is, what happens to anyone else who wants to send something to the actor? Is the actor still in a valid state after this? In single-threaded code, it's easy -- it's up to the caller. If the caller decides they can handle the exception, they can handle repairing the internal state of the callee, if that has to happen -- or they can drop the object and let it be collected. If the caller can't do that, we don't have to worry about anyone else sending a message, because the program's likely about to implode. But with an actor, anyone else might be sending messages at the same time. We might have to distinguish, then, between a transient error (which would simply notify whoever needs to be notified, probably whoever sent the message which caused it) and a fatal error (which kills that actor). > If you made > every actor linked by default, then you'd get the behavior you want, where > killing an actor killed the program (unless they took measure to stop that.) That brings up other interesting problems -- how do you handle the main thread? And how are we catching this, then? A method, maybe -- something like linked_process_died? The trick is, I want both -- I want something that works well for one-off examples, and something that works well for large clusters of independent nodes.