bridge (pb): implemented an asyncio compatible bridge:

`pb` bridge can now be used with asyncio by instantiating AIOBridge.
This commit is contained in:
Goffi 2019-09-25 08:53:38 +02:00
commit 5cee4fbab7
3 changed files with 449 additions and 55 deletions

View file

@ -40,7 +40,12 @@ class pbConstructor(base_constructor.Constructor):
d.addCallback({callback})
if errback is None:
errback = self._generic_errback
d.addErrback(errback)\n"""
d.addErrback(errback)\n""",
"async_methods": """\
def {name}(self{args_comma}{args}):
{debug}d = self.root.callRemote("{name}"{args_comma}{args_no_def})
d.addErrback(self._errback)
return d.asFuture(asyncio.get_event_loop())\n""",
}
def core_completion_signal(self, completion, function, default, arg_doc, async_):

View file

@ -18,10 +18,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import dataclasses
from sat.core.log import getLogger
from twisted.spread import jelly, pb
from twisted.internet import reactor
log = getLogger(__name__)
@ -40,34 +41,41 @@ def fixed_jelly(self, obj):
jelly._Jellier.jelly = fixed_jelly
@dataclasses.dataclass(eq=False)
class HandlerWrapper:
# we use a wrapper to keep signals handlers because RemoteReference doesn't support
# comparison (other than equality), making it unusable with a list
handler: pb.RemoteReference
class PBRoot(pb.Root):
def __init__(self):
self.signals_handlers = []
def remote_initBridge(self, signals_handler):
self.signals_handlers.append(signals_handler)
self.signals_handlers.append(HandlerWrapper(signals_handler))
log.info("registered signal handler")
def sendSignalEb(self, failure, signal_name):
log.error(
"Error while sending signal {name}: {msg}".format(
name=signal_name, msg=failure
def sendSignalEb(self, failure_, signal_name):
if not failure_.check(pb.PBConnectionLost):
log.error(
f"Error while sending signal {signal_name}: {failure_}",
)
)
def sendSignal(self, name, args, kwargs):
to_remove = []
for handler in self.signals_handlers:
for wrapper in self.signals_handlers:
handler = wrapper.handler
try:
d = handler.callRemote(name, *args, **kwargs)
except pb.DeadReferenceError:
to_remove.append(handler)
to_remove.append(wrapper)
else:
d.addErrback(self.sendSignalEb, name)
if to_remove:
for handler in to_remove:
for wrapper in to_remove:
log.debug("Removing signal handler for dead frontend")
self.signals_handlers.remove(handler)
self.signals_handlers.remove(wrapper)
def _bridgeDeactivateSignals(self):
if hasattr(self, "signals_paused"):

View file

@ -17,12 +17,16 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from sat.core.log import getLogger
import asyncio
from functools import partial
from twisted.spread import pb
from twisted.internet import reactor, defer
from twisted.internet.error import ConnectionRefusedError
from logging import getLogger
from sat.core import exceptions
from sat_frontends.bridge.bridge_frontend import BridgeException
log = getLogger(__name__)
from sat.core import exceptions
from twisted.spread import pb
from twisted.internet import reactor
class SignalsHandler(pb.Referenceable):
@ -38,7 +42,7 @@ class SignalsHandler(pb.Referenceable):
log.debug("registering signal {name}".format(name=name))
method_name = "remote_" + name
try:
self.__getattribute__(self, method_name)
self.__getattribute__(method_name)
except AttributeError:
pass
else:
@ -51,11 +55,12 @@ class SignalsHandler(pb.Referenceable):
class Bridge(object):
def __init__(self):
self.signals_handler = SignalsHandler()
def __getattr__(self, name):
return lambda *args, **kwargs: self.call(name, args, kwargs)
return partial(self.call, name)
def remoteCallback(self, result, callback):
"""call callback with argument or None
@ -70,7 +75,7 @@ class Bridge(object):
else:
callback(result)
def call(self, name, args, kwargs):
def call(self, name, *args, **kwargs):
"""call a remote method
@param name(str): name of the bridge method
@ -98,8 +103,9 @@ class Bridge(object):
if errback is not None:
d.addErrback(errback)
def _initBridgeEb(self, failure):
log.error("Can't init bridge: {msg}".format(msg=failure))
def _initBridgeEb(self, failure_):
log.error("Can't init bridge: {msg}".format(msg=failure_))
return failure_
def _set_root(self, root):
"""set remote root object
@ -111,19 +117,57 @@ class Bridge(object):
d.addErrback(self._initBridgeEb)
return d
def _generic_errback(self, failure):
log.error("bridge failure: {}".format(failure))
def getRootObjectEb(self, failure_):
"""Call errback with appropriate bridge error"""
if failure_.check(ConnectionRefusedError):
raise exceptions.BridgeExceptionNoService
else:
raise failure_
def bridgeConnect(self, callback, errback):
factory = pb.PBClientFactory()
reactor.connectTCP("localhost", 8789, factory)
d = factory.getRootObject()
d.addCallback(self._set_root)
d.addCallback(lambda __: callback())
d.addErrback(errback)
if callback is not None:
d.addCallback(lambda __: callback())
d.addErrback(self.getRootObjectEb)
if errback is not None:
d.addErrback(lambda failure_: errback(failure_.value))
return d
def register_signal(self, functionName, handler, iface="core"):
self.signals_handler.register_signal(functionName, handler, iface)
##METHODS_PART##
class AIOSignalsHandler(SignalsHandler):
def register_signal(self, name, handler, iface="core"):
async_handler = lambda *args, **kwargs: defer.Deferred.fromFuture(
asyncio.ensure_future(handler(*args, **kwargs)))
return super().register_signal(name, async_handler, iface)
class AIOBridge(Bridge):
def __init__(self):
self.signals_handler = AIOSignalsHandler()
def _errback(self, failure_):
raise BridgeException(
name=failure_.type.decode('utf-8'),
message=str(failure_.value)
)
def call(self, name, *args, **kwargs):
d = self.root.callRemote(name, *args, *kwargs)
d.addErrback(self._errback)
return d.asFuture(asyncio.get_event_loop())
async def bridgeConnect(self):
d = super().bridgeConnect(callback=None, errback=None)
return await d.asFuture(asyncio.get_event_loop())
##ASYNC_METHODS_PART##