Initial idea-notes dump and @singleton factory idea from `trio`-gitter
parent
5c2e972315
commit
9eb7741822
|
@ -0,0 +1,132 @@
|
||||||
|
# tractor: structured concurrent "actors".
|
||||||
|
# Copyright 2024-eternity Tyler Goodlet.
|
||||||
|
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
'''
|
||||||
|
Daemon subactor as service(s) management and supervision primitives
|
||||||
|
and API.
|
||||||
|
|
||||||
|
'''
|
||||||
|
from __future__ import annotations
|
||||||
|
from contextlib import (
|
||||||
|
# asynccontextmanager as acm,
|
||||||
|
contextmanager as cm,
|
||||||
|
)
|
||||||
|
from collections import defaultdict
|
||||||
|
from typing import (
|
||||||
|
Callable,
|
||||||
|
Any,
|
||||||
|
)
|
||||||
|
|
||||||
|
import trio
|
||||||
|
from trio import TaskStatus
|
||||||
|
from tractor import (
|
||||||
|
ActorNursery,
|
||||||
|
current_actor,
|
||||||
|
ContextCancelled,
|
||||||
|
Context,
|
||||||
|
Portal,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ._util import (
|
||||||
|
log, # sub-sys logger
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: implement a `@singleton` deco-API for wrapping the below
|
||||||
|
# factory's impl for general actor-singleton use?
|
||||||
|
#
|
||||||
|
# -[ ] go through the options peeps on SO did?
|
||||||
|
# * https://stackoverflow.com/questions/6760685/what-is-the-best-way-of-implementing-singleton-in-python
|
||||||
|
# * including @mikenerone's answer
|
||||||
|
# |_https://stackoverflow.com/questions/6760685/what-is-the-best-way-of-implementing-singleton-in-python/39186313#39186313
|
||||||
|
#
|
||||||
|
# -[ ] put it in `tractor.lowlevel._globals` ?
|
||||||
|
# * fits with our oustanding actor-local/global feat req?
|
||||||
|
# |_ https://github.com/goodboy/tractor/issues/55
|
||||||
|
# * how can it relate to the `Actor.lifetime_stack` that was
|
||||||
|
# silently patched in?
|
||||||
|
# |_ we could implicitly call both of these in the same
|
||||||
|
# spot in the runtime using the lifetime stack?
|
||||||
|
# - `open_singleton_cm().__exit__()`
|
||||||
|
# -`del_singleton()`
|
||||||
|
# |_ gives SC fixtue semantics to sync code oriented around
|
||||||
|
# sub-process lifetime?
|
||||||
|
# * what about with `trio.RunVar`?
|
||||||
|
# |_https://trio.readthedocs.io/en/stable/reference-lowlevel.html#trio.lowlevel.RunVar
|
||||||
|
# - which we'll need for no-GIL cpython (right?) presuming
|
||||||
|
# multiple `trio.run()` calls in process?
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# @singleton
|
||||||
|
# async def open_service_mngr(
|
||||||
|
# **init_kwargs,
|
||||||
|
# ) -> ServiceMngr:
|
||||||
|
# '''
|
||||||
|
# Note this function body is invoke IFF no existing singleton instance already
|
||||||
|
# exists in this proc's memory.
|
||||||
|
|
||||||
|
# '''
|
||||||
|
# # setup
|
||||||
|
# yield ServiceMngr(**init_kwargs)
|
||||||
|
# # teardown
|
||||||
|
|
||||||
|
|
||||||
|
# a deletion API for explicit instance de-allocation?
|
||||||
|
# @open_service_mngr.deleter
|
||||||
|
# def del_service_mngr() -> None:
|
||||||
|
# mngr = open_service_mngr._singleton[0]
|
||||||
|
# open_service_mngr._singleton[0] = None
|
||||||
|
# del mngr
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: singleton factory API instead of a class API
|
||||||
|
@cm
|
||||||
|
def open_service_mngr(
|
||||||
|
*,
|
||||||
|
_singleton: list[ServiceMngr|None] = [None],
|
||||||
|
# NOTE; since default values for keyword-args are effectively
|
||||||
|
# module-vars/globals as per the note from,
|
||||||
|
# https://docs.python.org/3/tutorial/controlflow.html#default-argument-values
|
||||||
|
#
|
||||||
|
# > "The default value is evaluated only once. This makes
|
||||||
|
# a difference when the default is a mutable object such as
|
||||||
|
# a list, dictionary, or instances of most classes"
|
||||||
|
#
|
||||||
|
**init_kwargs,
|
||||||
|
|
||||||
|
) -> ServiceMngr:
|
||||||
|
'''
|
||||||
|
Open a multi-subactor-as-service-daemon tree supervisor.
|
||||||
|
|
||||||
|
The delivered `ServiceMngr` is a singleton instance for each
|
||||||
|
actor-process and is allocated on first open and never
|
||||||
|
de-allocated unless explicitly deleted by al call to
|
||||||
|
`del_service_mngr()`.
|
||||||
|
|
||||||
|
'''
|
||||||
|
mngr: ServiceMngr|None
|
||||||
|
if (mngr := _singleton[0]) is None:
|
||||||
|
log.info('Allocating a new service mngr!')
|
||||||
|
mngr = _singleton[0] = ServiceMngr(**init_kwargs)
|
||||||
|
else:
|
||||||
|
log.info(
|
||||||
|
'Using extant service mngr!\n\n'
|
||||||
|
f'{mngr!r}\n' # it has a nice `.__repr__()` of services state
|
||||||
|
)
|
||||||
|
|
||||||
|
with mngr:
|
||||||
|
yield mngr
|
Loading…
Reference in New Issue