DSL/thread design question

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.

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

From: David M. [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

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?

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.

On Saturday 26 July 2008 20:43:37 Steven P. wrote:

From: David M. [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.)

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?

[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.

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)?

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…

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.)

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.

On Saturday 26 July 2008 17:10:24 Steven P. 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.

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs