replit-river 0.1.16.dev3__tar.gz → 0.1.16.dev4__tar.gz

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.
Files changed (28) hide show
  1. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/PKG-INFO +1 -1
  2. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/pyproject.toml +1 -1
  3. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/replit_river/client_transport.py +42 -32
  4. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/replit_river/session.py +18 -7
  5. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/replit_river/transport.py +1 -2
  6. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/replit_river/transport_options.py +1 -1
  7. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/LICENSE +0 -0
  8. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/README.md +0 -0
  9. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/replit_river/__init__.py +0 -0
  10. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/replit_river/client.py +0 -0
  11. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/replit_river/client_session.py +0 -0
  12. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/replit_river/codegen/__init__.py +0 -0
  13. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/replit_river/codegen/__main__.py +0 -0
  14. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/replit_river/codegen/client.py +0 -0
  15. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/replit_river/codegen/run.py +0 -0
  16. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/replit_river/codegen/schema.py +0 -0
  17. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/replit_river/codegen/server.py +0 -0
  18. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/replit_river/error_schema.py +0 -0
  19. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/replit_river/message_buffer.py +0 -0
  20. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/replit_river/messages.py +0 -0
  21. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/replit_river/py.typed +0 -0
  22. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/replit_river/rate_limiter.py +0 -0
  23. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/replit_river/rpc.py +0 -0
  24. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/replit_river/seq_manager.py +0 -0
  25. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/replit_river/server.py +0 -0
  26. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/replit_river/server_transport.py +0 -0
  27. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/replit_river/task_manager.py +0 -0
  28. {replit_river-0.1.16.dev3 → replit_river-0.1.16.dev4}/replit_river/websocket_wrapper.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: replit-river
3
- Version: 0.1.16.dev3
3
+ Version: 0.1.16.dev4
4
4
  Summary: Replit river toolkit for Python
5
5
  License: LICENSE
6
6
  Keywords: rpc,websockets
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name="replit-river"
7
- version="0.1.16.dev3"
7
+ version="0.1.16.dev4"
8
8
  description="Replit river toolkit for Python"
9
9
  authors = ["Replit <eng@replit.com>"]
10
10
  license = "LICENSE"
@@ -33,7 +33,6 @@ from replit_river.seq_manager import (
33
33
  IgnoreMessageException,
34
34
  InvalidMessageException,
35
35
  )
36
- from replit_river.session import Session
37
36
  from replit_river.transport import Transport
38
37
  from replit_river.transport_options import TransportOptions
39
38
 
@@ -57,29 +56,33 @@ class ClientTransport(Transport):
57
56
  self._rate_limiter = LeakyBucketRateLimit(
58
57
  transport_options.connection_retry_options
59
58
  )
59
+ # We want to make sure there's only one session creation at a time
60
+ self._create_session_lock = asyncio.Lock()
60
61
 
61
62
  async def close(self) -> None:
62
63
  self._rate_limiter.close()
63
64
  await self._close_all_sessions()
64
65
 
65
66
  async def _get_existing_session(self) -> Optional[ClientSession]:
66
- if not self._sessions:
67
- return None
68
- if len(self._sessions) > 1:
69
- raise RiverException(
70
- "session_error",
71
- "More than one session found in client, should only be one",
72
- )
73
- session = list(self._sessions.values())[0]
74
- if isinstance(session, ClientSession):
75
- return session
76
- else:
77
- raise RiverException(
78
- "session_error", f"Client session type wrong, got {type(session)}"
79
- )
67
+ async with self._session_lock:
68
+ if not self._sessions:
69
+ return None
70
+ if len(self._sessions) > 1:
71
+ raise RiverException(
72
+ "session_error",
73
+ "More than one session found in client, should only be one",
74
+ )
75
+ session = list(self._sessions.values())[0]
76
+ if isinstance(session, ClientSession):
77
+ return session
78
+ else:
79
+ raise RiverException(
80
+ "session_error", f"Client session type wrong, got {type(session)}"
81
+ )
80
82
 
