Repository: pohmelie/facet (language: python)

Issue:
Порядок остановки сервисов
@bizywizy @ridhid @Dandi91 @squaredbeaver
В данный момент если A зависит от B и C, а B зависит от C. То при остановке A будет остановлен C и если B что-то хочет делать с C в стопе, то будет фейл. Нужно ли это исправлять? Какие идеи на этот счёт?


Discussion / hints:
Да, я думаю стоит считать ссылки на сервис или типа того. 
У меня был кейс, когда я запускал N сервисов, которые зависели от БД. После остановки первого был неприятно удивлен, что он убил коннект к БД 😲
Обходился так:
```
async with AsyncExitStack() as stack:
    for service in services:
        await stack.enter_async_context(service)
    await asyncio.gather(*(service.process() for service in services))
```
Конечно, не такое не особо вписывается вообще в паттерн использования facet, но хотелось бы чтобы оно работало.

Fix patch:
diff --git a/facet.py b/facet.py
index ed9f7fc..3edb680 100644
--- a/facet.py
+++ b/facet.py
@@ -17,6 +17,7 @@ class ServiceMixin:
     __start_lock = None
     __stop_lock = None
     __exit_point = None
+    __dependents = 0
 
     async def __wait_with_cancellation_on_fail(self, tasks):
         try:
@@ -54,6 +55,7 @@ async def __start(self):
         if self.__start_lock is None:
             self.__start_lock = Lock()
         async with self.__start_lock:
+            self.__dependents += 1
             if self.__running:
                 return
             if self.__exit_point is None:
@@ -71,7 +73,8 @@ async def __stop(self):
         if self.__stop_lock is None:
             self.__stop_lock = Lock()
         async with self.__stop_lock:
-            if self.__running:
+            self.__dependents -= 1
+            if self.__dependents == 0 and self.__running:
                 await wait_for(self.__stop_two_steps(), timeout=self.graceful_shutdown_timeout)
 
     async def __stop_two_steps(self):


Test patch:
diff --git a/test.py b/test.py
index 772f352..3fdb36b 100644
--- a/test.py
+++ b/test.py
@@ -102,6 +102,15 @@ async def test_diamond_dependencies():
     assert a.c.stop_count == 1
 
 
+@pytest.mark.asyncio
+async def test_c_does_not_stops():
+    async with A() as a:
+        async with B(a.c):
+            pass
+        assert a.c.running is True
+    assert a.c.running is False
+
+
 class WithTask(ServiceMixin):
 
     def __init__(self):