Remote Tutorial

This page explains hot to use PyActor for remote communications between machines.

Sample 1 - Basic communication

This example shows the basis on setting a remote communication and sending tell requests. This is the full code of this sample, which you can find and test in pyactor\examples\Remote\s1_server.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
"""
Basic remote example sending tell messages. SERVER
@author: Daniel Barcelona Pons
"""
from pyactor.context import set_context, create_host, serve_forever


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

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


if __name__ == '__main__':
    set_context()
    host = create_host("http://127.0.0.1:1277/")

    e1 = host.spawn('echo1', Echo)
    serve_forever()

And pyactor\examples\Remote\s1_client.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
"""
Basic remote example sending tell messages. CLIENT
@author: Daniel Barcelona Pons
"""
from pyactor.context import set_context, create_host, shutdown


if __name__ == '__main__':
    set_context()
    host = create_host("http://127.0.0.1:1679")

    e1 = host.lookup_url("http://127.0.0.1:1277/echo1", 'Echo', 's1_server')

    e1.echo("Hi there!")    # TELL message
    e1.echo("See ya!")

    shutdown()

To create a host able to communicate with other machines, simply use as its URL one with an http scheme, as in the example. Using the http scheme will create a dispatcher on that host that will manage the queries through xml.

So, the server spawns an actor at 127.0.0.1:1277 and the client is able to look for that actor just giving that IP:port and path. If the client does not have the Class it is looking for, it must provide the module and the name of that class when calling the lookup method as shown.

Then, the calls are used as usual.

In s1_clientb.py we have the same code but the calls are repeated 1000 times.

Sample 2 - Basic communication 2

This example extends the first by adding ask requests. This is the full code of this sample, which you can find and test in pyactor\examples\Remote\s2_server.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
"""
Basic remote example sending ask messages. SERVER
@author: Daniel Barcelona Pons
"""
from pyactor.context import set_context, create_host, serve_forever


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

    def __init__(self):
        self.msgs = []

    def echo(self, msg):
        print(msg)
        self.msgs.append(msg)

    def get_msgs(self):
        return self.msgs


if __name__ == '__main__':
    set_context()
    host = create_host("http://127.0.0.1:1277/")

    e1 = host.spawn('echo1', Echo)
    serve_forever()

And pyactor\examples\Remote\s2_client.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
"""
Basic remote example sending ask messages. CLIENT
@author: Daniel Barcelona Pons
"""
from pyactor.context import set_context, create_host, shutdown


if __name__ == '__main__':
    set_context()
    host = create_host("http://127.0.0.1:1679")

    e1 = host.lookup_url("http://127.0.0.1:1277/echo1", 'Echo', 's2_server')

    e1.echo('Hi there!')    # TELL message
    e1.echo('See ya!')

    print(e1.get_msgs())

    shutdown()

This sample is like the previous one, but it includes examples of ask methods. As the tell methods, they are used as normally, like in the local examples.

Sample 3 - Remote spawning

This example shows how to spawn an actor in another host. This is the full code of this sample, which you can find and test in pyactor\examples\Remote\s3_host.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
"""
Remote example spawning on a remote server. SERVER
@author: Daniel Barcelona Pons
"""
from pyactor.context import set_context, create_host, serve_forever


if __name__ == '__main__':
    set_context()
    host = create_host("http://127.0.0.1:1277/")

    print("host listening at port 1277")

    serve_forever()

And pyactor\examples\Remote\s3_client.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
"""
Remote example spawning on a remote server. CLIENT
@author: Daniel Barcelona Pons
"""
from pyactor.context import set_context, create_host, Host, sleep, shutdown
from pyactor.exceptions import PyActorTimeoutError


class Server(object):
    _ask = {'add', 'wait_a_lot'}
    _tell = {'substract'}

    def add(self, x, y):
        return x + y

    def substract(self, x, y):
        print("subtract", x - y)

    def wait_a_lot(self):
        sleep(2)
        return "ok"


if __name__ == '__main__':
    set_context()
    host = create_host("http://127.0.0.1:1679")

    remote_host = host.lookup_url("http://127.0.0.1:1277/", Host)
    print(remote_host)
    server = remote_host.spawn('server', 's3_client/Server')
    z = server.add(6, 7)
    print(z)
    server.subtract(6, 5)
    t = server.add(8, 7)
    print(t)

    try:
        print(server.wait_a_lot(timeout=1))
    except PyActorTimeoutError as e:
        print(e)

    sleep(3)
    shutdown()

In this case the server part only creates its host and makes it serve forever (serve_forever()). The client is the one that uses lookup_url() to get the server reference and spawn an actor in it. Then, sends the work to the actor. To spawn the actor, as the class of it is defined in the client module, the method uses a string to define where is the Class so the server can import it. This string uses the form module/class_name:

server = remote_host.spawn('server', 's3_client/Server')

Sample 4 - Registry example

Here we have a basic example of a registry where some servers can bind to so the clients are able to see all the servers available and connect to one. This is the full code of this sample, which you can find and test in pyactor\examples\Remote\s4_registry.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
"""
Remote example with a registry. SERVER
@author: Daniel Barcelona Pons
"""
from pyactor.context import set_context, create_host, serve_forever


class NotFound(Exception):
    pass


class Registry(object):
    _ask = {'get_all', 'bind', 'lookup', 'unbind'}
    _ref = {'get_all', 'bind', 'lookup'}

    def __init__(self):
        self.actors = {}

    def bind(self, name, actor):
        print("server registred", name)
        self.actors[name] = actor

    def unbind(self, name):
        if name in self.actors.keys():
            del self.actors[name]
        else:
            raise NotFound()

    def lookup(self, name):
        if name in self.actors:
            return self.actors[name]
        else:
            return None

    def get_all(self):
        return self.actors.values()


if __name__ == '__main__':
    set_context()
    host = create_host("http://127.0.0.1:6000/")

    registry = host.spawn('regis', Registry)

    print("host listening at port 6000")

    serve_forever()

And pyactor\examples\Remote\s4_client.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
"""
Remote example with registry. CLIENT
@author: Daniel Barcelona Pons
"""
from pyactor.context import set_context, create_host, serve_forever


if __name__ == '__main__':
    set_context()
    host = create_host("http://127.0.0.1:6001")

    registry = host.lookup_url("http://127.0.0.1:6000/regis", 'Registry',
                               's4_registry')

    registry.bind('host1', host)

    serve_forever()

And pyactor\examples\Remote\s4_clientb.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
"""
Remote example with registry. CLIENT 2
@author: Daniel Barcelona Pons
"""
from pyactor.context import set_context, create_host, sleep, shutdown

from s4_registry import NotFound


class Server(object):
    _ask = {'add', 'wait_a_lot'}
    _tell = {'subtract'}

    def add(self, x, y):
        return x + y

    def subtract(self, x, y):
        print("subtract", x - y)

    def wait_a_lot(self):
        sleep(2)
        return "ok"


if __name__ == '__main__':
    set_context()
    host = create_host("http://127.0.0.1:6002")

    registry = host.lookup_url("http://127.0.0.1:6000/regis", 'Registry',
                               's4_registry')
    remote_host = registry.lookup('host1')
    if remote_host is not None:
        if not remote_host.has_actor('server'):
            server = remote_host.spawn('server', 's4_clientb/Server')
        else:
            server = remote_host.lookup('server')
        z = server.add(6, 7)
        print(z)
        server.subtract(6, 5)
        t = server.add(8, 7)
        print(t)

    try:
        registry.unbind('None')
    except NotFound:
        print("Cannot unbind this object: is not in the registry.")

    shutdown()

In this example we have a registry where Servers can be bound. The registry module starts an actor which is the registry itself to which servers can be bound and clients look for servers. The first client binds its host to the registry and waits. The second one uses the registry to find the first’s host and spawn a server on it. Then, send work to that server.

In order to execute the second client repeatedly without having to restart all the processes, before spawning the server remotely, it checks if the first client has already the server by using the method has_actor on the remote_host.

Sample 5 - Multiple Hosts

This example tests the creation of multiple host at the same time on one unique execution. This is the full code of this sample, which you can find and test in pyactor\examples\Remote\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
"""
Multiple hosts. Remote required since v0.9.
@author: Daniel Barcelona Pons
"""
from pyactor.context import set_context, create_host, sleep, shutdown


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

    def echo(self, msg, pref=None):
        print(msg, pref)


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

    h2 = create_host("http://127.0.0.1:7777/host")
    e2 = h2.spawn('echo1', Echo)
    e2.echo("hello 2", e1)

    sleep(1)

    e1.echo("hello 3", e2)

    sleep(1)
    shutdown()
    # or, to only stop one of them:
    # shutdown("http://127.0.0.1:7777/host")

The first thing to make clear is that you should never need to create more than one host locally, since they are meant for remote communication. This is for testing purposes.

To create more hosts, you only need to call again the function create_host(). But you will need to specify different locations for each host, since those are their identifiers. In the example we create two hosts in the same location, but attending different ports:

h = create_host("http://127.0.0.1:6666/host")
h2 = create_host("http://127.0.0.1:7777/host")

Note

Remember that the default address for a host is local://local:6666/host

Note

To communicate two hosts, both of them must have a remote dispatcher, so they must have one of the schemes required.

Now, each host will manage its own actors and threads, so they will need to communicate through TCP connections.

One thing important to know about this is that only one host can be used to manage the main execution of your program, so there always will be a main host and the other ones will be created as secondary hosts.

This main host will be automatically assigned to the first one created. If that one is closed and there still are other hosts operative, the oldest of them will assume the role of main host.

Using RabbitMQ

Unmaintained Only works on a single machine with multiple hosts and needs the rabbit server running locally.

This library also supports the usage of communication through RabbitMQ queues. To use this approach, simply define the hosts with an URL with the scheme amqp instead of http. This will create a dispatcher for that host that works with RabbitMQ, and all its actors will work at that scheme.

You can see an example with pyactor\examples\Remote\s1_clientrbb.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
"""
Basic remote example sending tell messages. CLIENT
@author: Daniel Barcelona Pons
"""
from pyactor.context import \
    set_context, create_host, set_rabbit_credentials, shutdown


if __name__ == '__main__':
    set_rabbit_credentials('daniel', 'passs')
    set_context()
    host = create_host("amqp://127.0.0.1:1679")

    e1 = host.lookup_url("amqp://127.0.0.1:1277/echo1", 'Echo', 's1_server')

    e1.echo("Hi there!")    # TELL message
    e1.echo("See ya!")

    shutdown()

and pyactor\examples\Remote\s1_serverrbb.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
"""
Basic remote example sending tell messages. SERVER
@author: Daniel Barcelona Pons
"""
from pyactor.context import \
    set_context, create_host, set_rabbit_credentials, serve_forever


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

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


if __name__ == '__main__':
    # set_rabbit_credentials('daniel', 'passs')
    set_context()
    host = create_host("amqp://127.0.0.1:1277/")

    e1 = host.spawn('echo1', Echo)
    serve_forever()

You can configure your rabbit credentials with:

setRabbitCredentials('user', 'password')

If you don’t, it will use the default Rabbit guest user, which only can connect locally.