Tutorial

A quick guide on how to use the PyActor library through examples.

Installation

This library allows the creation and management of actors in a distributed system using Python. It follows the classic actor model and tries to be a simple way to get two remote actors to quickly communicate.

To install the library, use:

python setup.py install

You can check that works with the examples explained in this page, that you can find in the ./examples directory of this project.

The library requires Gevent.

It is also available at PYPI, so the most easy way of installing PyActor is by:

pip install pyactor

Then you can check the examples from the repository.

Global indications

This library is implemented using two types of concurrence: threads and green threads (Gevent). To define which one you want, always use the function set_context() at the beginning of your script. The default value uses threads but you can specify the mode with one of the following strings:

  • 'thread'
  • 'green_thread'

Then, first of all, a Host is needed in order to create some actors. To create a host, use the function create_host() which returns a proxy (Proxy) to the instance of a Host. You should never work with the instance itself, but always with proxies to send messages to actors. When you have the proxy, use it to spawn actors by giving the class type of the actor to create and one string that will identify it in the host. The spawn() method will return the proxy that manages that actor. See example:

h = create_host()
actor1 = h.spawn('id1', MyClass)

The class of an actor must have defined its methods in the _tell and _ask sets so they can be called through the proxy. In the _tell set will be named those methods meant to be asynchronous and in the _ask set, the synchronous ones. In this example we have a class MyClass with a sync method ask_me() and an async method tell_me():

class MyClass:
    _tell = {'tell_me'}
    _ask = {'ask_me'}
    def tell_me(self, msg):
        print(msg)
    def ask_me(self):
        return "hello back"

As you can see, the async method receives a message and simply prints it while the sync method returns a result. More detailed examples can be found in the ‘pyactor/examples’ directory of the project. They are also explained below as a tutorial for this library.

Sample 1 - Basic

This example shows and tests the most basic elements of PyActor. It creates a Host and adds an actor to it. Then, queries an async method of this actor. This is the full code of this sample, which you can find and test in pyactor\examples\sample1.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
"""
Basic host creation sample.
"""
from pyactor.context import set_context, create_host, sleep, shutdown


class Echo(object):
    _tell = {'echo'}

    def echo(self, msg):
        print(msg)


if __name__ == '__main__':
    set_context()
    h = create_host()
    e1 = h.spawn('echo1', Echo)
    e1.echo("hello there !!")

    sleep(1)
    shutdown()

The example is similar to the one shown above in Global indications, but here we’ll explain it more carefully.

In this case, we need to import the create_host() function from the project in order to use it. We also import the sleep function, to give time to the actor to work, and the setting function for the type, set_context(). Finally, we also need the shutdown() function to stop and clean the host before finishing.

The actor to create in this example will be an Echo. This class only has one method which prints the message msg, given by parameter. As you can see, the classes destined to be actors must have the attributes _tell={...} and _ask={...} that include the names of the methods that can be remotely invoked in an asynchronous or synchronous way, respectively. In this sample we have the echo method, which is async, as no response from it is needed.

Note

In this sample we do not have synchronous methods, so it is not necessary to declare the _ask set. However, it could also be declared as an empty ser _ask = set().

The first thing to do is define which model are we going to use. For now, we are using the classic threads, so we’ll call the function without parameters to use the default solution.

set_context()

To begin the execution we’ll need a Host to contain the actors. For that, we create a new variable by using the function we imported before.

h = create_host()

Now we have a Host in the ‘h’ variable. Actually, as Host objects are also actors, this call returns a Proxy that will manage that actor. It can create actors attached to itself. To do that, we use the spawn() method. The first parameter is a string with the ID of the actor that will identify it among the host, so no repeated values are allowed. The second is the class the actor will be instance of. In this case we create an actor which will be an Echo and with the id ‘echo1’:

e1 = h.spawn('echo1', Echo)

‘e1’ will now represent that actor (actually, it’s a Proxy that manages it).

As we have the actor, we can invoke its methods as we would do normally since the proxy will redirect the queries to the actual placement of it. If we didn’t have specified the methods in the statements appointed before (_tell and _ask), we wouldn’t be able to do this now, giving a ‘no such attribute error’. The execution should work properly and print on screen:

hello there !!

Then, the sleep gives time to the actor for doing the work and finally, we close the host, which will stop all its actors. This function (shutdown()) should be always called at the end to do a clean exit:

shutdown()

Note

As the host is an actor itself, it has sync and async methods and can receive remote queries if we use its proxy.

Note

As said, the host is also a living actor so it could receive queries remotely in the future. This means you can send its reference to another host, which allows to spawn remotely (remote spawns require a bit more info, see the remote tutorial).

Note

Now you can try and see how it works with green threads by just specifying ‘green_thread’ in the setting function. set_context('green_thread')

Sample 2 - Sync

This example extends the content of the previous one by including sync requests. It still creates a Host and adds an actor to it. This is the full code of this sample, which you can find and test in pyactor\examples\sample2.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
"""
Sync/async queries sample.
"""
from pyactor.context import set_context, create_host, sleep, shutdown


class Echo(object):
    _tell = {'echo', 'bye'}
    _ask = {'say_something'}

    def echo(self, msg):
        print(msg)

    def bye(self):
        print("bye")

    def say_something(self):
        return "something"


if __name__ == '__main__':
    set_context()
    h = create_host()
    e1 = h.spawn('echo1', Echo)
    e1.echo("hello there !!")
    e1.bye()

    print(e1.say_something())

    sleep(1)
    shutdown()

Now Echo has two new methods, bye() and say_something(). The first one is async like the previous echo(), but the other one is synchronous.

The invocation of ask methods is simply the same you would do normally.

The correct output for this sample is the following:

hello there !!
bye
something

Sample 3 - Timeout

This example tests the raising of timeouts. This is the full code of this sample, which you can find and test in pyactor\examples\sample3.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
"""
Timeout sample.
"""
from pyactor.context import set_context, create_host, sleep, shutdown
from pyactor.exceptions import PyActorTimeoutError


class Echo(object):
    _tell = {'echo', 'bye'}
    _ask = {'say_something'}

    def echo(self, msg):
        print(msg)

    def bye(self):
        print("bye")

    def say_something(self):
        sleep(2)
        return "something"


if __name__ == '__main__':
    set_context()
    h = create_host()
    e1 = h.spawn('echo1', Echo)
    e1.echo("hello there !!")
    e1.bye()

    try:
        x = e1.say_something(timeout=1)
    except PyActorTimeoutError:
        print("timeout caught")
    sleep(1)
    shutdown()

Now we have the same Echo class but in the sync method we added a sleep of 2 seconds. Also, we surrounded the method call by a try structure catching a PyActorTimeoutError exception from pyactor.exceptions. Since we are giving an expire time of 1 second to the invocation, the timeout will be reached and the exception raised.

You can set a timeout for the query of your choice. For that, add the keyword parameter timeout=X in the call, in seconds.

x = e1.say_something(timeout=3)

The default timeout is 10 seconds. To wait indefinitely, just set it to None, but that is not recommended.

The correct output for this sample is the following:

hello there !!
bye
timeout caught

Sample 4 - Lookup

This example shows the usage of the lookup methods applied to a host. This is the full code of this sample, which you can find and test in pyactor\examples\sample4.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
"""
Lookup sample.
"""
from pyactor.context import set_context, create_host, sleep, shutdown


class Echo(object):
    _tell = {'echo', 'bye'}
    _ask = {'say_something'}

    def echo(self, msg):
        print(msg)

    def bye(self):
        print("bye")

    def say_something(self):
        return "something"


if __name__ == '__main__':
    set_context()
    h = create_host()
    e1 = h.spawn('echo1', Echo)

    e = h.lookup('echo1')
    print(e.say_something())

    ee = h.lookup_url("local://local:6666/echo1", Echo)
    print(ee.say_something())

    sleep(1)
    shutdown()

We have two ways to get the reference of one already existing actor of a host. If it is local, of the same host, it is fine to use the method lookup() giving by parameter only the id of the actor you wish:

e = h.lookup('echo1')

If you are working remotely, you could need lookup_url() to get the reference. In this example, it is used also to get a local reference giving the standard local URL at which the host is initialized by default:

ee = h.lookup_url('local://local:6666/echo1')

Note

Please follow the remote tutorial to get a better overview of the programming with remote hosts. This tutorial focuses on local hosts.

Sample 5 - References to actors

This example tests the sending of proxy references by parameter using the definition of the _ref set. This is the full code of this sample, which you can find and test in pyactor\examples\sample5.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
"""
Proxy references by parameter sample.
"""
from pyactor.context import set_context, create_host, sleep, shutdown


class Echo(object):
    _tell = {'echo', 'echo2', 'echo3'}
    _ref = {'echo', 'echo2', 'echo3'}

    def echo(self, msg, sender):
        print(f"{msg} from: {sender.get_name()}")

    def echo2(self, msg, senders):
        for sender in senders:
            print(f"{msg} from: {sender.get_name()}")

    def echo3(self, msg, senders):
        for sender in senders.values():
            print(f"{msg} from: {sender.get_name()}")


class Bot(object):
    _ask = {'get_name'}

    def get_name(self):
        return self.id


if __name__ == '__main__':
    set_context()
    h = create_host()
    e1 = h.spawn('echo1', Echo)
    bot = h.spawn('bot1', Bot)
    bot2 = h.spawn('bot2', Bot)
    sleep(1)
    e1.echo("HI!", bot)
    e1.echo2("hello there!", [bot2])
    e1.echo3("hello there!!", {'bot1': bot, 'bot2': bot2})

    sleep(1)
    shutdown()

If you pass references to actors (proxies) by parameter in actors methods, would mean they are sharing the same instance of a proxy. This could cause various concurrency problems, so we might want different proxies in different spots. To achieve that, you have to indicate that a method receives or returns a proxy by adding it to the class’ _ref set (it still must be in _ask or _tell).

With this indication, PyActor will search for proxies in the parameters and make a new proxy for the actor in the context that the method will be executed.

In the example, Echo has methods that receive a proxy, in this methods you can see examples of passing proxies even inside lists or dictionaries. For that to work correctly on any system, Echo needs to define its methods as they have this functionality. This is why all three methods are in the _ref set

_ref = {'echo', 'echo2', 'echo3'}

Although the proxies are different, you may yet compare them directly so when using p1 == p2 on two proxies, the comparison will be done on the actors that they represent and not on the proxy instance itself. See the basic examples on proxies_test.py.

Sample 6 - self.id, proxy and host

This example tests the self references to an actor’s id and proxy. This is the full code of this sample, which you can find and test in pyactor\examples\sample6.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
"""
Self references sample. Actor id/proxy. + serve_forever
"""
from pyactor.context import set_context, create_host, sleep, serve_forever


class Echo(object):
    _tell = {'echo'}
    _ref = {'echo'}

    def echo(self, msg, sender):
        print(f"{msg} from: {sender.get_name()} at {sender.get_net()}")
        # print(sender.get_id(), sender.get_url())


class Bot(object):
    _tell = {'set_echo', 'say_hi'}
    _ask = {'get_name', 'get_net'}

    def __init__(self):
        self.greetings = ["hello", "hi", "hey", "what's up?"]

    def set_echo(self):
        self.echo = self.host.lookup('echo1')

    def get_name(self):
        return self.id

    def get_net(self):
        return self.url

    def say_hi(self):
        for salute in self.greetings:
            self.echo.echo(salute, self.proxy)


if __name__ == '__main__':
    set_context()
    h = create_host()
    e1 = h.spawn('echo1', Echo)
    bot = h.spawn('bot1', Bot)
    bot.set_echo()
    bot.say_hi()

    sleep(1)
    serve_forever()

This sample demonstrates how to get references to an actor from the actor itself. With self.id we obtain the string that identifies the actor in the host it is located, self.url contains its network location. Then, with self.proxy you can get a reference to a proxy managing the actor so you can give it to another function, class or module in a safe and easy way.

Note

Remember to put methods that receive or return proxies in the _ref set.

It is also possible to use self.host, which will give a proxy to the host in which the actor is, so you can lookup() other actors from there, among other possibilities.

In the example, we use these three calls to send various salutations from a Bot to an Echo giving by parameter also a proxy from the Bot so the Echo can call one of the Bot’s methods to get its id. Also, the set_echo() method, in this case, does not receive the Echo by parameter. It uses the inside reference it already has to call a lookup() to the host and get the wanted reference.

Also notice that every proxy has the methods get_id and get_url already defined, so you can get the actor’s information directly from the proxy. This means we could use sender.get_id() instead of sender.get_name(); and sender.get_url() instead of sender.get_net() on the echo method.

The correct output for this sample is the following:

