omserv 0.0.0.dev7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- omserv/__about__.py +28 -0
- omserv/__init__.py +0 -0
- omserv/apps/__init__.py +0 -0
- omserv/apps/base.py +23 -0
- omserv/apps/inject.py +89 -0
- omserv/apps/markers.py +41 -0
- omserv/apps/routes.py +139 -0
- omserv/apps/sessions.py +57 -0
- omserv/apps/templates.py +90 -0
- omserv/dbs.py +24 -0
- omserv/node/__init__.py +0 -0
- omserv/node/models.py +53 -0
- omserv/node/registry.py +124 -0
- omserv/node/sql.py +131 -0
- omserv/secrets.py +12 -0
- omserv/server/__init__.py +18 -0
- omserv/server/config.py +51 -0
- omserv/server/debug.py +14 -0
- omserv/server/events.py +83 -0
- omserv/server/headers.py +36 -0
- omserv/server/lifespans.py +132 -0
- omserv/server/multiprocess.py +157 -0
- omserv/server/protocols/__init__.py +1 -0
- omserv/server/protocols/h11.py +334 -0
- omserv/server/protocols/h2.py +407 -0
- omserv/server/protocols/protocols.py +91 -0
- omserv/server/protocols/types.py +18 -0
- omserv/server/resources/__init__.py +8 -0
- omserv/server/sockets.py +111 -0
- omserv/server/ssl.py +47 -0
- omserv/server/streams/__init__.py +0 -0
- omserv/server/streams/httpstream.py +237 -0
- omserv/server/streams/utils.py +53 -0
- omserv/server/streams/wsstream.py +447 -0
- omserv/server/taskspawner.py +111 -0
- omserv/server/tcpserver.py +173 -0
- omserv/server/types.py +94 -0
- omserv/server/workercontext.py +52 -0
- omserv/server/workers.py +193 -0
- omserv-0.0.0.dev7.dist-info/LICENSE +21 -0
- omserv-0.0.0.dev7.dist-info/METADATA +21 -0
- omserv-0.0.0.dev7.dist-info/RECORD +44 -0
- omserv-0.0.0.dev7.dist-info/WHEEL +5 -0
- omserv-0.0.0.dev7.dist-info/top_level.txt +1 -0
@@ -0,0 +1,173 @@
|
|
1
|
+
import errno
|
2
|
+
import logging
|
3
|
+
import math
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
import anyio.abc
|
7
|
+
|
8
|
+
from omlish import check
|
9
|
+
|
10
|
+
from .config import Config
|
11
|
+
from .events import Closed
|
12
|
+
from .events import RawData
|
13
|
+
from .events import ServerEvent
|
14
|
+
from .events import Updated
|
15
|
+
from .protocols import ProtocolWrapper
|
16
|
+
from .sockets import parse_socket_addr
|
17
|
+
from .taskspawner import TaskSpawner
|
18
|
+
from .types import AppWrapper
|
19
|
+
from .workercontext import WorkerContext
|
20
|
+
|
21
|
+
|
22
|
+
log = logging.getLogger(__name__)
|
23
|
+
|
24
|
+
|
25
|
+
MAX_RECV = 2 ** 16
|
26
|
+
|
27
|
+
|
28
|
+
class TcpServer:
|
29
|
+
def __init__(
|
30
|
+
self,
|
31
|
+
app: AppWrapper,
|
32
|
+
config: Config,
|
33
|
+
context: WorkerContext,
|
34
|
+
stream: anyio.abc.SocketStream,
|
35
|
+
) -> None:
|
36
|
+
super().__init__()
|
37
|
+
|
38
|
+
self.app = app
|
39
|
+
self.config = config
|
40
|
+
self.context = context
|
41
|
+
self.protocol: ProtocolWrapper
|
42
|
+
self.send_lock = anyio.Lock()
|
43
|
+
self.idle_lock = anyio.Lock()
|
44
|
+
self.stream = stream
|
45
|
+
|
46
|
+
self._idle_handle: anyio.abc.CancelScope | None = None
|
47
|
+
self._task_spawner: TaskSpawner | None = None
|
48
|
+
|
49
|
+
def __await__(self) -> ta.Generator[ta.Any, None, None]:
|
50
|
+
return self.run().__await__()
|
51
|
+
|
52
|
+
async def run(self) -> None:
|
53
|
+
socket = self.stream._raw_socket # noqa
|
54
|
+
|
55
|
+
try:
|
56
|
+
client = parse_socket_addr(socket.family, socket.getpeername())
|
57
|
+
server = parse_socket_addr(socket.family, socket.getsockname())
|
58
|
+
|
59
|
+
async with TaskSpawner() as task_spawner:
|
60
|
+
self._task_spawner = task_spawner
|
61
|
+
|
62
|
+
self.protocol = ProtocolWrapper(
|
63
|
+
self.app,
|
64
|
+
self.config,
|
65
|
+
self.context,
|
66
|
+
task_spawner,
|
67
|
+
client,
|
68
|
+
server,
|
69
|
+
self.protocol_send,
|
70
|
+
)
|
71
|
+
|
72
|
+
await self.protocol.initiate()
|
73
|
+
await self._start_idle()
|
74
|
+
await self._read_data()
|
75
|
+
|
76
|
+
except* OSError:
|
77
|
+
pass
|
78
|
+
|
79
|
+
except* Exception as eg:
|
80
|
+
for e in eg.exceptions:
|
81
|
+
log.exception('Internal omlicorn error', exc_info=e)
|
82
|
+
|
83
|
+
finally:
|
84
|
+
await self._close()
|
85
|
+
|
86
|
+
async def protocol_send(self, event: ServerEvent) -> None:
|
87
|
+
if isinstance(event, RawData):
|
88
|
+
async with self.send_lock:
|
89
|
+
try:
|
90
|
+
with anyio.CancelScope() as cancel_scope:
|
91
|
+
cancel_scope.shield = True
|
92
|
+
await self.stream.send(event.data)
|
93
|
+
|
94
|
+
except (anyio.BrokenResourceError, anyio.ClosedResourceError):
|
95
|
+
await self.protocol.handle(Closed())
|
96
|
+
|
97
|
+
elif isinstance(event, Closed):
|
98
|
+
await self._close()
|
99
|
+
await self.protocol.handle(Closed())
|
100
|
+
|
101
|
+
elif isinstance(event, Updated):
|
102
|
+
if event.idle:
|
103
|
+
await self._start_idle()
|
104
|
+
else:
|
105
|
+
await self._stop_idle()
|
106
|
+
|
107
|
+
async def _read_data(self) -> None:
|
108
|
+
while True:
|
109
|
+
try:
|
110
|
+
with anyio.fail_after(self.config.read_timeout or math.inf):
|
111
|
+
data = await self.stream.receive(MAX_RECV)
|
112
|
+
|
113
|
+
except (
|
114
|
+
anyio.EndOfStream,
|
115
|
+
anyio.ClosedResourceError,
|
116
|
+
anyio.BrokenResourceError,
|
117
|
+
TimeoutError,
|
118
|
+
):
|
119
|
+
break
|
120
|
+
|
121
|
+
else:
|
122
|
+
await self.protocol.handle(RawData(data))
|
123
|
+
if data == b'':
|
124
|
+
break
|
125
|
+
|
126
|
+
await self.protocol.handle(Closed())
|
127
|
+
|
128
|
+
async def _close(self) -> None:
|
129
|
+
try:
|
130
|
+
await self.stream.send_eof()
|
131
|
+
|
132
|
+
except OSError as e:
|
133
|
+
if e.errno != errno.EBADF:
|
134
|
+
raise
|
135
|
+
|
136
|
+
except (
|
137
|
+
anyio.BrokenResourceError,
|
138
|
+
AttributeError,
|
139
|
+
anyio.BusyResourceError,
|
140
|
+
anyio.ClosedResourceError,
|
141
|
+
):
|
142
|
+
# They're already gone, nothing to do - or it is a SSL stream
|
143
|
+
pass
|
144
|
+
|
145
|
+
await self.stream.aclose()
|
146
|
+
|
147
|
+
async def _initiate_server_close(self) -> None:
|
148
|
+
await self.protocol.handle(Closed())
|
149
|
+
await self.stream.aclose()
|
150
|
+
|
151
|
+
async def _start_idle(self) -> None:
|
152
|
+
async with self.idle_lock:
|
153
|
+
if self._idle_handle is None:
|
154
|
+
self._idle_handle = await check.not_none(self._task_spawner).start(self._run_idle)
|
155
|
+
|
156
|
+
async def _stop_idle(self) -> None:
|
157
|
+
async with self.idle_lock:
|
158
|
+
if self._idle_handle is not None:
|
159
|
+
self._idle_handle.cancel()
|
160
|
+
self._idle_handle = None
|
161
|
+
|
162
|
+
async def _run_idle(
|
163
|
+
self,
|
164
|
+
task_status: anyio.abc.TaskStatus[ta.Any] = anyio.TASK_STATUS_IGNORED,
|
165
|
+
) -> None:
|
166
|
+
cancel_scope = anyio.CancelScope()
|
167
|
+
task_status.started(cancel_scope)
|
168
|
+
with cancel_scope:
|
169
|
+
with anyio.move_on_after(self.config.keep_alive_timeout):
|
170
|
+
await self.context.terminated.wait()
|
171
|
+
|
172
|
+
cancel_scope.shield = True
|
173
|
+
await self._initiate_server_close()
|
omserv/server/types.py
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
import typing as ta
|
2
|
+
|
3
|
+
|
4
|
+
##
|
5
|
+
|
6
|
+
|
7
|
+
AsgiReceiveEvent: ta.TypeAlias = dict[str, ta.Any]
|
8
|
+
AsgiSendEvent: ta.TypeAlias = dict[str, ta.Any]
|
9
|
+
|
10
|
+
AsgiReceiveCallable: ta.TypeAlias = ta.Callable[[], ta.Awaitable[AsgiReceiveEvent]]
|
11
|
+
AsgiSendCallable: ta.TypeAlias = ta.Callable[[AsgiSendEvent], ta.Awaitable[None]]
|
12
|
+
|
13
|
+
Scope: ta.TypeAlias = dict[str, ta.Any]
|
14
|
+
AsgiFramework: ta.TypeAlias = ta.Callable[
|
15
|
+
[
|
16
|
+
Scope,
|
17
|
+
AsgiReceiveCallable,
|
18
|
+
AsgiSendCallable,
|
19
|
+
],
|
20
|
+
ta.Awaitable[None],
|
21
|
+
]
|
22
|
+
|
23
|
+
LifespanScope: ta.TypeAlias = Scope
|
24
|
+
|
25
|
+
HttpResponseStartEvent: ta.TypeAlias = dict[str, ta.Any]
|
26
|
+
HttpScope: ta.TypeAlias = Scope
|
27
|
+
WebsocketScope: ta.TypeAlias = Scope
|
28
|
+
|
29
|
+
WebsocketAcceptEvent: ta.TypeAlias = dict[str, ta.Any]
|
30
|
+
WebsocketResponseBodyEvent: ta.TypeAlias = dict[str, ta.Any]
|
31
|
+
WebsocketResponseStartEvent: ta.TypeAlias = dict[str, ta.Any]
|
32
|
+
|
33
|
+
|
34
|
+
##
|
35
|
+
|
36
|
+
|
37
|
+
class UnexpectedMessageError(Exception):
|
38
|
+
pass
|
39
|
+
|
40
|
+
|
41
|
+
class AsgiWrapper:
|
42
|
+
def __init__(self, app: AsgiFramework) -> None:
|
43
|
+
super().__init__()
|
44
|
+
self.app = app
|
45
|
+
|
46
|
+
async def __call__(
|
47
|
+
self,
|
48
|
+
scope: Scope,
|
49
|
+
receive: AsgiReceiveCallable,
|
50
|
+
send: AsgiSendCallable,
|
51
|
+
sync_spawn: ta.Callable,
|
52
|
+
call_soon: ta.Callable,
|
53
|
+
) -> None:
|
54
|
+
await self.app(scope, receive, send)
|
55
|
+
|
56
|
+
|
57
|
+
class AppWrapper(ta.Protocol):
|
58
|
+
async def __call__(
|
59
|
+
self,
|
60
|
+
scope: Scope,
|
61
|
+
receive: AsgiReceiveCallable,
|
62
|
+
send: AsgiSendCallable,
|
63
|
+
sync_spawn: ta.Callable,
|
64
|
+
call_soon: ta.Callable,
|
65
|
+
) -> None:
|
66
|
+
pass
|
67
|
+
|
68
|
+
|
69
|
+
def wrap_app(
|
70
|
+
app: AsgiFramework | AppWrapper,
|
71
|
+
) -> AppWrapper:
|
72
|
+
if isinstance(app, AsgiWrapper):
|
73
|
+
return app
|
74
|
+
return AsgiWrapper(app) # type: ignore
|
75
|
+
|
76
|
+
|
77
|
+
##
|
78
|
+
|
79
|
+
|
80
|
+
class WaitableEvent(ta.Protocol):
|
81
|
+
def __init__(self) -> None:
|
82
|
+
pass
|
83
|
+
|
84
|
+
async def clear(self) -> None:
|
85
|
+
pass
|
86
|
+
|
87
|
+
async def set(self) -> None:
|
88
|
+
pass
|
89
|
+
|
90
|
+
async def wait(self) -> None:
|
91
|
+
pass
|
92
|
+
|
93
|
+
def is_set(self) -> bool:
|
94
|
+
pass
|
@@ -0,0 +1,52 @@
|
|
1
|
+
import anyio
|
2
|
+
|
3
|
+
from .types import WaitableEvent
|
4
|
+
|
5
|
+
|
6
|
+
class WaitableEventWrapper:
|
7
|
+
def __init__(self) -> None:
|
8
|
+
super().__init__()
|
9
|
+
self._event = anyio.Event()
|
10
|
+
|
11
|
+
async def clear(self) -> None:
|
12
|
+
self._event = anyio.Event()
|
13
|
+
|
14
|
+
async def wait(self) -> None:
|
15
|
+
await self._event.wait()
|
16
|
+
|
17
|
+
async def set(self) -> None:
|
18
|
+
self._event.set()
|
19
|
+
|
20
|
+
def is_set(self) -> bool:
|
21
|
+
return self._event.is_set()
|
22
|
+
|
23
|
+
|
24
|
+
class WorkerContext:
|
25
|
+
event_class: type[WaitableEvent] = WaitableEventWrapper
|
26
|
+
|
27
|
+
def __init__(self, max_requests: int | None) -> None:
|
28
|
+
super().__init__()
|
29
|
+
self.max_requests = max_requests
|
30
|
+
self.requests = 0
|
31
|
+
self.terminate = self.event_class()
|
32
|
+
self.terminated = self.event_class()
|
33
|
+
|
34
|
+
async def mark_request(self) -> None:
|
35
|
+
if self.max_requests is None:
|
36
|
+
return
|
37
|
+
|
38
|
+
self.requests += 1
|
39
|
+
if self.requests > self.max_requests:
|
40
|
+
await self.terminate.set()
|
41
|
+
|
42
|
+
@staticmethod
|
43
|
+
async def sleep(wait: float) -> None:
|
44
|
+
return await anyio.sleep(wait)
|
45
|
+
|
46
|
+
@staticmethod
|
47
|
+
def time() -> float:
|
48
|
+
return anyio.current_time()
|
49
|
+
|
50
|
+
|
51
|
+
class ShutdownError(Exception):
|
52
|
+
pass
|
omserv/server/workers.py
ADDED
@@ -0,0 +1,193 @@
|
|
1
|
+
import errno
|
2
|
+
import functools
|
3
|
+
import logging
|
4
|
+
import os
|
5
|
+
import random
|
6
|
+
import signal # noqa
|
7
|
+
import typing as ta
|
8
|
+
|
9
|
+
import anyio
|
10
|
+
import anyio.abc
|
11
|
+
|
12
|
+
from .config import Config
|
13
|
+
from .lifespans import Lifespan
|
14
|
+
from .sockets import Sockets
|
15
|
+
from .sockets import create_sockets
|
16
|
+
from .sockets import repr_socket_addr
|
17
|
+
from .tcpserver import TcpServer
|
18
|
+
from .types import AppWrapper
|
19
|
+
from .types import AsgiFramework
|
20
|
+
from .types import wrap_app
|
21
|
+
from .workercontext import ShutdownError
|
22
|
+
from .workercontext import WorkerContext
|
23
|
+
|
24
|
+
|
25
|
+
log = logging.getLogger(__name__)
|
26
|
+
|
27
|
+
|
28
|
+
async def raise_shutdown(shutdown_event: ta.Callable[..., ta.Awaitable]) -> None:
|
29
|
+
await shutdown_event()
|
30
|
+
raise ShutdownError
|
31
|
+
|
32
|
+
|
33
|
+
# Errors that accept(2) can return, and which indicate that the system is overloaded
|
34
|
+
ACCEPT_CAPACITY_ERRNOS = {
|
35
|
+
errno.EMFILE,
|
36
|
+
errno.ENFILE,
|
37
|
+
errno.ENOMEM,
|
38
|
+
errno.ENOBUFS,
|
39
|
+
}
|
40
|
+
|
41
|
+
|
42
|
+
async def _run_handler(
|
43
|
+
stream: anyio.abc.SocketStream,
|
44
|
+
handler: ta.Callable[[anyio.abc.SocketStream], ta.Awaitable],
|
45
|
+
) -> None:
|
46
|
+
try:
|
47
|
+
await handler(stream)
|
48
|
+
finally:
|
49
|
+
await anyio.aclose_forcefully(stream)
|
50
|
+
|
51
|
+
|
52
|
+
SLEEP_TIME = 0.100
|
53
|
+
|
54
|
+
|
55
|
+
async def _serve_one_listener(
|
56
|
+
listener: anyio.abc.SocketListener,
|
57
|
+
handler_task_group: anyio.abc.TaskGroup,
|
58
|
+
handler: ta.Callable[[anyio.abc.SocketStream], ta.Awaitable],
|
59
|
+
) -> ta.NoReturn:
|
60
|
+
async with listener:
|
61
|
+
while True:
|
62
|
+
try:
|
63
|
+
stream = await listener.accept()
|
64
|
+
except OSError as exc:
|
65
|
+
if exc.errno in ACCEPT_CAPACITY_ERRNOS:
|
66
|
+
log.exception(
|
67
|
+
'accept returned %s (%s); retrying in %s seconds',
|
68
|
+
errno.errorcode[exc.errno],
|
69
|
+
os.strerror(exc.errno),
|
70
|
+
SLEEP_TIME,
|
71
|
+
)
|
72
|
+
await anyio.sleep(SLEEP_TIME)
|
73
|
+
else:
|
74
|
+
raise
|
75
|
+
else:
|
76
|
+
handler_task_group.start_soon(_run_handler, stream, handler)
|
77
|
+
|
78
|
+
|
79
|
+
async def serve_listeners(
|
80
|
+
handler: ta.Callable[[anyio.abc.SocketStream], ta.Awaitable],
|
81
|
+
listeners: ta.Iterable[anyio.abc.SocketListener],
|
82
|
+
*,
|
83
|
+
handler_task_group: anyio.abc.TaskGroup | None = None,
|
84
|
+
task_status: anyio.abc.TaskStatus[ta.Iterable[anyio.abc.SocketListener]] = anyio.TASK_STATUS_IGNORED,
|
85
|
+
) -> ta.NoReturn:
|
86
|
+
async with anyio.create_task_group() as task_group:
|
87
|
+
if handler_task_group is None:
|
88
|
+
handler_task_group = task_group
|
89
|
+
for listener in listeners:
|
90
|
+
task_group.start_soon(_serve_one_listener, listener, handler_task_group, handler)
|
91
|
+
# The listeners are already queueing connections when we're called, but we wait until the end to call started()
|
92
|
+
# just in case we get an error or whatever.
|
93
|
+
task_status.started(listeners)
|
94
|
+
|
95
|
+
raise RuntimeError('unreachable')
|
96
|
+
|
97
|
+
|
98
|
+
async def _install_signal_handler(
|
99
|
+
tg: anyio.abc.TaskGroup,
|
100
|
+
) -> ta.Callable[..., ta.Awaitable[None]] | None:
|
101
|
+
signal_event = anyio.Event()
|
102
|
+
|
103
|
+
sigs = [
|
104
|
+
getattr(signal, signal_name)
|
105
|
+
for signal_name in ('SIGINT', 'SIGTERM', 'SIGBREAK')
|
106
|
+
if hasattr(signal, signal_name)
|
107
|
+
]
|
108
|
+
|
109
|
+
if not sigs:
|
110
|
+
return None
|
111
|
+
|
112
|
+
async def _handler(*, task_status=anyio.TASK_STATUS_IGNORED):
|
113
|
+
with anyio.open_signal_receiver(signal.SIGINT, signal.SIGTERM) as signals:
|
114
|
+
task_status.started()
|
115
|
+
async for signum in signals:
|
116
|
+
if signum == signal.SIGINT:
|
117
|
+
print('Ctrl+C pressed!')
|
118
|
+
else:
|
119
|
+
print('Terminated!')
|
120
|
+
|
121
|
+
signal_event.set()
|
122
|
+
return
|
123
|
+
|
124
|
+
await tg.start(_handler)
|
125
|
+
return signal_event.wait
|
126
|
+
|
127
|
+
|
128
|
+
async def serve(
|
129
|
+
app: AsgiFramework | AppWrapper,
|
130
|
+
config: Config,
|
131
|
+
*,
|
132
|
+
sockets: Sockets | None = None,
|
133
|
+
shutdown_trigger: ta.Callable[..., ta.Awaitable[None]] | None = None,
|
134
|
+
handle_shutdown_signals: bool = False,
|
135
|
+
task_status: anyio.abc.TaskStatus[ta.Sequence[str]] = anyio.TASK_STATUS_IGNORED,
|
136
|
+
) -> None:
|
137
|
+
app = wrap_app(app)
|
138
|
+
|
139
|
+
lifespan = Lifespan(app, config)
|
140
|
+
max_requests = None
|
141
|
+
if config.max_requests is not None:
|
142
|
+
max_requests = config.max_requests + random.randint(0, config.max_requests_jitter)
|
143
|
+
context = WorkerContext(max_requests)
|
144
|
+
|
145
|
+
async with anyio.create_task_group() as lifespan_task_group:
|
146
|
+
if shutdown_trigger is None and handle_shutdown_signals:
|
147
|
+
shutdown_trigger = await _install_signal_handler(lifespan_task_group)
|
148
|
+
|
149
|
+
await lifespan_task_group.start(lifespan.handle_lifespan)
|
150
|
+
await lifespan.wait_for_startup()
|
151
|
+
|
152
|
+
async with anyio.create_task_group() as server_task_group:
|
153
|
+
if sockets is None:
|
154
|
+
sockets = create_sockets(config)
|
155
|
+
for sock in sockets.insecure_sockets:
|
156
|
+
sock.listen(config.backlog)
|
157
|
+
|
158
|
+
listeners = []
|
159
|
+
binds = []
|
160
|
+
|
161
|
+
for sock in sockets.insecure_sockets:
|
162
|
+
listeners.append(anyio._core._eventloop.get_async_backend().create_tcp_listener(sock)) # noqa
|
163
|
+
bind = repr_socket_addr(sock.family, sock.getsockname())
|
164
|
+
binds.append(f'http://{bind}')
|
165
|
+
log.info('Running on http://%s (CTRL + C to quit)', bind)
|
166
|
+
|
167
|
+
task_status.started(binds)
|
168
|
+
try:
|
169
|
+
async with anyio.create_task_group() as task_group:
|
170
|
+
if shutdown_trigger is not None:
|
171
|
+
task_group.start_soon(raise_shutdown, shutdown_trigger)
|
172
|
+
task_group.start_soon(raise_shutdown, context.terminate.wait)
|
173
|
+
|
174
|
+
task_group.start_soon(
|
175
|
+
functools.partial(
|
176
|
+
serve_listeners,
|
177
|
+
functools.partial(TcpServer, app, config, context),
|
178
|
+
listeners,
|
179
|
+
handler_task_group=server_task_group,
|
180
|
+
),
|
181
|
+
)
|
182
|
+
|
183
|
+
await anyio.sleep_forever()
|
184
|
+
except BaseExceptionGroup as error:
|
185
|
+
_, other_errors = error.split((ShutdownError, KeyboardInterrupt)) # noqa
|
186
|
+
if other_errors is not None:
|
187
|
+
raise other_errors # noqa
|
188
|
+
finally:
|
189
|
+
await context.terminated.set()
|
190
|
+
server_task_group.cancel_scope.deadline = anyio.current_time() + config.graceful_timeout
|
191
|
+
|
192
|
+
await lifespan.wait_for_shutdown()
|
193
|
+
lifespan_task_group.cancel_scope.cancel()
|
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright 2023- wrmsr
|
2
|
+
|
3
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
|
4
|
+
following conditions are met:
|
5
|
+
|
6
|
+
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
|
7
|
+
disclaimer.
|
8
|
+
|
9
|
+
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
|
10
|
+
disclaimer in the documentation and/or other materials provided with the distribution.
|
11
|
+
|
12
|
+
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
|
13
|
+
derived from this software without specific prior written permission.
|
14
|
+
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
16
|
+
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
17
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
18
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
19
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
20
|
+
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
21
|
+
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@@ -0,0 +1,21 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: omserv
|
3
|
+
Version: 0.0.0.dev7
|
4
|
+
Summary: omserv
|
5
|
+
Author: wrmsr
|
6
|
+
License: BSD-3-Clause
|
7
|
+
Project-URL: source, https://github.com/wrmsr/omlish
|
8
|
+
Classifier: License :: OSI Approved :: BSD License
|
9
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
10
|
+
Classifier: Intended Audience :: Developers
|
11
|
+
Classifier: Operating System :: OS Independent
|
12
|
+
Classifier: Operating System :: POSIX
|
13
|
+
Requires-Python: >=3.12
|
14
|
+
License-File: LICENSE
|
15
|
+
Requires-Dist: omlish ==0.0.0.dev7
|
16
|
+
Provides-Extra: server
|
17
|
+
Requires-Dist: h11 >=0.14 ; extra == 'server'
|
18
|
+
Requires-Dist: h2 >=4.1 ; extra == 'server'
|
19
|
+
Requires-Dist: priority >=2 ; extra == 'server'
|
20
|
+
Requires-Dist: wsproto >=1.2 ; extra == 'server'
|
21
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
omserv/__about__.py,sha256=SVIHOVKqN_bCysy9qjnPhoEnzysjl_l7dXo85n3rMS0,616
|
2
|
+
omserv/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
omserv/dbs.py,sha256=RLXwNmENmM9MLsjFqDho_R8ibMjOKAxWUn4zhzTlP9Y,756
|
4
|
+
omserv/secrets.py,sha256=ja0VsCB01MHxYwn5OHjFeXV9cRah9AQl-0uJzZELpic,256
|
5
|
+
omserv/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
+
omserv/apps/base.py,sha256=KSwxbC0_fY87_DucvpEOHG6gZ2wL78D-N3We8RTEU4Q,385
|
7
|
+
omserv/apps/inject.py,sha256=RZjPcB_xEPhdw5Ki3rnKeHMqhJ8zFJZ_QZSrKpH-0xM,2670
|
8
|
+
omserv/apps/markers.py,sha256=LBVx9LTyKB2sPqDrMHjwVpqH3xDIRqMjlFFw1HsXOWI,867
|
9
|
+
omserv/apps/routes.py,sha256=shcN8qCSF2YoKal7nk-lemCAK3RX8MuHgNHhq_CTnh0,3762
|
10
|
+
omserv/apps/sessions.py,sha256=glruQSbOSbCYLPp6nDRNSHCyp5hj4oiOPhh3R0F9BTM,1537
|
11
|
+
omserv/apps/templates.py,sha256=NyCsKmPaLsGYc1mWyGiC7EcYsisQhq-wWdrnHnbwxC8,2095
|
12
|
+
omserv/node/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
|
+
omserv/node/models.py,sha256=XsfAI-LjlRkRfK0_ofh-gai5IUq_g357UfThK9dZ0UM,1258
|
14
|
+
omserv/node/registry.py,sha256=0hodmyQn3POOZJBG8zTr9ojR80-ZNmIwJpQbchJg8EM,3459
|
15
|
+
omserv/node/sql.py,sha256=vy7RP50JiH3jQHMVa7Hxk0pFJK3QcbGeTvyNppB1W4I,2826
|
16
|
+
omserv/server/__init__.py,sha256=K8l4nfv9c_5o8dph-SyadW_19CdTC2FGlikyHMU5lAU,375
|
17
|
+
omserv/server/config.py,sha256=oGWL1kuk45bJ6sVr8n3ow5Q-1nz9EqByjoykU2iOHIY,1189
|
18
|
+
omserv/server/debug.py,sha256=N7RI0Jj-ttmys3DJD0RREmGG5XZpTCp6y9Yu0x98Agg,299
|
19
|
+
omserv/server/events.py,sha256=VMr_rArsVjJYnyH9SqLWtOLUg18vSu1O0ep9gNBGR_c,1369
|
20
|
+
omserv/server/headers.py,sha256=3H-NxMMQg5WuF5wF4AWFUEqkToh4NqNqHouavzbOQok,1188
|
21
|
+
omserv/server/lifespans.py,sha256=kRVxDQM18jCBzRUpafyb69q_bGSCyxxjAtrkxjqcZdE,4607
|
22
|
+
omserv/server/multiprocess.py,sha256=xEQ4Na-Nej3oO_cxvYmQ1vEKtuLJNirrfRRMeKTxtVo,4377
|
23
|
+
omserv/server/sockets.py,sha256=lwqNP7URlp605ibsjHzp0pc-lyjcyTu-hD-uyojLUYk,3389
|
24
|
+
omserv/server/ssl.py,sha256=gmB5ecM8Mck-YtGYF8pb2dwFdjABVGzERFCDzM9lBck,1483
|
25
|
+
omserv/server/taskspawner.py,sha256=ljzF26UPtnp7GLAY_BvjzuwCoCO9aL7TKLwRNTmUy1M,3008
|
26
|
+
omserv/server/tcpserver.py,sha256=akC-2WOhmoIiJBH0Ti0m1uK_sOTBYGie0CoRkEcUmkA,5082
|
27
|
+
omserv/server/types.py,sha256=XXY5py8RYlEeD4FZrWNqSyX7DD-ffSlcG-T2s9BY9eI,2017
|
28
|
+
omserv/server/workercontext.py,sha256=4rcLuGsyiU7URO7T_eHylOBPPNUS9C23QfEUVyJUtIY,1200
|
29
|
+
omserv/server/workers.py,sha256=rdV8qEzWKMZ6HnN9nUoGS58U9LRsrsqOcAd_7yl73Y0,6586
|
30
|
+
omserv/server/protocols/__init__.py,sha256=Ryu2PDZ1TUI6F2l-HBEYgyzZ7wHqE6VmjqnS0tIvmQI,47
|
31
|
+
omserv/server/protocols/h11.py,sha256=ctakfp8fyI2opzIWzwV1FGlnJlWL3QkMABp-qoPYFXI,12050
|
32
|
+
omserv/server/protocols/h2.py,sha256=pClkPlaX0WnQ1dKee7Fvr4csSH2bBqItTcsKVuk1p1Q,15297
|
33
|
+
omserv/server/protocols/protocols.py,sha256=1ky8PGYIIR8wco6fcw-41RmHW-Kz_cNcZZoOqi7btNk,2790
|
34
|
+
omserv/server/protocols/types.py,sha256=OXt5U3LsBToDxT7ipfFGiIHJU0WVvlfT6QQacF-_Plc,440
|
35
|
+
omserv/server/resources/__init__.py,sha256=KppZbvZBKX1TdyaQZYY92oEKpdB3nuUXxharAgrbAIA,193
|
36
|
+
omserv/server/streams/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
37
|
+
omserv/server/streams/httpstream.py,sha256=0DeiAPLGbEGNa0fHTs8lUpi_CFZs4M5_QB-TiS8mobQ,8015
|
38
|
+
omserv/server/streams/utils.py,sha256=aMOrqWIg_Hht5W4kLg3y7oR5AEkVvMrZhyjzo6U5owE,1527
|
39
|
+
omserv/server/streams/wsstream.py,sha256=3Vyzox7dCE1tDSXjb6xBubWo41ZF9d38Hrsrlj6h1J8,15482
|
40
|
+
omserv-0.0.0.dev7.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
|
41
|
+
omserv-0.0.0.dev7.dist-info/METADATA,sha256=Y7anWoLnYvQ2eCXsmCjKdXGMIApFW1sKmz0ebI1OG64,680
|
42
|
+
omserv-0.0.0.dev7.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
|
43
|
+
omserv-0.0.0.dev7.dist-info/top_level.txt,sha256=HXehpnxeKscKNULzKNzZ27oNawBrsh1PaNAirbX-XNA,7
|
44
|
+
omserv-0.0.0.dev7.dist-info/RECORD,,
|
@@ -0,0 +1 @@
|
|
1
|
+
omserv
|