Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How do pykka actors send a message to themselves? #24

Open
fatuhoku opened this issue Sep 1, 2013 · 5 comments
Open

How do pykka actors send a message to themselves? #24

fatuhoku opened this issue Sep 1, 2013 · 5 comments

Comments

@fatuhoku
Copy link

fatuhoku commented Sep 1, 2013

I'm writing a simple Producer actor that's supposed to spit out a string every second. At creation time, it doesn't do anything.
When I send it a 'start' message it should start producing the string I nominate to the target I nominate.
When I send it a 'stop' message it should stop producing.

If we were doing this like Erlang, there'd be a sort of tail-recursive call that serves as the continuation of the process. But that's all a bit deep for Python. All we have is the on_receive function, which may or may not block.

My first stab looks something like this:

class StringProducer(GeventActor):
    def __init__(self, target, string='UNDEFINED STRING'):
        super(StringProducer, self).__init__()
        self.string = string
        self.target = target

    def on_receive(self):
        while True:
            self.target.tell({'produced':self.string})
            gevent.sleep(1)

I don't even bother looking at the command type (start or stop) because I feel stuck already. Is this the way to do it?
I would start a producer and give it a poke as to begin its production loop:

p1 = StringProducer.start(buffer, "apple")
p1.tell({'command':'start'})
gevent.sleep(5)
p1.tell({'command':'stop'})

How would you guys do it?

@jodal
Copy link
Owner

jodal commented Sep 1, 2013

Something like this should work:

import time

import pykka


class StringPrinter(pykka.ThreadingActor):
    def on_receive(self, message):
        print 'I got a message:', message


class StringProducer(pykka.ThreadingActor):
    def __init__(self, target, string):
        super(StringProducer, self).__init__()
        self.target = target
        self.string = string
        self.running = False

    def on_receive(self, message):
        if message.get('command') == 'start':
            self.running = True
            self.produce()
        elif message.get('command') == 'stop':
            self.running = False
        elif message.get('command') == 'produce':
            self.produce()

    def produce(self):
        if not self.running:
            return
        time.sleep(1)
        self.target.tell({
            'command': 'string_delivery',
            'string': self.string,
        })
        self.actor_ref.tell({'command': 'produce'})


consumer = StringPrinter.start()
producer = StringProducer.start(consumer, 'apple')

producer.tell({'command': 'start'})
time.sleep(3)
producer.tell({'command': 'stop'})

producer.stop()
consumer.stop()

The trick is to use self.actor_ref to send messages to yourself to schedule more work, while at the same time allowing other messages, like the stop command, to be delivered and handled. The inelegant part here is the fact that Pykka has no built-in way of saying "deliver this in N seconds", so we're forced to use a time.sleep() or similar.

@fatuhoku
Copy link
Author

fatuhoku commented Sep 1, 2013

Awesome. self.actor_ref wasn't really documented in the tutorial so it'd be great if it's added.
I tested the code, it works fine. Thanks a lot jodal!

In reality the inelegance of time.sleep() is not a problem since that would be more like a blocking call that yields execution to other greenlets.

Hmmm. This question is outside the scope of the issue, but can you recommend any HTTP libraries that perform requests asynchronously that would work well with this pykka approach? (quite a useful piece of information for other developers too I'm sure)

@jodal
Copy link
Owner

jodal commented Sep 1, 2013

I haven't used much HTTP libs, so I don't off hand know of any async HTTP libs for Python. Ignoring async/sync, I'd have a look at requests.

@fatuhoku
Copy link
Author

fatuhoku commented Sep 1, 2013

Well do with this issue as you will. I'm happy for it to be closed, though you might want to attach any changes relevant to it before you do so.

@stefanmohl
Copy link

So, I am trying to do something similar using the GeventActor, but I am having real problems. I can't even get Jodals producer-consumer example from above (comment on 1 Sep 2013) to work the same way as the ThreadingActor example (the GeventActor version will only produce a single message, no matter how long the time.sleep() waits). Is there some inherent inconsistency making the different threading models behave differently? How would you write them to make sure they always work? I am trying to build a version where the producer is always guaranteed to run (e.g. it is producing from external input with limited buffering).

It seems to me that if you take the asynchronous nature of actors seriously, the implementation is allowed to take as long as it likes to deliver a message. To guarantee timeliness, the example would have to be re-written so that the producer runs in an actual loop rather than tail-recursing through actor-messages, but that puts us back at the suggestion from the initial poster.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants