dbos 0.28.0a6__py3-none-any.whl → 0.28.0a7__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.
Potentially problematic release.
This version of dbos might be problematic. Click here for more details.
- dbos/_conductor/conductor.py +70 -3
- {dbos-0.28.0a6.dist-info → dbos-0.28.0a7.dist-info}/METADATA +2 -2
- {dbos-0.28.0a6.dist-info → dbos-0.28.0a7.dist-info}/RECORD +6 -6
- {dbos-0.28.0a6.dist-info → dbos-0.28.0a7.dist-info}/WHEEL +0 -0
- {dbos-0.28.0a6.dist-info → dbos-0.28.0a7.dist-info}/entry_points.txt +0 -0
- {dbos-0.28.0a6.dist-info → dbos-0.28.0a7.dist-info}/licenses/LICENSE +0 -0
dbos/_conductor/conductor.py
CHANGED
@@ -2,6 +2,7 @@ import socket
|
|
2
2
|
import threading
|
3
3
|
import time
|
4
4
|
import traceback
|
5
|
+
from importlib.metadata import version
|
5
6
|
from typing import TYPE_CHECKING, Optional
|
6
7
|
|
7
8
|
from websockets import ConnectionClosed, ConnectionClosedOK, InvalidStatus
|
@@ -21,6 +22,9 @@ from . import protocol as p
|
|
21
22
|
if TYPE_CHECKING:
|
22
23
|
from dbos import DBOS
|
23
24
|
|
25
|
+
ws_version = version("websockets")
|
26
|
+
use_keepalive = ws_version < "15.0"
|
27
|
+
|
24
28
|
|
25
29
|
class ConductorWebsocket(threading.Thread):
|
26
30
|
|
@@ -35,14 +39,64 @@ class ConductorWebsocket(threading.Thread):
|
|
35
39
|
self.url = (
|
36
40
|
conductor_url.rstrip("/") + f"/websocket/{self.app_name}/{conductor_key}"
|
37
41
|
)
|
42
|
+
# TODO: once we can upgrade to websockets>=15.0, we can always use the built-in keepalive
|
43
|
+
self.ping_interval = 20 # Time between pings in seconds
|
44
|
+
self.ping_timeout = 15 # Time to wait for a pong response in seconds
|
45
|
+
self.keepalive_thread: Optional[threading.Thread] = None
|
46
|
+
self.pong_event: Optional[threading.Event] = None
|
47
|
+
self.last_ping_time = -1.0
|
48
|
+
|
49
|
+
self.dbos.logger.debug(
|
50
|
+
f"Connecting to conductor at {self.url} using websockets version {ws_version}"
|
51
|
+
)
|
52
|
+
|
53
|
+
def keepalive(self) -> None:
|
54
|
+
self.dbos.logger.debug("Starting keepalive thread")
|
55
|
+
while not self.evt.is_set():
|
56
|
+
if self.websocket is None or self.websocket.close_code is not None:
|
57
|
+
time.sleep(1)
|
58
|
+
continue
|
59
|
+
try:
|
60
|
+
self.last_ping_time = time.time()
|
61
|
+
self.pong_event = self.websocket.ping()
|
62
|
+
self.dbos.logger.debug("> Sent ping to conductor")
|
63
|
+
pong_result = self.pong_event.wait(self.ping_timeout)
|
64
|
+
elapsed_time = time.time() - self.last_ping_time
|
65
|
+
if not pong_result:
|
66
|
+
self.dbos.logger.warning(
|
67
|
+
f"Failed to receive pong from conductor after {elapsed_time:.2f} seconds. Reconnecting."
|
68
|
+
)
|
69
|
+
self.websocket.close()
|
70
|
+
continue
|
71
|
+
# Wait for the next ping interval
|
72
|
+
self.dbos.logger.debug(
|
73
|
+
f"< Received pong from conductor after {elapsed_time:.2f} seconds"
|
74
|
+
)
|
75
|
+
wait_time = self.ping_interval - elapsed_time
|
76
|
+
self.evt.wait(max(0, wait_time))
|
77
|
+
except ConnectionClosed:
|
78
|
+
# The main loop will try to reconnect
|
79
|
+
self.dbos.logger.debug("Connection to conductor closed.")
|
80
|
+
except Exception as e:
|
81
|
+
self.dbos.logger.warning(f"Failed to send ping to conductor: {e}.")
|
82
|
+
self.websocket.close()
|
38
83
|
|
39
84
|
def run(self) -> None:
|
40
85
|
while not self.evt.is_set():
|
41
86
|
try:
|
42
87
|
with connect(
|
43
|
-
self.url,
|
88
|
+
self.url,
|
89
|
+
open_timeout=5,
|
90
|
+
close_timeout=5,
|
91
|
+
logger=self.dbos.logger,
|
44
92
|
) as websocket:
|
45
93
|
self.websocket = websocket
|
94
|
+
if use_keepalive and self.keepalive_thread is None:
|
95
|
+
self.keepalive_thread = threading.Thread(
|
96
|
+
target=self.keepalive,
|
97
|
+
daemon=True,
|
98
|
+
)
|
99
|
+
self.keepalive_thread.start()
|
46
100
|
while not self.evt.is_set():
|
47
101
|
message = websocket.recv()
|
48
102
|
if not isinstance(message, str):
|
@@ -283,8 +337,15 @@ class ConductorWebsocket(threading.Thread):
|
|
283
337
|
# Still need to send a response to the conductor
|
284
338
|
websocket.send(unknown_message.to_json())
|
285
339
|
except ConnectionClosedOK:
|
286
|
-
self.
|
287
|
-
|
340
|
+
if self.evt.is_set():
|
341
|
+
self.dbos.logger.info("Conductor connection terminated")
|
342
|
+
break
|
343
|
+
# Otherwise, we are trying to reconnect
|
344
|
+
self.dbos.logger.warning(
|
345
|
+
"Connection to conductor lost. Reconnecting..."
|
346
|
+
)
|
347
|
+
time.sleep(1)
|
348
|
+
continue
|
288
349
|
except ConnectionClosed as e:
|
289
350
|
self.dbos.logger.warning(
|
290
351
|
f"Connection to conductor lost. Reconnecting: {e}"
|
@@ -305,3 +366,9 @@ class ConductorWebsocket(threading.Thread):
|
|
305
366
|
)
|
306
367
|
time.sleep(1)
|
307
368
|
continue
|
369
|
+
|
370
|
+
# Wait for the keepalive thread to finish
|
371
|
+
if self.keepalive_thread is not None:
|
372
|
+
if self.pong_event is not None:
|
373
|
+
self.pong_event.set()
|
374
|
+
self.keepalive_thread.join()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: dbos
|
3
|
-
Version: 0.28.
|
3
|
+
Version: 0.28.0a7
|
4
4
|
Summary: Ultra-lightweight durable execution in Python
|
5
5
|
Author-Email: "DBOS, Inc." <contact@dbos.dev>
|
6
6
|
License: MIT
|
@@ -22,7 +22,7 @@ Requires-Dist: docker>=7.1.0
|
|
22
22
|
Requires-Dist: cryptography>=43.0.3
|
23
23
|
Requires-Dist: rich>=13.9.4
|
24
24
|
Requires-Dist: pyjwt>=2.10.1
|
25
|
-
Requires-Dist: websockets>=
|
25
|
+
Requires-Dist: websockets>=14.0
|
26
26
|
Description-Content-Type: text/markdown
|
27
27
|
|
28
28
|
|
@@ -1,14 +1,14 @@
|
|
1
|
-
dbos-0.28.
|
2
|
-
dbos-0.28.
|
3
|
-
dbos-0.28.
|
4
|
-
dbos-0.28.
|
1
|
+
dbos-0.28.0a7.dist-info/METADATA,sha256=qUA4yCy56WYU15e1V8V2Pb_xNM9ccKpov9vvnfMGYYM,13268
|
2
|
+
dbos-0.28.0a7.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
|
3
|
+
dbos-0.28.0a7.dist-info/entry_points.txt,sha256=_QOQ3tVfEjtjBlr1jS4sHqHya9lI2aIEIWkz8dqYp14,58
|
4
|
+
dbos-0.28.0a7.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
|
5
5
|
dbos/__init__.py,sha256=-FdBlOlr-f2tY__C23J4v22MoCAXqcDN_-zXsJXdoZ0,1005
|
6
6
|
dbos/__main__.py,sha256=G7Exn-MhGrVJVDbgNlpzhfh8WMX_72t3_oJaFT9Lmt8,653
|
7
7
|
dbos/_admin_server.py,sha256=CM02jyC9H21fM7Pjn1BhPxNwAOV7CXmMJd0SdaNq8dQ,9062
|
8
8
|
dbos/_app_db.py,sha256=3j8_5-MlSDY0otLRszFE-GfenU6JC20fcfSL-drSNYk,11800
|
9
9
|
dbos/_classproperty.py,sha256=f0X-_BySzn3yFDRKB2JpCbLYQ9tLwt1XftfshvY7CBs,626
|
10
10
|
dbos/_client.py,sha256=mGrricoU6437QM4SWV-6Vm806AgVru8ygKgGgK1LZGA,13823
|
11
|
-
dbos/_conductor/conductor.py,sha256=
|
11
|
+
dbos/_conductor/conductor.py,sha256=L8va6dGae0BlH43AHWAAW8_z04QUYPWPhjUK64sn3P4,19692
|
12
12
|
dbos/_conductor/protocol.py,sha256=jwX8ZjmAIlXu1vw9R3b5PfHSNdwofeYOKj8rkfAFVg0,6630
|
13
13
|
dbos/_context.py,sha256=Ly1CXF1nWxICQgIpDZSaONGlz1yERBs63gqmR-yqCzM,24476
|
14
14
|
dbos/_core.py,sha256=UDpSgRA9m_YuViNXR9tVgNFLC-zxKZPxjlkj2a-Kj00,48317
|
@@ -67,4 +67,4 @@ dbos/cli/cli.py,sha256=gXKELYAK9_CTejQ-WbNEIqnEYByJndXHDYSX4naFg8g,19106
|
|
67
67
|
dbos/dbos-config.schema.json,sha256=8KcwJb_sQc4-6tQG2TLmjE_nratfrQa0qVLl9XPsvWE,6367
|
68
68
|
dbos/py.typed,sha256=QfzXT1Ktfk3Rj84akygc7_42z0lRpCq0Ilh8OXI6Zas,44
|
69
69
|
version/__init__.py,sha256=L4sNxecRuqdtSFdpUGX3TtBi9KL3k7YsZVIvv-fv9-A,1678
|
70
|
-
dbos-0.28.
|
70
|
+
dbos-0.28.0a7.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|