replit-river 0.1.16.dev2__tar.gz → 0.1.16.dev3__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.
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/PKG-INFO +1 -1
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/pyproject.toml +1 -1
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/replit_river/client_transport.py +70 -106
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/replit_river/session.py +14 -14
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/replit_river/transport.py +25 -14
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/LICENSE +0 -0
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/README.md +0 -0
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/replit_river/__init__.py +0 -0
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/replit_river/client.py +0 -0
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/replit_river/client_session.py +0 -0
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/replit_river/codegen/__init__.py +0 -0
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/replit_river/codegen/__main__.py +0 -0
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/replit_river/codegen/client.py +0 -0
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/replit_river/codegen/run.py +0 -0
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/replit_river/codegen/schema.py +0 -0
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/replit_river/codegen/server.py +0 -0
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/replit_river/error_schema.py +0 -0
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/replit_river/message_buffer.py +0 -0
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/replit_river/messages.py +0 -0
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/replit_river/py.typed +0 -0
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/replit_river/rate_limiter.py +0 -0
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/replit_river/rpc.py +0 -0
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/replit_river/seq_manager.py +0 -0
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/replit_river/server.py +0 -0
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/replit_river/server_transport.py +0 -0
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/replit_river/task_manager.py +0 -0
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/replit_river/transport_options.py +0 -0
- {replit_river-0.1.16.dev2 → replit_river-0.1.16.dev3}/replit_river/websocket_wrapper.py +0 -0
|
@@ -57,35 +57,26 @@ class ClientTransport(Transport):
|
|
|
57
57
|
self._rate_limiter = LeakyBucketRateLimit(
|
|
58
58
|
transport_options.connection_retry_options
|
|
59
59
|
)
|
|
60
|
-
# We want to make sure there's only one session creation at a time
|
|
61
|
-
self._create_session_lock = asyncio.Lock()
|
|
62
|
-
# Only one retry should happen at a time
|
|
63
|
-
self._retry_ws_lock = asyncio.Lock()
|
|
64
|
-
|
|
65
|
-
async def _on_session_closed(self, session: Session) -> None:
|
|
66
|
-
logging.info(f"Client session {session.advertised_session_id} closed")
|
|
67
|
-
await self._delete_session(session)
|
|
68
60
|
|
|
69
61
|
async def close(self) -> None:
|
|
70
62
|
self._rate_limiter.close()
|
|
71
63
|
await self._close_all_sessions()
|
|
72
64
|
|
|
73
65
|
async def _get_existing_session(self) -> Optional[ClientSession]:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
)
|
|
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
|
+
)
|
|
89
80
|
|
|
90
81
|
async def _establish_new_connection(
|
|
91
82
|
self,
|
|
@@ -112,7 +103,9 @@ class ClientTransport(Transport):
|
|
|
112
103
|
if not existing_session
|
|
113
104
|
else existing_session.session_id
|
|
114
105
|
)
|
|
115
|
-
logging.error(
|
|
106
|
+
logging.error(
|
|
107
|
+
f"##### _establish_new_connection: existing session : {existing_session}"
|
|
108
|
+
)
|
|
116
109
|
rate_limit.consume_budget(client_id)
|
|
117
110
|
handshake_request, handshake_response = await self._establish_handshake(
|
|
118
111
|
self._transport_id, self._server_id, session_id, ws
|
|
@@ -130,94 +123,65 @@ class ClientTransport(Transport):
|
|
|
130
123
|
"Failed to create session after retrying max number of times",
|
|
131
124
|
)
|
|
132
125
|
|
|
133
|
-
async def
|
|
134
|
-
self,
|
|
135
|
-
) ->
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
new_session = ClientSession(
|
|
156
|
-
transport_id=self._transport_id,
|
|
157
|
-
to_id=self._server_id,
|
|
158
|
-
session_id=hs_request.sessionId,
|
|
159
|
-
advertised_session_id=server_session_id,
|
|
160
|
-
websocket=new_ws,
|
|
161
|
-
transport_options=self._transport_options,
|
|
162
|
-
is_server=False,
|
|
163
|
-
handlers={},
|
|
164
|
-
close_session_callback=self._on_session_closed,
|
|
165
|
-
retry_connection_callback=lambda x: self._retry_session_connection(
|
|
166
|
-
x
|
|
167
|
-
),
|
|
168
|
-
)
|
|
169
|
-
return new_session
|
|
170
|
-
else:
|
|
171
|
-
# If the session is still active and aligns with the server session
|
|
172
|
-
# we replace the websocket in it.
|
|
173
|
-
await session_to_replace_ws.replace_with_new_websocket(new_ws)
|
|
174
|
-
return session_to_replace_ws
|
|
126
|
+
async def _create_new_session(
|
|
127
|
+
self,
|
|
128
|
+
) -> ClientSession:
|
|
129
|
+
new_ws, hs_request, hs_response = await self._establish_new_connection()
|
|
130
|
+
advertised_session_id = hs_response.status.sessionId
|
|
131
|
+
if not advertised_session_id:
|
|
132
|
+
raise RiverException(
|
|
133
|
+
ERROR_SESSION,
|
|
134
|
+
"Server did not return a sessionId in successful handshake",
|
|
135
|
+
)
|
|
136
|
+
new_session = ClientSession(
|
|
137
|
+
transport_id=self._transport_id,
|
|
138
|
+
to_id=self._server_id,
|
|
139
|
+
session_id=hs_request.sessionId,
|
|
140
|
+
advertised_session_id=advertised_session_id,
|
|
141
|
+
websocket=new_ws,
|
|
142
|
+
transport_options=self._transport_options,
|
|
143
|
+
is_server=False,
|
|
144
|
+
handlers={},
|
|
145
|
+
close_session_callback=self._delete_session,
|
|
146
|
+
retry_connection_callback=lambda x: self._get_or_create_session(),
|
|
147
|
+
)
|
|
175
148
|
|
|
176
|
-
|
|
177
|
-
|
|
149
|
+
await self._set_session(new_session, acquire_lock=False)
|
|
150
|
+
await new_session.start_serve_responses()
|
|
151
|
+
return new_session
|
|
178
152
|
|
|
179
|
-
|
|
153
|
+
async def _get_or_create_session(self) -> ClientSession:
|
|
154
|
+
async with self._session_lock:
|
|
180
155
|
existing_session = await self._get_existing_session()
|
|
181
|
-
if existing_session:
|
|
182
|
-
|
|
183
|
-
|
|
156
|
+
if not existing_session:
|
|
157
|
+
logging.error(f"##### No existing session")
|
|
158
|
+
return await self._create_new_session()
|
|
159
|
+
is_session_open = await existing_session.is_session_open()
|
|
160
|
+
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
|
|
164
|
+
)
|
|
165
|
+
return await self._create_new_session()
|
|
166
|
+
is_ws_open = existing_session.is_websocket_open()
|
|
167
|
+
if is_ws_open:
|
|
168
|
+
logging.error(f"##### Reuse existing session")
|
|
169
|
+
return existing_session
|
|
170
|
+
else:
|
|
171
|
+
new_ws, _, hs_response = await self._establish_new_connection()
|
|
172
|
+
if (
|
|
173
|
+
hs_response.status.sessionId
|
|
174
|
+
== existing_session.advertised_session_id
|
|
175
|
+
):
|
|
176
|
+
logging.error(f"##### session open, replacing websocket")
|
|
177
|
+
await existing_session.replace_with_new_websocket(new_ws)
|
|
184
178
|
return existing_session
|
|
185
179
|
else:
|
|
186
|
-
logging.error(f"
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if not isinstance(session, ClientSession):
|
|
190
|
-
raise RiverException(
|
|
191
|
-
ERROR_SESSION,
|
|
192
|
-
f"Session type is not ClientSession, got {type(session)}",
|
|
193
|
-
)
|
|
194
|
-
return session
|
|
195
|
-
else:
|
|
196
|
-
logging.error(f"####### establish new connection")
|
|
197
|
-
new_ws, hs_request, hs_response = await self._establish_new_connection()
|
|
198
|
-
advertised_session_id = hs_response.status.sessionId
|
|
199
|
-
if not advertised_session_id:
|
|
200
|
-
raise RiverException(
|
|
201
|
-
ERROR_SESSION,
|
|
202
|
-
"Server did not return a sessionId in successful handshake",
|
|
180
|
+
logging.error(f"##### session open, not same session id, reuse")
|
|
181
|
+
await existing_session.close(
|
|
182
|
+
is_unexpected_close=False, acquire_transport_lock=False
|
|
203
183
|
)
|
|
204
|
-
|
|
205
|
-
transport_id=self._transport_id,
|
|
206
|
-
to_id=self._server_id,
|
|
207
|
-
session_id=hs_request.sessionId,
|
|
208
|
-
advertised_session_id=advertised_session_id,
|
|
209
|
-
websocket=new_ws,
|
|
210
|
-
transport_options=self._transport_options,
|
|
211
|
-
is_server=False,
|
|
212
|
-
handlers={},
|
|
213
|
-
close_session_callback=self._on_session_closed,
|
|
214
|
-
retry_connection_callback=lambda x: self._retry_session_connection(
|
|
215
|
-
x
|
|
216
|
-
),
|
|
217
|
-
)
|
|
218
|
-
await self._set_session(new_session)
|
|
219
|
-
await new_session.start_serve_responses()
|
|
220
|
-
return new_session
|
|
184
|
+
return await self._create_new_session()
|
|
221
185
|
|
|
222
186
|
async def _send_handshake_request(
|
|
223
187
|
self,
|
|
@@ -52,7 +52,7 @@ class Session(object):
|
|
|
52
52
|
transport_options: TransportOptions,
|
|
53
53
|
is_server: bool,
|
|
54
54
|
handlers: Dict[Tuple[str, str], Tuple[str, GenericRpcHandler]],
|
|
55
|
-
close_session_callback: Callable[["Session"], Coroutine[Any, Any, None]],
|
|
55
|
+
close_session_callback: Callable[["Session", bool], Coroutine[Any, Any, None]],
|
|
56
56
|
retry_connection_callback: Optional[
|
|
57
57
|
Callable[
|
|
58
58
|
["Session"],
|
|
@@ -104,13 +104,6 @@ class Session(object):
|
|
|
104
104
|
async with self._ws_lock:
|
|
105
105
|
return await self._ws_wrapper.is_open()
|
|
106
106
|
|
|
107
|
-
async def _on_websocket_unexpected_close(self) -> None:
|
|
108
|
-
"""Handle unexpected websocket close."""
|
|
109
|
-
logging.debug(
|
|
110
|
-
"Unexpected websocket close from %s to %s", self._transport_id, self._to_id
|
|
111
|
-
)
|
|
112
|
-
await self._begin_close_session_countdown()
|
|
113
|
-
|
|
114
107
|
async def _begin_close_session_countdown(self) -> None:
|
|
115
108
|
"""Begin the countdown to close session, this should be called when
|
|
116
109
|
websocket is closed.
|
|
@@ -119,6 +112,7 @@ class Session(object):
|
|
|
119
112
|
if self._close_session_after_time_secs is not None:
|
|
120
113
|
# already in grace period, no need to set again
|
|
121
114
|
return
|
|
115
|
+
await self._ws_wrapper.close()
|
|
122
116
|
logging.debug(
|
|
123
117
|
"websocket closed from %s to %s begin grace period",
|
|
124
118
|
self._transport_id,
|
|
@@ -137,7 +131,7 @@ class Session(object):
|
|
|
137
131
|
try:
|
|
138
132
|
await self._handle_messages_from_ws(tg)
|
|
139
133
|
except ConnectionClosed as e:
|
|
140
|
-
await self.
|
|
134
|
+
await self._begin_close_session_countdown()
|
|
141
135
|
logging.debug("ConnectionClosed while serving: %r", e)
|
|
142
136
|
except FailedSendingMessageException as e:
|
|
143
137
|
# Expected error if the connection is closed.
|
|
@@ -292,7 +286,7 @@ class Session(object):
|
|
|
292
286
|
"%r closing websocket because of heartbeat misses",
|
|
293
287
|
self.session_id,
|
|
294
288
|
)
|
|
295
|
-
await self.
|
|
289
|
+
await self._begin_close_session_countdown()
|
|
296
290
|
await self.close_websocket(
|
|
297
291
|
self._ws_wrapper, should_retry=not self._is_server
|
|
298
292
|
)
|
|
@@ -327,7 +321,7 @@ class Session(object):
|
|
|
327
321
|
) -> None:
|
|
328
322
|
try:
|
|
329
323
|
await send_transport_message(
|
|
330
|
-
msg, websocket, self.
|
|
324
|
+
msg, websocket, self._begin_close_session_countdown, prefix_bytes
|
|
331
325
|
)
|
|
332
326
|
except WebsocketClosedException as e:
|
|
333
327
|
raise e
|
|
@@ -416,6 +410,9 @@ class Session(object):
|
|
|
416
410
|
) -> None:
|
|
417
411
|
"""Mark the websocket as closed, close the websocket, and retry if needed."""
|
|
418
412
|
async with self._ws_lock:
|
|
413
|
+
# Already closed.
|
|
414
|
+
if not await ws_wrapper.is_open():
|
|
415
|
+
return
|
|
419
416
|
await ws_wrapper.close()
|
|
420
417
|
if should_retry and self._retry_connection_callback:
|
|
421
418
|
await self._retry_connection_callback(self)
|
|
@@ -495,7 +492,9 @@ class Session(object):
|
|
|
495
492
|
async def start_serve_responses(self) -> None:
|
|
496
493
|
await self._task_manager.create_task(self.serve())
|
|
497
494
|
|
|
498
|
-
async def close(
|
|
495
|
+
async def close(
|
|
496
|
+
self, is_unexpected_close: bool, acquire_transport_lock: bool = True
|
|
497
|
+
) -> None:
|
|
499
498
|
"""Close the session and all associated streams."""
|
|
500
499
|
logging.info(
|
|
501
500
|
f"{self._transport_id} closing session "
|
|
@@ -508,9 +507,10 @@ class Session(object):
|
|
|
508
507
|
return
|
|
509
508
|
self._state = SessionState.CLOSING
|
|
510
509
|
self._reset_session_close_countdown()
|
|
511
|
-
|
|
510
|
+
async with self._ws_lock:
|
|
511
|
+
await self._ws_wrapper.close()
|
|
512
512
|
# Clear the session in transports
|
|
513
|
-
await self._close_session_callback(self)
|
|
513
|
+
await self._close_session_callback(self, acquire_transport_lock)
|
|
514
514
|
await self._task_manager.cancel_all_tasks()
|
|
515
515
|
# TODO: unexpected_close should close stream differently here to
|
|
516
516
|
# throw exception correctly.
|
|
@@ -45,14 +45,24 @@ class Transport:
|
|
|
45
45
|
await asyncio.gather(*tasks)
|
|
46
46
|
logging.info(f"Transport closed {self._transport_id}")
|
|
47
47
|
|
|
48
|
-
async def _delete_session(
|
|
49
|
-
|
|
48
|
+
async def _delete_session(
|
|
49
|
+
self, session: Session, acquire_lock: bool = True
|
|
50
|
+
) -> None:
|
|
51
|
+
if acquire_lock:
|
|
52
|
+
async with self._session_lock:
|
|
53
|
+
if session._to_id in self._sessions:
|
|
54
|
+
del self._sessions[session._to_id]
|
|
55
|
+
else:
|
|
50
56
|
if session._to_id in self._sessions:
|
|
51
57
|
del self._sessions[session._to_id]
|
|
52
58
|
|
|
53
|
-
async def _set_session(self, session: Session) -> None:
|
|
54
|
-
|
|
55
|
-
self.
|
|
59
|
+
async def _set_session(self, session: Session, acquire_lock: bool = True) -> None:
|
|
60
|
+
if acquire_lock:
|
|
61
|
+
async with self._session_lock:
|
|
62
|
+
self._sessions[session._to_id] = session
|
|
63
|
+
else:
|
|
64
|
+
if session._to_id in self._sessions:
|
|
65
|
+
del self._sessions[session._to_id]
|
|
56
66
|
|
|
57
67
|
def generate_nanoid(self) -> str:
|
|
58
68
|
return str(nanoid.generate())
|
|
@@ -87,6 +97,7 @@ class Transport:
|
|
|
87
97
|
) -> Session:
|
|
88
98
|
session_to_close: Optional[Session] = None
|
|
89
99
|
new_session: Optional[Session] = None
|
|
100
|
+
logging.error(f"## get_or_create_session, {to_id}")
|
|
90
101
|
async with self._session_lock:
|
|
91
102
|
if to_id not in self._sessions:
|
|
92
103
|
logging.debug(
|
|
@@ -138,13 +149,13 @@ class Transport:
|
|
|
138
149
|
new_session = old_session
|
|
139
150
|
except FailedSendingMessageException as e:
|
|
140
151
|
raise e
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
152
|
+
|
|
153
|
+
if session_to_close:
|
|
154
|
+
logging.debug(
|
|
155
|
+
"Closing stale session %s", session_to_close.advertised_session_id
|
|
156
|
+
)
|
|
157
|
+
await session_to_close.close(
|
|
158
|
+
is_unexpected_close=False, acquire_transport_lock=False
|
|
159
|
+
)
|
|
160
|
+
await self._set_session(new_session, acquire_lock=False)
|
|
150
161
|
return new_session
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|