RPCs and responses
In this tutorial, we will see how to initialize PyMargo, register an RPC function, and call it from a client.
PyMargo server
The following code initializes a PyMargo Engine for use as a server, then prints the address at which the server can be contacted. It exposes a “hello” RPC that takes a firstname and a lastname, prints them, and returns a string.
from pymargo.core import Engine, Handle
def hello(handle: Handle, firstname: str, lastname: str):
print(f"Hello {firstname} {lastname}!")
handle.respond(f"Good bye {firstname}")
if __name__ == "__main__":
with Engine("tcp") as engine:
engine.register("hello", hello)
print(f"Service running at {engine.address}")
engine.enable_remote_shutdown()
engine.wait_for_finalize()
The Engine
’s constructor takes the protocol as the first
argument. Additional arguments may be provided:
mode
:pymargo.client
orpymargo.server
, to indicate whether the engine will be used as a client or a server (defaults to server);use_progress_thread
: a boolean indicating whether to use a progress thread or not;num_rpc_threads
: the number of Argobots ES for servicing RPCs. Note that because of the GIL, using a value other than 0 will not necessarily result in better concurrency;config
: a dictionary or a JSON-formatted string representing a Margo configuration.
We then call engine.register
to associate an RPC name with
a function. Since in Python everything is an object, you can pass
any callable to the engine.register
function, including
methods bound to instances, or instances of objects with a __call__
method.
The register
function also accepts the following optional
arguments:
provider_id
: an integer equal or greater than 0 and less than 65535. This argument is used when working with providers.disable_response
: a boolean indicating that the RPC will not send a response back.
The engine.address
call inside our print statement returns an
Address
object corresponding to the address of our server.
This address is here converted into a string when printed.
The call to enable_remote_shutdown
on the engine will allow a remote
process to ask our server to shutdown.
Looking at the hello
function, the first argument
must be a Handle
object. The rest of the arguments and their numbers
can be anything, as long as their types are
pickleable.
The Handle
object is used to call respond
, among other things.
handle.address
can be used to get the address of the sender.
Any object can be passed to respond
, again as long as its type
is pickleable. Here, our function takes two argument (firstname and lastname)
of expected type str
, and will respond with a string.
Note that the Engine
is used as a context manager (with
statement),
this context will automatically call finalize
on the engine
when exiting, hence we have to explicitely block on a wait_for_finalize
call to avoid exiting the context and start servicing RPCs.
PyMargo client
The following code is the corresponding client.
import sys
from pymargo.core import Engine
import pymargo
if __name__ == "__main__":
with Engine("tcp", mode=pymargo.client) as engine:
hello = engine.register("hello")
address = engine.lookup(sys.argv[1])
response = hello.on(address)("Matthieu", lastname="Dorier")
assert response == "Good bye Matthieu"
address.shutdown()
In this code, we initialize the Engine
with mode=pymargo.client
.
We then use the engine’s lookup
function to convert a string address
(taken from sys.argv[1]
) into an Address
object.
We call register
with only the name of the RPC.
This function returns a RemoteFunction
object.
By calling on(Address)
on this object, we get a CallableRemoteFunction
with a __call__
operator that we can call. Said address is obtained by
calling engine.lookup
on a string address (here taken from sys.argv[1]
).
The code shows how arguments (positional and keywords) will be sent to the server
when passed to the __call__
operator of the CallableRemoteFunction
.
Here firstname
is provided as positional, while lastname
is provided as a keyword argument. The returned value is the object passed to
handle.respond
in the server.
Finally, address.shutdown()
sends a specific message to our server
asking it to shutdown.
Important
Do not use the argument names blocking
and timeout
when defining RPCs (or if you do, make sure you use them as positional
arguments when invoking the RPC). These argument keys are used by
PyMargo to control the behavior of the call (more on that later).
Note
Python installs a signal handler to catch keyboard interrupts, but
this signal handler cannot run when the server is blocked on
wait_for_finalize
or on C/C++ code more generally.
Hence Ctrl-C won’t kill your server.
Due to the interactions between the GIL and Argobots, it is also
possible that a program does not terminate after an exception is
raised. To kill such a program, you will have to resort to kill -9
.