dbos 0.28.0a6__py3-none-any.whl → 0.28.0a8__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.
@@ -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()
dbos/_docker_pg_helper.py CHANGED
@@ -83,6 +83,7 @@ def start_docker_postgres(pool_config: Dict[str, Any]) -> bool:
83
83
  logging.info("Starting a Postgres Docker container...")
84
84
  container_name = "dbos-db"
85
85
  pg_data = "/var/lib/postgresql/data"
86
+ image_name = "pgvector/pgvector:pg16"
86
87
 
87
88
  try:
88
89
  client = docker.from_env()
@@ -103,9 +104,15 @@ def start_docker_postgres(pool_config: Dict[str, Any]) -> bool:
103
104
  # Container doesn't exist, proceed with creation
104
105
  pass
105
106
 
107
+ # Pull the image if it doesn't exist
108
+ imgs = client.images.list(name=image_name)
109
+ if len(imgs) == 0:
110
+ logging.info(f"Pulling Docker image {image_name}...")
111
+ client.images.pull(image_name)
112
+
106
113
  # Create and start the container
107
114
  container = client.containers.run(
108
- image="pgvector/pgvector:pg16",
115
+ image=image_name,
109
116
  name=container_name,
110
117
  detach=True,
111
118
  environment={
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dbos
3
- Version: 0.28.0a6
3
+ Version: 0.28.0a8
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.0a8.dist-info/METADATA,sha256=ed_1fo-R4JZXcSiu5dRKS1AzETl8dB9Qwk-tX4mC1Ho,13268
2
+ dbos-0.28.0a8.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
3
+ dbos-0.28.0a8.dist-info/entry_points.txt,sha256=_QOQ3tVfEjtjBlr1jS4sHqHya9lI2aIEIWkz8dqYp14,58
4
+ dbos-0.28.0a8.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
@@ -16,7 +16,7 @@ dbos/_croniter.py,sha256=XHAyUyibs_59sJQfSNWkP7rqQY6_XrlfuuCxk4jYqek,47559
16
16
  dbos/_dbos.py,sha256=nne4oaIEC0tR2OV766y7mjTLjGaqKz7EnuY-T2CbTtc,48431
17
17
  dbos/_dbos_config.py,sha256=L0Z0OOB5FoPM9g-joZqXGeJnlxWQsEUtgPtgtg9Uf48,21732
18
18
  dbos/_debug.py,sha256=MNlQVZ6TscGCRQeEEL0VE8Uignvr6dPeDDDefS3xgIE,1823
19
- dbos/_docker_pg_helper.py,sha256=NmcgqmR5rQA_4igfeqh8ugNT2z3YmoOvuep_MEtxTiY,5854
19
+ dbos/_docker_pg_helper.py,sha256=tLJXWqZ4S-ExcaPnxg_i6cVxL6ZxrYlZjaGsklY-s2I,6115
20
20
  dbos/_error.py,sha256=EN4eVBjMT3k7O7hfqJl6mIf4sxWPsiAOM086yhcGH_g,8012
21
21
  dbos/_event_loop.py,sha256=NmaLbEQFfEK36S_0KhVD39YdYrGce3qSKCTJ-5RqKQ0,2136
22
22
  dbos/_fastapi.py,sha256=m4SL3H9P-NBQ_ZrbFxAWMOqNyIi3HGEn2ODR7xAK038,3118
@@ -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.0a8.dist-info/RECORD,,