Re: DSL/thread design question

Whoops. Reposting to all.

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.