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.

@@ -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, open_timeout=5, logger=self.dbos.logger
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.dbos.logger.info("Conductor connection terminated")
287
- break
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.0a6
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>=15.0
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.0a6.dist-info/METADATA,sha256=EtDjmsRZNSxN7U8Yv-ifyxQjawPTEKkL-wylJYH6e4c,13268
2
- dbos-0.28.0a6.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
3
- dbos-0.28.0a6.dist-info/entry_points.txt,sha256=_QOQ3tVfEjtjBlr1jS4sHqHya9lI2aIEIWkz8dqYp14,58
4
- dbos-0.28.0a6.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
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=mWOHBzSvwHa4YTere8za90rDRspCvin4CWGQ1tHtZ00,16646
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.0a6.dist-info/RECORD,,
70
+ dbos-0.28.0a7.dist-info/RECORD,,