def __init__(self, name, loop=asyncio.get_event_loop(), address=None, mailbox_size=1000, inbox=None): """ The constructor :Keyword Arguments: *publisher (BaseActor): The publishing actor *router (BaseActor): a router for subscribers """ super().__init__(name, loop, address, mailbox_size, inbox) self._subscription_router = RoundRobinRouter() if self._subscription_router.get_state() is ActorState.LIMBO: self._subscription_router.start() if self._subscription_router.get_state() is not ActorState.RUNNING: raise ActorStateError("Router not Started in PubSub") self.register_handler(Subscribe, self.subscribe) self.register_handler(Publish, self.do_publish) self.register_handler(DeSubscribe, self.desubscribe)
def test__round_robin_tell_at_load(): print("Load Testing With Tell") sys = ActorSystem("tests") rr = RoundRobinRouter("test_router") rr.start() rr.set_actor_system(sys, "tests") actors = [] async def say_hello(i): await rr.route_tell(StringMessage("Hello {}".format(i))) async def comp(funcs): await asyncio.gather(*funcs) print("Starting Actor") for i in range(0, 100): a = StringTestActor() a.start() rr.add_actor(a) actors.append(a) print("Start Adding") funcs = [say_hello(i) for i in range(0, len(actors))] print("Waiting") asyncio.get_event_loop().run_until_complete(comp(funcs)) sys.close() del sys gc.collect() del gc.garbage[:] assert(rr.get_state() is ActorState.TERMINATED), "Router Not Terminated" print("Load Testing With Tell Complete")
def test_round_robin_at_load(): print("Load Testing") sys = ActorSystem("tests") rr = RoundRobinRouter("test_router") rr.start() rr.set_actor_system(sys, "tests") actors = [] async def get_addition(): res = await rr.route_ask(AddIntMessage(1)) return res async def get_sum(funcs): res = await asyncio.gather(*funcs) return sum(res) / 2 print("Starting Actor") for i in range(0, 100): a = AddTestActor("testa") a.start() rr.add_actor(a) actors.append(a) print("Start Adding") funcs = [get_addition() for x in actors] print("Waiting") res = asyncio.get_event_loop().run_until_complete(get_sum(funcs)) print(res) assert(int(res) is len(funcs)), "Result {} is not {}".format(res, len(funcs)) sys.close() assert(rr.get_state() is ActorState.TERMINATED), "Router Not Terminated" print("Done Load Testing")
def test_creation(): print("Create Router Test") sys = ActorSystem("tests") kwargs = {'name': 'test_router'} args = [] rr = RoundRobinRouter(*args, **kwargs) rr.start() assert (rr.get_name() == 'test_router') rr.set_actor_system(sys, "tests") sys.close() assert (rr.get_state() == ActorState.TERMINATED), "Router Not Terminated" print("Completed Router Creation Test")
def create_router(self, concurrency): """ Create the initial router with existing actors. :param concurrency: Number of routers to start :type concurrency: int() """ if self.router is None: self.router = RoundRobinRouter() self.router.start() for i in range(0, concurrency): try: pyname = __name__ router_name = "{}_{}".format(pyname, i) ta = TaskActor(name=router_name, on_call=self.on_pull, loop=self.loop) ta.start() self.router.add_actor(ta) except Exception: self.handle_fail()
class NodePubSub(PubSub): """ A standard graph stage pub sub. This pub sub receives pull requests which wait on a push to then complete a task and send back to the sender. Non-blocking io should assure maximal concurrency. """ def __init__(self, name, providers=[], loop=asyncio.get_event_loop(), address=None, mailbox_size=1000, inbox=None, empty_demand_logic = "broadcast", concurrency=cpu_count(), tick_delay=120): """ Constructor :param name: Name of the actor :type name: str() :param loop: Asyncio loop for the actor :type loop: AbstractEventLoop() :param address: Address for the actor :type address: str() :param mailbox_size: Size of the mailbox :type mailbox_size: int) :param inbox: Actor inbox :type inbox: asyncio.Queue() :param empty_demand_logic: round_robin or broadcast :type empty_demand_logic: str() :param concurrency: Number concurrent tasks to run :type concurrency: int() """ super().__init__(name, loop, address, mailbox_size, inbox) self.register_handler(Tick, self.__pull_tick) self.subscribers = [] self.__providers = providers self.__current_provider = 0 self.__task_q = PyQueue() self.__empty_logic = empty_demand_logic self.__result_q = PyQueue() self.router = None self.create_router(concurrency) self.set_handlers() self.tick_delay = tick_delay self.__concurrency = concurrency self.__pull_tick() def set_handlers(self): """ Set the handlers for the actor """ self.register_handler(Publish, self.__push) self.register_handler(Push, self.__push) self.register_handler(Pull, self.__pull) self.register_handler(Tick, self.__do_pull_tick) self.register_handler(DeSubscribe, self.__de_subscribe_upstream) self.register_handler(Subscribe, self.__subscribe_upstream) self.register_handler(TaskMessage, self.__append_result) def create_router(self, concurrency): """ Create the initial router with existing actors. :param concurrency: Number of routers to start :type concurrency: int() """ if self.router is None: self.router = RoundRobinRouter() self.router.start() for i in range(0, concurrency): try: pyname = __name__ router_name = "{}_{}".format(pyname, i) ta = TaskActor(name=router_name, on_call=self.on_pull, loop=self.loop) ta.start() self.router.add_actor(ta) except Exception: self.handle_fail() def start(self): """ Start the actor """ super().start() if len(self.__providers) > 0: for i in range(0, self.__concurrency): if self.__empty_logic == "broadcast": for provider in self.__providers: self.loop.run_until_complete( self.tell(provider, Pull(None, self))) else: if len(self.__providers) > i: if i < len(self.__providers): lprv = len(self.__providers) if self.__current_provider >= lprv: self.__current_provider = 0 prov = self.__providers[self.__current_provider] self.loop.run_until_complete( self.tell(prov, Pull(None, self))) self.__current_provider += 1 def call_tick(self): """ Perform a tick call. """ def handle_tick(): """ Handle the tick """ try: asyncio.run_coroutine_threadsafe( self.tell(self, Tick(None, self)), loop=self.loop) except Exception: self.handle_fail() try: self.loop.call_later( delay = self.tick_delay, callback=handle_tick) except Exception: self.handle_fail() async def __do_pull_tick(self, message=None): """ Do a pull tick :param message: The message for the pull tick :type message: Message() """ try: if self.__task_q.full() is False: if self.__providers and len(self.__providers) > 0: for provider in self.__providers: if isinstance(provider, AbstractActor): if provider.get_state() != ActorState.RUNNING: self.__providers.remove(provider) if len(self.__providers) > 0: sender = self.__providers[self.__current_provider] await self.tell(sender,Pull(None, self)) self.__current_provider += 1 if self.__current_provider >= len(self.__providers): self.__current_provider = 0 else: self.__current_provider = 0 else: self.__current_provider = 0 except Exception: self.handle_fail() try: if self.get_state() != ActorState.TERMINATED: self.call_tick() except Exception: self.handle_fail() def __pull_tick(self): """ Perform a periodic pull. """ self.loop.run_until_complete(self.__do_pull_tick()) async def __subscribe_upstream(self, message): """ Subscribes to an the upstream publisher :param message: The provided Subscribe message :type message: Subscribe() """ payload = message.payload if isinstance(payload, AbstractActor): if payload not in self.subscribers: self.subscribers.append(payload) else: msg = "Can Only Subscribe Object of Abstract Actor to StreamPubSub" logging.error(msg) async def __de_subscribe_upstream(self, message): """ De-subscribe from the upstream actors. :param message: The DeSubscribe message :type message: DeSubscribe() """ try: if isinstance(message, DeSubscribe): actor = message.payload if isinstance(actor, AbstractActor): if actor in self.subscribers: self.subscribers.remove(actor) if self.__current_provider >= len(self.subscribers): self.__current_provider = 0 except Exception: self.handle_fail() async def __append_result(self, message): """ Append an incoming result to the result queue. :param message: The incoming message :type message: Message() """ try: if isinstance(message, TaskMessage): payload = message.payload self.__result_q.put_nowait(payload) except Exception: self.handle_fail() async def __pull(self, message): """ The pull message. :param message: The Pull message :type message: Pull() """ try: await self.__signal_provider() if isinstance(message, Pull): if self.__task_q.empty(): self.run_on_empty() task = self.__task_q.get() if task: if isinstance(task, Push): task = task.payload await self.tell( self.router, RouteTell(TaskMessage(task, message.sender, self))) except Exception: self.handle_fail() async def __signal_provider(self): """ Signal the provider. """ try: prov = self.__providers[self.__current_provider] await self.tell(prov, Pull(None, self)) except Exception as e: self.handle_fail() self.__current_provider += 1 if self.__current_provider >= len(self.__providers): self.__current_provider = 0 def on_pull(self, message): """ User implemented function to handle incoming methods. """ logging.error("Should Override Push Function") return None async def __push(self, message): """ The push function. """ if isinstance(message, Publish): self.__task_q.put_nowait(message.payload)
def test_round_robin_broadcast(): print("Testing Broadcast") sys = ActorSystem("tests") rr = RoundRobinRouter("test_router") rr.start() rr.set_actor_system(sys, "tests") a = StringTestActor() a.start() rr.add_actor(a) b = StringTestActor() b.start() rr.add_actor(b) asyncio.get_event_loop().run_until_complete( rr.broadcast(StringMessage("Hello World!"))) msg = "Actors Missing. Length {}".format(rr.get_num_actors()) assert(rr.get_num_actors() is 2), msg asyncio.get_event_loop().run_until_complete( rr.route_tell(StringMessage("Hello World"))) sys.close() assert(a.get_state() is ActorState.TERMINATED), "Actor a Not Terminated" assert(b.get_state() is ActorState.TERMINATED), " Actor b Not Terminated" assert(rr.get_state() is ActorState.TERMINATED), "Router Not Terminated" print("Finished Testing Broadcast")
def test_round_robin_arithmetic(): print("Testing multiplication") a_sys = ActorSystem("tests") rr = RoundRobinRouter("test_router") rr.start() rr.set_actor_system(a_sys, "tests") a = AddTestActor("testa") a.start() rr.add_actor(a) b = AddTestActor("testb") b.start() rr.add_actor(b) async def get_addition(): res = await rr.route_ask(AddIntMessage(1)) return res res = asyncio.get_event_loop().run_until_complete(get_addition()) print(rr.get_current_index()) assert(res is 2), "Addition Not Completed" msg = "Actors Missing. Length {}".format(rr.get_num_actors()) assert(rr.get_num_actors() is 2), msg a_sys.close() assert(a.get_state() is ActorState.TERMINATED), "Actor a Not Terminated" assert(b.get_state(), ActorState.TERMINATED)," Actor b Not Terminated" assert(rr.get_state(), ActorState.TERMINATED), "Router Not Terminated" print("Done Testing Multiplication")
def test_round_robin_actor_addition(): print("Starting Actor Addition Test") sys = ActorSystem("tests") rr = RoundRobinRouter("test_router") rr.start() rr.set_actor_system(sys, "tests") a = BaseActor("testa") a.start() rr.add_actor(a) b = BaseActor("testb") b.start() rr.add_actor(b) msg = "Actors Missing. Length {}".format(rr.get_num_actors()) assert(rr.get_num_actors() == 2), msg rr.remove_actor(a) assert(rr.get_num_actors() == 1), "Number of Actors Should be 1" asyncio.get_event_loop().run_until_complete(a.stop()) sys.close() assert(a.get_state() == ActorState.TERMINATED), "Actor a Not Terminated" assert(b.get_state() == ActorState.TERMINATED), " Actor b Not Terminated" assert(rr.get_state() == ActorState.TERMINATED), "Router Not Terminated" print("Actor Addition Test Complete")
class PubSub(BaseActor): """ Special Publisher/Subscriber for streaming """ def __init__(self, name, loop=asyncio.get_event_loop(), address=None, mailbox_size=1000, inbox=None): """ The constructor :Keyword Arguments: *publisher (BaseActor): The publishing actor *router (BaseActor): a router for subscribers """ super().__init__(name, loop, address, mailbox_size, inbox) self._subscription_router = RoundRobinRouter() if self._subscription_router.get_state() is ActorState.LIMBO: self._subscription_router.start() if self._subscription_router.get_state() is not ActorState.RUNNING: raise ActorStateError("Router not Started in PubSub") self.register_handler(Subscribe, self.subscribe) self.register_handler(Publish, self.do_publish) self.register_handler(DeSubscribe, self.desubscribe) def subscribe(self, actor): """ Subscribe to the pub/sub. This will replace the subscriber queue to create a balancing router. """ try: self._subscription_router.add_actor(actor) except Exception: self.handle_fail() async def desubscribe(self, message): try: await self.tell(self._subscription_router, message) except Exception: self.handle_fail() def broadcast(self, message): """ Send a broadcast to all members of the publishers router """ try: self.loop.run_until_complete( self.tell(self._subscription_router, RouteBroadcast(message))) except Exception: self.handle_fail() async def do_publish(self, message): """ Submit a message to a chosen actor in the subscribers """ try: await self.tell(self._subscription_router, RouteTell(message.payload)) except Exception: self.handle_fail() async def handle_broadcast(self, message): """ Broadcast to all actors. Do not connect the actors subscribing here to a broadcast router. """ try: for actor in self._subscribers: await self.tell(actor, message) except Exception: self.handle_fail()