81
83
  async def _establish_new_connection(
82
84
  self,
85
+ old_session: Optional[ClientSession] = None,
83
86
  ) -> Tuple[
84
87
  WebSocketCommonProtocol,
85
88
  ControlMessageHandshakeRequest,
@@ -92,19 +95,24 @@ class ClientTransport(Transport):
92
95
  for i in range(max_retry):
93
96
  if i > 0:
94
97
  logging.info(f"Retrying build handshake number {i} times")
98
+ logging.info(
99
+ f"old_session: {old_session}, old_session.is_session_open(): {await old_session.is_session_open() if old_session else None}"
100
+ )
95
101
  if not rate_limit.has_budget(client_id):
96
102
  logging.debug("No retry budget for %s.", client_id)
97
103
  break
98
104
  try:
105
+ logging.error(
106
+ f"##### _establish_new_connection: old session : {old_session}"
107
+ )
99
108
  ws = await websockets.connect(self._websocket_uri)
100
- existing_session = await self._get_existing_session()
101
109
  session_id = (
102
110
  self.generate_session_id()
103
- if not existing_session
104
- else existing_session.session_id
111
+ if not old_session
112
+ else old_session.session_id
105
113
  )
106
114
  logging.error(
107
- f"##### _establish_new_connection: existing session : {existing_session}"
115
+ f"##### _establish_new_connection: existing session : {old_session}"
108
116
  )
109
117
  rate_limit.consume_budget(client_id)
110
118
  handshake_request, handshake_response = await self._establish_handshake(
@@ -120,7 +128,7 @@ class ClientTransport(Transport):
120
128
  await asyncio.sleep(backoff_time / 1000)
121
129
  raise RiverException(
122
130
  ERROR_HANDSHAKE,
123
- "Failed to create session after retrying max number of times",
131
+ "Failed to create ws after retrying max number of times",
124
132
  )
125
133
 
126
134
  async def _create_new_session(
@@ -151,36 +159,38 @@ class ClientTransport(Transport):
151
159
  return new_session
152
160
 
153
161
  async def _get_or_create_session(self) -> ClientSession:
154
- async with self._session_lock:
162
+ logging.error(f"####### start get or create session")
163
+ async with self._create_session_lock:
155
164
  existing_session = await self._get_existing_session()
156
165
  if not existing_session:
157
- logging.error(f"##### No existing session")
166
+ logging.error(f"##### _get_or_create_session No existing session")
158
167
  return await self._create_new_session()
159
168
  is_session_open = await existing_session.is_session_open()
160
169
  if not is_session_open:
161
- logging.error(f"##### session open, creating new session")
162
- await existing_session.close(
163
- is_unexpected_close=False, acquire_transport_lock=False
170
+ logging.error(
171
+ f"##### _get_or_create_session session open, creating new session"
164
172
  )
165
173
  return await self._create_new_session()
166
- is_ws_open = existing_session.is_websocket_open()
174
+ is_ws_open = await existing_session.is_websocket_open()
167
175
  if is_ws_open:
168
- logging.error(f"##### Reuse existing session")
176
+ logging.error(f"##### _get_or_create_session Reuse existing session")
169
177
  return existing_session
170
178
  else:
171
- new_ws, _, hs_response = await self._establish_new_connection()
179
+ new_ws, _, hs_response = await self._establish_new_connection(
180
+ existing_session
181
+ )
172
182
  if (
173
183
  hs_response.status.sessionId
174
184
  == existing_session.advertised_session_id
175
185
  ):
176
- logging.error(f"##### session open, replacing websocket")
186
+ logging.error(
187
+ f"##### _get_or_create_session session open, replacing websocket"
188
+ )
177
189
  await existing_session.replace_with_new_websocket(new_ws)
178
190
  return existing_session
179
191
  else:
180
192
  logging.error(f"##### session open, not same session id, reuse")
181
- await existing_session.close(
182
- is_unexpected_close=False, acquire_transport_lock=False
183
- )
193
+ await existing_session.close(is_unexpected_close=False)
184
194
  return await self._create_new_session()
185
195
 
186
196
  async def _send_handshake_request(
@@ -108,11 +108,9 @@ class Session(object):
108
108
  """Begin the countdown to close session, this should be called when
109
109
  websocket is closed.
110
110
  """
111
- logging.debug("begin_close_session_countdown")
112
111
  if self._close_session_after_time_secs is not None:
113
112
  # already in grace period, no need to set again
114
113
  return
115
- await self._ws_wrapper.close()
116
114
  logging.debug(
117
115
  "websocket closed from %s to %s begin grace period",
118
116
  self._transport_id,
@@ -122,6 +120,7 @@ class Session(object):
122
120
  self._close_session_after_time_secs = (
123
121
  await self._get_current_time() + grace_period_ms / 1000
124
122
  )
123
+ await self.close_websocket(self._ws_wrapper, should_retry=not self._is_server)
125
124
 
126
125
  async def serve(self) -> None:
127
126
  """Serve messages from the websocket."""
@@ -164,8 +163,15 @@ class Session(object):
164
163
  self._ws_wrapper.id,
165
164
  )
166
165
  try:
167
- async for message in self._ws_wrapper.ws:
166
+ ws_wrapper = self._ws_wrapper
167
+ async for message in ws_wrapper.ws:
168
168
  try:
169
+ logging.error(
170
+ f" await ws_wrapper.is_open(): : { await ws_wrapper.is_open()}"
171
+ )
172
+ if not await ws_wrapper.is_open():
173
+ # We should not process messages if the websocket is closed.
174
+ break
169
175
  msg = parse_transport_msg(message, self._transport_options)
170
176
 
171
177
  logging.debug(f"{self._transport_id} got a message %r", msg)
@@ -235,9 +241,13 @@ class Session(object):
235
241
  await asyncio.sleep(
236
242
  self._transport_options.close_session_check_interval_ms / 1000
237
243
  )
244
+ logging.error("#### _check_to_close_session")
238
245
  if not self._close_session_after_time_secs:
239
246
  continue
240
247
  current_time = await self._get_current_time()
248
+ logging.error(
249
+ f"#### _check_to_close_session : current_time: {current_time} self._close_session_after_time_secs: {self._close_session_after_time_secs}, {current_time > self._close_session_after_time_secs}"
250
+ )
241
251
  if current_time > self._close_session_after_time_secs:
242
252
  logging.debug(
243
253
  "Grace period ended for %s, closing session", self._transport_id
@@ -282,14 +292,14 @@ class Session(object):
282
292
  self._heartbeat_misses
283
293
  >= self._transport_options.heartbeats_until_dead
284
294
  ):
295
+ if self._close_session_after_time_secs is not None:
296
+ # already in grace period, no need to set again
297
+ continue
285
298
  logging.debug(
286
299
  "%r closing websocket because of heartbeat misses",
287
300
  self.session_id,
288
301
  )
289
302
  await self._begin_close_session_countdown()
290
- await self.close_websocket(
291
- self._ws_wrapper, should_retry=not self._is_server
292
- )
293
303
  continue
294
304
  except FailedSendingMessageException:
295
305
  # this is expected during websocket closed period
@@ -415,7 +425,8 @@ class Session(object):
415
425
  return
416
426
  await ws_wrapper.close()
417
427
  if should_retry and self._retry_connection_callback:
418
- await self._retry_connection_callback(self)
428
+ logging.error("### running retry_connection_callback")
429
+ await self._task_manager.create_task(self._retry_connection_callback(self))
419
430
 
420
431
  async def _open_stream_and_call_handler(
421
432
  self,
@@ -61,8 +61,7 @@ class Transport:
61
61
  async with self._session_lock:
62
62
  self._sessions[session._to_id] = session
63
63
  else:
64
- if session._to_id in self._sessions:
65
- del self._sessions[session._to_id]
64
+ self._sessions[session._to_id] = session
66
65
 
67
66
  def generate_nanoid(self) -> str:
68
67
  return str(nanoid.generate())
@@ -13,7 +13,7 @@ class ConnectionRetryOptions(BaseModel):
13
13
  max_backoff_ms: float = 32_000
14
14
  attempt_budget_capacity: float = 5
15
15
  budget_restore_interval_ms: float = 200
16
- max_retry: int = 10
16
+ max_retry: int = 100
17
17
 
18
18
 
19
19
  # setup in replit web can be found at