ask / mode

Python AsyncIO Services

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Services should be singletons

panasenco opened this issue · comments

Checklist

  • I have included information about relevant versions
  • I have verified that the issue persists when using the master branch of Mode.

Steps to reproduce

Create a service with two child dependencies, each of which depends on the same root dependency:

from typing import List

from mode import Service
from mode.utils.objects import cached_property

class RootService(Service):
    async def on_start(self) -> None:
        print('Starting root!')

class Child1(Service):
    def on_init_dependencies(self) -> List:
        return [self.root]
    
    @cached_property
    def root(self) -> RootService:
        return RootService()
    
    async def on_start(self) -> None:
        print('Starting child 1')
    
class Child2(Service):
    def on_init_dependencies(self) -> List:
        return [self.root]
    
    @cached_property
    def root(self) -> RootService:
        return RootService()
    
    async def on_start(self) -> None:
        print('Starting child 2')

class App(Service):
    def on_init_dependencies(self) -> List:
        return [self.child1, self.child2]
    
    @cached_property
    def child1(self) -> Child1:
        return Child1()
    
    @cached_property
    def child2(self) -> Child2:
        return Child2()
    
    async def on_start(self) -> None:
        print('Starting app')

if __name__ == '__main__':
    from mode import Worker
    Worker(App(), loglevel='info').execute_from_commandline()

Expected behavior

Expected the root service to only be started once.

Actual behavior

The root service was started twice.

Full traceback

[2020-10-15 15:25:05,021] [20096] [INFO] [^Worker]: Starting...
[2020-10-15 15:25:05,023] [20096] [INFO] [^-App]: Starting...
[2020-10-15 15:25:05,024] [20096] [WARNING] Starting app
[2020-10-15 15:25:05,025] [20096] [INFO] [^--Child1]: Starting...
[2020-10-15 15:25:05,026] [20096] [WARNING] Starting child 1
[2020-10-15 15:25:05,027] [20096] [INFO] [^---RootService]: Starting...
[2020-10-15 15:25:05,028] [20096] [WARNING] Starting root!
[2020-10-15 15:25:05,029] [20096] [INFO] [^--Child2]: Starting...
[2020-10-15 15:25:05,030] [20096] [WARNING] Starting child 2
[2020-10-15 15:25:05,031] [20096] [INFO] [^---RootService]: Starting...
[2020-10-15 15:25:05,032] [20096] [WARNING] Starting root!

Versions

  • Python version: 3.8.3
  • Mode version: 4.3.2 (master branch)
  • Operating system: Windows 10

It's absolutely critical sometimes for a service to only be started once, for example if a service needs to lock a resource to operate.

Is there any way to ensure a service is started exactly once in mode?

Thanks!

Aram

I figured out a better way to do what I want with dependency injection.

A service should accept any dependencies that must not be duplicated as optional parameters at initialization time:

from typing import List

from mode import Service
from mode.utils.objects import cached_property

class RootService(Service):
    async def on_start(self) -> None:
        print('Starting root!')

class Child1(Service):
    def __init__(self, root: RootService = None) -> None:
        super().__init__()
        if root: self.root = root
    
    def on_init_dependencies(self) -> List:
        return [self.root]
    
    @cached_property
    def root(self) -> RootService:
        return RootService()
    
    async def on_start(self) -> None:
        print('Starting child 1')
    
class Child2(Service):
    def __init__(self, root: RootService = None) -> None:
        super().__init__()
        if root: self.root = root

    def on_init_dependencies(self) -> List:
        return [self.root]
    
    @cached_property
    def root(self) -> RootService:
        return RootService()
    
    async def on_start(self) -> None:
        print('Starting child 2')

class App(Service):
    def on_init_dependencies(self) -> List:
        return [self.root, self.child1, self.child2]
    
    @cached_property
    def root(self) -> RootService:
        return RootService()
    
    @cached_property
    def child1(self) -> Child1:
        return Child1(self.root)
    
    @cached_property
    def child2(self) -> Child2:
        return Child2(self.root)
    
    async def on_start(self) -> None:
        print('Starting app')

if __name__ == '__main__':
    from mode import Worker
    Worker(App(), loglevel='info').execute_from_commandline()

New Traceback:

[2020-10-20 10:36:30,102] [10136] [INFO] [^Worker]: Starting...
[2020-10-20 10:36:30,104] [10136] [INFO] [^-App]: Starting...
[2020-10-20 10:36:30,105] [10136] [WARNING] Starting app
[2020-10-20 10:36:30,106] [10136] [INFO] [^--RootService]: Starting...
[2020-10-20 10:36:30,107] [10136] [WARNING] Starting root!
[2020-10-20 10:36:30,108] [10136] [INFO] [^--Child1]: Starting...
[2020-10-20 10:36:30,109] [10136] [WARNING] Starting child 1
[2020-10-20 10:36:30,111] [10136] [INFO] [^--Child2]: Starting...
[2020-10-20 10:36:30,112] [10136] [WARNING] Starting child 2
[2020-10-20 10:36:34,335] [10136] [INFO] [^Worker]: Stopping...
[2020-10-20 10:36:34,338] [10136] [INFO] [^-App]: Stopping...
[2020-10-20 10:36:34,340] [10136] [INFO] [^--Child2]: Stopping...
[2020-10-20 10:36:34,341] [10136] [INFO] [^---RootService]: Stopping...
[2020-10-20 10:36:34,343] [10136] [INFO] [^--Child1]: Stopping...
[2020-10-20 10:36:34,344] [10136] [INFO] [^Worker]: Gathering service tasks...
[2020-10-20 10:36:34,346] [10136] [INFO] [^Worker]: Gathering all futures...
[2020-10-20 10:36:36,357] [10136] [INFO] [^Worker]: Closing event loop