DSL/thread design question

D

David Masover

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.
Works something like this:

s = Suit.new([])
s.push(1,2,3,4,5) # asynchronous, so ignoring the return value
s[2].now # returns 3
s.length.then{|l| puts l} # prints 5, at some point
s.join # reap the thread

The idea is simple: Map Ruby's object model onto the actor model. (Actually, I
didn't know I was doing actors until I found Revactor -- I just knew it was
vaguely Erlang-inspired.) Therefore, an actor simply looks and feels like an
object, and message-passing is done through Ruby method calls (which are sort
of message passing anyway).

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
Lock. I don't believe separate Unix processes will scale well enough.

There are a few design issues, and I think I'm out of my league here.

Three problems:



First, while I like the syntax of "now" vs "then", it does require some
discipline if you were expecting this to behave like the object it's
referring to. It also means I can't drop a Suit-wrapped object into something
expecting the original.

But making things asynchronous by default would force developers to be aware
of how synchronous (or not) they're actually being.

So, should this be synchronous by default? (Right now, it's not.) Maybe I
should return a "promise" instead, so that things are asynchronous until you
actually need information out of the returned value.



Second, for asynchronous calls, since I'm using a Queue, things arrive
in-order. Would there be an advantage to not requiring that behavior?
Intuitively, I think there would be times when random delivery would be an
asset, but I can't justify it. About the only reason for attempting random
delivery would be to force the programmer to think about multiple threads
sending messages simultaneously.



And finally, what about exception handling? Right now, exceptions will be
delivered on #join, but that requires explicitly checking for them. The thing
that I love about exceptions in a single-threaded app is that unless you do
something special, the exception kills your program. I would much prefer the
behavior to be similar to that, rather than trying to return an exception.

I kind of like how exception handling is done in Erlang, in which
one "process" (think: actor) is responsible for handling the errors of
another. But I'm not sure what the simplest way of doing that in Ruby would
be.
 
S

Steven Parkes

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/

Of course, I'm biased, but I did create it out of actor-envy towards Erlang.
s = Suit.new([])

s = Dramatis::Actor.new( [] ) => Dramatis::Actor::Name === s

in dramatis.
s.push(1,2,3,4,5) # asynchronous, so ignoring the
return value

release( s ).push(1,2,3,4,5) => nil
s[2].now # returns 3

s[2] => 3
s.length.then{|l| puts l} # prints 5, at some point

This rather ungainly syntax:
( interface( s ).continue { |l| puts l } ).length => puts 5
or, in the next release, slightly better:
( continuation( s ) { |l| puts l } ).length => puts 5
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.
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).
(By the way, dramatis also runs on python and some of the examples run on
jython but I haven't run the full suite of tests on jython yet).
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. But it is possible to code to get it async
by default, for example:

s = Dramatis::Actor.new( [] )
s = release( s )

would make all subsequent calls on s async by default. The proxy object
maintains the type of continuation that will be used when calls are made
against that proxy.
Maybe I
should return a "promise" instead, so that things are
asynchronous until you
actually need information out of the returned value.

Dramatis has futures, though they're experimental (actually, all of dramatis
is, they're just _more_ experimental.)

In my limited tests, each kind of continuation has uses for which it is more
natural than the alternatives.
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.
Would there be an advantage to not requiring that behavior?

It generally works out the other way: often people want guaranteed ordered
queuing, at least among messages sent between the same pairs of actors (so
things like your example work). But while this is easy in a single process,
it's more difficult when you're exchanging messages over the network.

Writing code that can't rely on ordering can get tricky (think of what you'd
have to do to your simple example).

Dramatis has selective receive (though implemented differently than Erlang)
which helps loosen the need for in-order delivery in some contexts.
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,
though it's been helpful in my toy examples. This isn't what Erlang does,
where you need to catch the exception explicitly and send it to the caller;
otherwise uncaught exceptions kill the actor (Erlang process).

Erlang really sets the bar for this: while one may not completely love the
way they've implemented things, they have large systems that perform
reliability. Their linked actors is a hugely valuable feature. 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.)

Dramatis doesn't have this yet but I sure wish it did and hopefully it will
...
 
D

David Masover

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.
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?
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.
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.
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[...]
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.
 
S

Steven Parkes

From: David Masover [mailto:[email protected]]
Keep in mind, I don't care about implementation at this
point, but design.

Well, we're together on that, though, of course, peoples' opinion of design
differs.
(I find Erlang ugly.)

