Providers
Providers are objects that encapsulate a number of fonctions exposed as RPCs. While the previous tutorials already show how one can register methods of an object’s instance to use as and RPC, PyMargo provides a number of decorators to automatize the registration of such functions.
The following server code shows these decorators.
from mochi.margo import Engine, Handle, provider, remote, on_finalize, on_prefinalize
@provider(service_name="my_service")
class MyProvider:
@remote
def say_hello(self, handle, firstname, lastname):
print(f"Hello {firstname} {lastname}!")
handle.respond(f"Good bye {firstname}")
@on_finalize
def finalize(self):
print("Called when the engine finalizes")
@on_prefinalize
def prefinalize(self):
print("Called right before the engine finalizes")
if __name__ == "__main__":
with Engine("tcp") as engine:
provider = MyProvider()
engine.register_provider(provider, provider_id=42)
print(f"Service running at {engine.address}")
engine.enable_remote_shutdown()
engine.wait_for_finalize()
The provider decorator can be used to decorate a class
with a service name. This service name will be prepended to
function names, along with a “_”, to make up the full RPC names.
The remote decorator indicates that a function is
remotely callable. This decorator can be used with optional arguments,
e.g., remote(rpc_name="name", disable_response=True).
When registered, the RPC name used will be the function name,
unless an rpc_name parameter is provided to the decorator.
The on_prefinalize and on_finalize decorators
are here to specify that a function should be called before
the engine is finalized, and during finalization (the difference
being that in pre-finalization stage, it is still allowed to
send and receive RPCs, while in finalization phase it is not).
Once an instance of MyProvider is created, we simply
pass it to engine.register_provider and the engine
will discover the functions marked as RPCs and the pre-finalization
and finalization callbacks. This function also takes an optional
provider_id argument, so that multiple instances can be
registered, distinguished by their provider id.
The following code shows the corresponding client.
import sys
from mochi.margo import Engine, MargoException
import mochi.margo as pymargo
if __name__ == "__main__":
with Engine("tcp", mode=pymargo.client) as engine:
hello = engine.register("my_service_say_hello")
address = engine.lookup(sys.argv[1])
try:
response = hello.on(address, provider_id=42)("Matthieu", lastname="Dorier")
assert response == "Good bye Matthieu"
except MargoException as e:
print(e)
address.shutdown()
The client needs to build the full RPC name by itself
(here “my_service_say_hello”). The on function, which
binds a RemoteFunction with an Address, takes
an optional provider_id argument.
Important
The code above also protects the call to the RPC with a
try ... except. It is important in PyMargo code
to catch exceptions and properly act on them. Uncaught,
an exception could make objects like RPC handles outlive
the engine that created them, causing segmentation faults
when the garbage collector tries to free them.
Note
We have demonstrated here the registration of a provider
object, but since in Python everything is an object, we
can also pass a module with
global functions decorated in the same manner to
register_provider.