hello from: bot1
hi from: bot1
hey from: bot1
what`s up? from: bot1
Press Ctrl+C to kill the execution

In this sample, we also see the usage of the serve_forever() function which is very useful in remote communication in order to keep a host alive as another one sends queries to its actors. The usage is very simple, instead of shutting the host down at the end, we call:

serve_forever()

This will maintain the host alive in lower process consumption until the user presses Ctrl+C allowing other hosts to lookup and call methods from actors in this host.

Sample 7 - References extended

This example extends sample 5. This is the full code of this sample, which you can find and test in pyactor\examples\sample7.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
"""
Proxy references by parameter sample.
"""
from pyactor.context import set_context, create_host, sleep, shutdown


class Echo(object):
    _tell = {'echo', 'echo2', 'echo3'}
    _ref = {'echo', 'echo2', 'echo3'}

    def echo(self, msg, sender):
        print(f"{msg} from: {sender.get_name()}")

    def echo2(self, msg, senders):
        for sender in senders:
            print(f"{msg} from: {sender.get_name()}")

    def echo3(self, msg, senders):
        for sender in senders.values():
            print(f"{msg} from: {sender.get_name()}")


class Bot(object):
    _tell = {'set_echo', 'say_hi'}
    _ask = {'get_name'}
    _ref = {'set_echo'}

    def __init__(self):
        self.greetings = ["hello", "hi", "hey", "what's up?"]

    def set_echo(self, echo):
        self.echo = echo

    def get_name(self):
        return self.id

    def say_hi(self):
        for salute in self.greetings:
            self.echo.echo(salute, self.proxy)


if __name__ == '__main__':
    set_context()
    h = create_host()
    e1 = h.spawn('echo1', Echo)
    bot = h.spawn('bot1', Bot)
    bot2 = h.spawn('bot2', Bot)
    bot.set_echo(e1)    # Passing a proxy to a method marked as _ref
    sleep(1)            # Give time to host to lookup the first one
    bot2.set_echo(e1)
    bot.say_hi()
    sleep(1)
    e1.echo2("hello there!", [bot2])
    e1.echo3("hello there!!", {'bot1': bot, 'bot2': bot2})

    sleep(1)
    shutdown()

To remark the importance of using the _ref set, we extend here sample 5 with more examples of passing proxies combined with the self references we saw in sample 6.

Bot has a method set_echo that gets the echo it will use by parameter. As this echo has to be a proxy, Bot includes the next definition:

_ref = {'set_echo'}

So then, at the main code, we can make this call without any concurrency problems, as the proxies are not shared:

bot.set_echo(e1)

As already seen in sample 5, Echo has methods that receive a proxy. Including examples of passing proxies even inside lists or dictionaries.

Sample 8 - Futures

This example tests more deeply the features of futures. This is the full code of this sample, which you can find and test in pyactor\examples\sample8.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
"""
Futures Sample.
@author: Daniel Barcelona Pons
"""
from pyactor.context import set_context, create_host, sleep, shutdown


class Echo(object):
    _tell = {'echo'}
    _ask = {'say_something', 'raise_something'}

    def echo(self, msg):
        print(msg)

    def say_something(self):
        return "something"

    def raise_something(self):
        raise Exception("raising something")


if __name__ == '__main__':
    set_context()
    # set_context('green_thread')
    h = create_host()
    e1 = h.spawn('echo1', Echo)
    e1.echo("hello there !!")

    # ask = e1.raise_something(future=True)
    ask = e1.say_something(future=True)
    print(f"Future: {ask}")
    sleep(0.1)
    if ask.done():
        print(f"Exception: {ask.exception()}")
        try:
            print(f"Result: {ask.result(1)}")
        except Exception as e:
            print(e)

    sleep(1)
    shutdown()

The example is like Sample 3, but here we use the futures approach.

We do this by adding the parameter future=True to the call. This will make the query return a Future instance instead of the result. That means that the execution of the query may have not been completed yet. To get the result from a Future, use the method result() as you can see in the try section.

Also shows the usage of the consulting methods of futures: done(), and exception().

Change between this lines:

ask = e1.raise_something(future=True)
ask = e1.say_something(future=True)

to check the raising of exceptions.

Finally, note that the only argument for result() (also for exception()) is the timeout: the time, in seconds, to wait for a result before raising an error.

Sample 9 - Callback

This example tries the functionality of the callback element of the synchronous queries. This is the full code of this sample, which you can find and test in pyactor\examples\sample9.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
"""
Callback sample.
"""
from pyactor.context import set_context, create_host, sleep, shutdown


class Echo(object):
    _tell = {'echo', 'bye'}
    _ask = {'say_something'}

    def echo(self, msg):
        print(msg)

    def bye(self):
        print("bye")

    def say_something(self):
        sleep(1)
        return "something"


class Bot(object):
    _tell = {'set_echo', 'ping', 'pong'}
    _ref = {'set_echo'}

    def set_echo(self, echo):
        self.echo = echo

    def ping(self):
        future = self.echo.say_something(future=True)
        future.add_callback('pong')
        future.add_callback('pong')
        print("pinging...")

    def pong(self, future):
        msg = future.result()
        print("callback", msg)


if __name__ == '__main__':
    set_context()
    h = create_host()
    e1 = h.spawn('echo1', Echo)
    bot = h.spawn('bot', Bot)
    bot.set_echo(e1)
    bot.ping()

    sleep(2)
    shutdown()

This time we keep having the same initialization as before, but now there is a new class. Bot has three async methods that will allow to prove the callback functionality. set_echo() registers an Echo to the Bot so it can call it. \(ping\) creates the query for the say_something() method and sets the callback for this to his other method pong(). This second will receive the result of the execution of the say_something() method.

Remember, set_echo() needs to be listed in the Bot class’ _ref set.

In order to add a callback, the sync call must be defined as a Future. We do this by adding the parameter future=True to the call.

Then, use the Future method add_callback() which takes by parameter the name of the method to callback, which is one from the actor that calls it. You can add various callbacks to one future, and they will be called in order when the work is finished. Also, if you add a callback to a finished future, it will be directly invoked.

See Sample 8 - Futures for a more complex sample on Futures.

Note

add_callback() needs to be called from inside an actor, specifying a method of that same actor.

Note

The method treated as a callback must have one unique parameter, which is the future. Inside the method you can use result() to get the result of the call (exceptions can be raised) or exception() to get the instance of a possible raised exception. You can also check the state of the future with one of its methods: done() or running().

The correct output for this sample is the following:

pinging...
callback something
callback something

Sample 10 - Parallel

This example tests the creation and execution of actors with parallel methods. This is the full code of this sample, which you can find and test in pyactor\examples\sample10.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
"""
Parallel methods sample.
"""
from pyactor.context import set_context, create_host, sleep, shutdown
from pyactor.exceptions import PyActorTimeoutError


class File(object):
    _ask = {'download'}

    def download(self, filename):
        print(f"downloading {filename}")
        sleep(5)
        return True


class Web(object):
    _ask = {'list_files', 'get_file'}
    _tell = {'remote_server'}
    _parallel = {'list_files', 'get_file', 'remote_server'}
    # Comment the line above to check the raise of timeouts if parallels
    # are not used.
    _ref = {'remote_server'}

    def __init__(self):
        self.files = ["a1.txt", "a2.txt", "a3.txt", "a4.zip"]

    def remote_server(self, file_server):
        self.server = file_server

    def list_files(self):
        return self.files

    def get_file(self, filename):
        return self.server.download(filename, timeout=6)


class Workload(object):
    _tell = {'launch', 'download', 'remote_server'}
    _ref = {'remote_server'}

    def launch(self):
        for i in range(10):
            try:
                print(self.server.list_files(timeout=2))
            except PyActorTimeoutError as e:
                print(i, e)

    def remote_server(self, web_server):
        self.server = web_server

    def download(self):
        self.server.get_file('a1.txt', timeout=10)
        print("download finished")


if __name__ == '__main__':
    set_context('green_thread')
    # set_context()

    host = create_host()

    f1 = host.spawn('file1', File)
    web = host.spawn('web1', Web)
    sleep(1)
    web.remote_server(f1)
    load = host.spawn('wl1', Workload)
    load.remote_server(web)
    load2 = host.spawn('wl2', Workload)
    load2.remote_server(web)

    load.launch()
    load2.download()

    sleep(7)
    shutdown()

Parallels are a way of letting one actor process many queries at a time. This will allow the actor to keep receiving calls when another call has been blocked with another job (an I/O call or a synchronous call to another actor).

To make one method execute parallel, you need to specify it in the class attribute _parallel, which is a set. The method must also be in one of the sets _tell or _ask. The methods with this tag will be executed in new threads so their execution do not interfere with receiving other queries. That is, the actor can attend other queries while executing the parallel method.

As you could think, executing methods of the same actor at the same time can compromise the integrity of data. PyActor ensures that only one thread is executing on an actor at the same time, allowing other threads to execute when the one executing is blocked with some call. This prevents two threads from accessing the same data at a time, but is up to the programmer to prevent the data to change during the execution of a method if that is not intended, as a method could modify a property of the actor while a parallel, that operates with that data, is blocked, leading to an inconsistency.

In this example we have three classes: File, Web and Workload. File represents a server that serves the download of files. Simulates the work with a sleep. Web represents a web server which contains a list of files. It must have a file server that provides the files and can list its files (list_files) and return one of them (get_file). Workload is the class that will do the work. It asks the web to list its files ten times, or requests to download one of the files.

The execution is simple, we create one file server, one web server and attach the file server to the web:

web.remote_server(f1)

Then let’s do the work. Create two Workload instances and pass to them the web server we created:

load = host.spawn('wl1', Workload)
load.remote_server(web)
load2 = host.spawn('wl2', Workload)
load2.remote_server(web)

The first worker will make the ten queries to list_files, while the second one will download a file:

load.launch()
load2.download()

As the method get_file is marked as parallel, its execution will be done in another thread, so when the method blocks downloading (in the sleep), it will free the actor so it can keep serving answers to the first load.

If we do not use parallels in this example (which you can try by commenting the right line as indicated) some of the calls to the list_files method will raise TimeoutError as that actor’s thread is blocked with the download.

Note

sample10b combines this example with the use of Futures.

Note

You can test another parallel example with parall.py. That might result simpler to follow.

Sample 11 - Intervals

This example tests the usage of intervals that allow an actor to periodically do an action. This is the full code of this sample, which you can find and test in pyactor\examples\sample11.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
"""
Intervals sample
@author: Daniel Barcelona Pons
"""
from pyactor.context import set_context, create_host, sleep, shutdown, \
    interval, later


class Registry(object):
    _tell = {'hello', 'init_start', 'stop_interval'}
    # _ref = {'hello'}

    def init_start(self):
        self.interval1 = interval(self.host, 1, self.proxy, "hello", "you", "too")
        later(5, self.proxy, "stop_interval")

    def stop_interval(self):
        print("stopping interval")
        self.interval1.set()

    def hello(self, msg, m2):
        print(f"{self.id} Hello {msg} {m2}")


if __name__ == '__main__':
    N = 2   # 10000

    set_context()
    host = create_host()
    registry = list()
    for i in range(0, N):
        registry.append(host.spawn(str(i), Registry))

    for i in range(0, N):
        registry[i].init_start()

    sleep(8)
    shutdown()

To generate intervals, we use the functions context.interval() and context.later() that can be imported if needed. The class (actor) will call the first one giving firstly the proxy of the host that will manage the interval, accessible from within the actor by self.host; next, the interval time and the proxy to the actor to which make the periodic call (that can be itself with self.proxy or another actor) as well as the name of the method in that actor that will be called. The method to be executed must be a tell method (with ref or without it), otherwise, it will raise and exception.

This function returns an interval instance that we have to keep in order to stop it later by calling .set().

In this example we use context.later() to set a timer that will stop the interval after a certain time. This method works similar to the other. You specify by parameter the actor and the method to be executed after that time, and only accepts methods of the tell type.

If the method requires arguments, those can be passed in the same call. In the example, hello needs one argument and it is passed as:

self.host.interval(1, self.proxy, "hello", "you")

If the method needed two of them, it would be like follows:

self.host.interval(1, self.proxy, "hello", "you", "too")

Sample 1b - Stopping an Actor (Advanced)

This example is like the first one, but extended with a new functionality for the hosts. This shows how to stop an actor and delete all its references from one host. This is the full code of this sample, which you can find and test in pyactor\examples\sample1b.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
"""
Stopping an actor.
"""
from pyactor.context import set_context, create_host, sleep, shutdown


class Echo(object):
    _tell = {'echo'}

    def echo(self, msg):
        print(msg)


if __name__ == '__main__':
    set_context()
    h = create_host()
    e1 = h.spawn('echo1', Echo)
    e1.echo("hello there !!")

    sleep(1)
    h.stop_actor('echo1')

    e1 = h.spawn('echo1', Echo)
    e1.echo("hello there !!")

    sleep(1)
    shutdown()

You can always delete an actor by calling the method stop_actor() of its host. This function will stop the thread of that actor and all its references from the host. This means the actor cannot be looked up anymore, it will not receive any more work and you can create a new actor with its same id.

Note

Parallel queries already submitted will end as usual.

Note

Intervals involving that actor’s methods might result in errors.