Beyond that, it's predominantly functional, not object-oriented, and not
used widely for general purpose development. I could get over the syntax but
...
What does "release" do, in this context? And why not make it
a method on the
wrapped object?

It generates a new name that when used to call methods, calls the methods
asynchronously/with a null continuation.

s.push is a sync/rpc-style call, release( s ).push is async.

release does this by returning a new name with different continuation
semantics. It's all encapsulated within the name.

I didn't make it a method because I didn't want it to always have to be
there, i.e., I wanted to be able to use
s.push in the simple case (as opposed to s.sync.push and s.async.push).

With that constraint, I didn't want to make it a method because it impinges
on the namespace of the serial behavior of the actor, i.e., if sync is the
default, and you have to say s.async to get async, you can't (easily) use an
#async method on the actor itself.

It's important to me that there be no methods on the proxy that are aimed at
the proxy rather than what the proxy points at.
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.

Yeah: you could have a problem with the thread-per-actor because it might
not be clear when the actor is not actually doing anything (it can't be GC'd
while its doing something)?

But GC is hard when you move to distributed anyway: distributed GC is hard,
which might be reason enough to bag it. I haven't gone there yet.
However, Java threads are probably heavier than YARV threads.
Just a guess.

Actually, if I had to guess, I'd guess the opposite. They're both kernel
scheduled threads and there's a heck of a lot more experience with threads
in Java than there is in 1.9.
I do want to know how "async by default" was painful, though.

In my code, sync calls, for example for status, were fairly common.
Requiring all those to have something explicit to make sync work was
painful. Taking a trivial call like "other.status" and exploding it to lots
more characters or multiple lines gets old fast. In my eyes, anyway.

I really want code that looks serial to do the right serial thing, even if
the objects are actors. So far, this works in dramatis.

If you're doing sync calls w/o an explicit receive, you might want to look
at Erlang/OTP's gen_server behaviour: it does that (and raises the selective
receive issues).
s.push(...).now
s[...]

Yup; just using a sync call even if you don't need the value is common and
valid way of generating the necessary control flow.

Brings up selective receive again, though. Can the calling actor receive any
other messages while it's waiting for #now? That's one of the trickier
things to handle in actor programs.
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?

If the exception is delivered to the caller, the actor remains in a fine
state. It's pretty much what Erlang does if you manually catch the exception
and forward it. But given the wide variety of exceptions that can occur,
maybe sending the exception up is a poor default. And you can't do it in the
async case, anyway, so ...

But this introduces a big difference between serial and actor code even in
the rpc case, which I don't like.

So I don't know ....

I> In single-threaded code, it's easy -- it's up to the caller.

Right. There's no ambiguity. No choice. Here there's a choice. As soon as
you have multiple actors, you have multiple stacks and in theory you can
send the exception up either. I have cases where both are useful but I don't
have anyway of making the runtime figure out the right way to handle things
except making it explicit.
That brings up other interesting problems -- how do you
handle the main
thread?

There are two parts to this. Any thread that the runtime didn't create is
considered external/exogenous and in order to fit it into the actor model, a
pseudo-actor is created for it if necessary (when, for example, it needs to
accept the response from an rpc-like call).

The other issue unique to main is keeping it from exiting when there is
actor work to be done. I use an at_exit handler for that.
And how are we catching this, then? A method, maybe -- something like
linked_process_died?

Something similar to that. More likely I'll provide a method that takes a
block: if you want to catch an exception signal (using Erlang terminology),
the actor calls this method with the block that it wants to get the signal.
That block will be called when the signal is received, in which case the
recipient won't be killed. This is more or less what Erlang does (I forget
the BIF you call to do this.)

This is getting pretty deep into the guts. I started a list a few weeks ago
for people discussing actor issues across languages/implementations:
http://groups.google.com/group/actor-talk. Would it make more sense to do
this there? There's also a list for dramatis
(http://groups.google.com/group/dramatis) but if you just want to compare,
actor-talk is probably better.
 
D

David Masover

From: David Masover [mailto:[email protected]]
Keep in mind, I don't care about implementation at this
point, but design.

Well, we're together on that, though, of course, peoples' opinion of design
differs.

That's part of why I posted. (The other reason is to try to figure out the
exception handling.)
What does "release" do, in this context? And why not make it
a method on the
wrapped object?
[snip]
I didn't make it a method because I didn't want it to always have to be
there, i.e., I wanted to be able to use
s.push in the simple case (as opposed to s.sync.push and s.async.push).

I was thinking it would be easier to be able to do s.sync.push (or
s.async.push), and still have the semantics you want, as in:

released_s = s.release
released_s.push(...)

In fact, that's part of where my syntax came from -- the object returned from
every method call is a "ReturnPath" object, which can then be used to control
what happens after the call. That's why I have things like this:

s.length.now

What I'm thinking now is that I should be returning futures instead, so that I
keep the asynchronous-by-default behavior, but no extra effort is needed to
use things synchronously.

The one danger here is (again) exception handling. If something goes wrong, I
don't know when I actually make the method call, I know when I check the
future -- and one of the appeals of the design is that if I don't check the
future, it's a blind call.
With that constraint, I didn't want to make it a method because it impinges
on the namespace of the serial behavior of the actor, i.e., if sync is the
default, and you have to say s.async to get async, you can't (easily) use an
#async method on the actor itself.

Very early on, I realized I was going to end up doing this. I'd much rather
pollute the actor's namespace than the kernel namespace, and there are two
assumptions being made here: First, that most actors will be specifically
written for that purpose, and second, that there would be some sort of
standard override -- some #actor_send method.

So far, though, I haven't actually modified the real objects, only the proxy.
Yeah: you could have a problem with the thread-per-actor because it might
not be clear when the actor is not actually doing anything (it can't be GC'd
while its doing something)?

Well, I would love for Ruby to GC them on their own.
I do want to know how "async by default" was painful, though.
[snip]
I really want code that looks serial to do the right serial thing, even if
the objects are actors. So far, this works in dramatis.

I agree.

But I also want parallel code to not only be easy to write, I want it to be as
natural as serial code.
Brings up selective receive again, though. Can the calling actor receive any
other messages while it's waiting for #now?

In short, no. The implementation is absurdly simple -- I believe it's
something like 100 lines of code and 200 lines of specs.

So, that said, here's some relevant code:

class Suit
def initialize obj
...
@Thread = Thread.new do
loop do
message = queue.pop
break if message.nil?
message.call object
end
end
end

The messages sent are actually blocks. Specifically:

def thread_eval &block
queue << block
end

And, predictably, the main usage is:

def method_missing *arguments, &block
ReturnPath.new.tap do |path|
thread_eval do |obj|
path.value = obj.public_send(*arguments, &block)
end
end
end

So, in short, nothing can happen in that thread outside the loop. The loop
will block calling that method on the object (indirectly). So if the method
itself ever blocks, the entire thread is blocked.

Messages can be sent while this happens, but they will be queued.

This was, in fact, the whole point -- from beginning to end of the method
call, nothing else may interfere. Within the object itself, there is no
concurrency, and you don't have to think about concurrency.
But this introduces a big difference between serial and actor code even in
the rpc case, which I don't like. [snip]
I> In single-threaded code, it's easy -- it's up to the caller.

Right. There's no ambiguity. No choice. Here there's a choice. As soon as
you have multiple actors, you have multiple stacks and in theory you can
send the exception up either. I have cases where both are useful but I don't
have anyway of making the runtime figure out the right way to handle things
except making it explicit.

I see it as more a semantic problem -- I started this because I like working
with sequential Ruby, and I want to keep most of the semantics of that. But
concurrency does require at least thinking in a different way...
Something similar to that. More likely I'll provide a method that takes a
block: if you want to catch an exception signal (using Erlang terminology),
the actor calls this method with the block that it wants to get the signal.
That block will be called when the signal is received, in which case the
recipient won't be killed. This is more or less what Erlang does (I forget
the BIF you call to do this.)

I can see that -- one advantage is, no pollution of the actor's own namespace.

In what context would it run?
This is getting pretty deep into the guts. I started a list a few weeks ago
for people discussing actor issues across languages/implementations:
http://groups.google.com/group/actor-talk. Would it make more sense to do
this there? There's also a list for dramatis
(http://groups.google.com/group/dramatis) but if you just want to compare,
actor-talk is probably better.

I want to compare, at first, and learn. Dramatis looks more complete, but I
like my syntax better (hey, I'm biased) -- ultimately, I'd rather not have
duplicate code. (Unless I dig deeper and find myself hating yours, in which
case, it's on! :p)

I am specifically interested in doing this in Ruby, so I don't think it's
entirely offtopic for ruby-talk, either.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads


Members online

No members online now.

Forum statistics

Threads
473,990
Messages
2,570,211
Members
46,796
Latest member
SteveBreed

Latest Threads

Top