Services should be singletons
panasenco opened this issue · comments
Aram Panasenco commented
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
Aram Panasenco commented
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