motorcortex-python 1.2.0__tar.gz → 1.2.1__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.
- {motorcortex_python-1.2.0/motorcortex_python.egg-info → motorcortex_python-1.2.1}/PKG-INFO +1 -1
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex/__init__.py +27 -2
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex/_request_utils.py +51 -7
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex/request.py +27 -6
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex/subscribe.py +35 -10
- motorcortex_python-1.2.1/motorcortex/version.py +1 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1/motorcortex_python.egg-info}/PKG-INFO +1 -1
- motorcortex_python-1.2.0/motorcortex/version.py +0 -1
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/LICENSE +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/MANIFEST.in +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/README.md +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex/_connection_state.py +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex/_request_builders.py +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex/_sp_stream.py +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex/_subscribe_dispatch.py +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex/exceptions.py +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex/init_threads.py +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex/message_types.py +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex/motorcortex_hash.json +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex/motorcortex_pb2.py +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex/motorcortex_pb2.pyi +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex/nng_url.py +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex/parameter_tree.py +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex/py.typed +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex/reply.py +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex/session.py +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex/setup_logger.py +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex/state_callback_handler.py +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex/subscription.py +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex/timespec.py +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex_python.egg-info/SOURCES.txt +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex_python.egg-info/dependency_links.txt +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex_python.egg-info/requires.txt +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex_python.egg-info/top_level.txt +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/pyproject.toml +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/setup.cfg +0 -0
- {motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/setup.py +0 -0
|
@@ -41,6 +41,7 @@ from motorcortex.exceptions import (
|
|
|
41
41
|
McxTimeout,
|
|
42
42
|
)
|
|
43
43
|
from motorcortex.motorcortex_pb2 import StatusCode as _PbStatusCode
|
|
44
|
+
from motorcortex import _request_utils
|
|
44
45
|
from motorcortex.setup_logger import logger # not re-exported — ``__all__`` gates package surface
|
|
45
46
|
|
|
46
47
|
# Build an ``IntEnum`` facade over the protobuf ``StatusCode`` enum so
|
|
@@ -312,7 +313,15 @@ def connect(
|
|
|
312
313
|
|
|
313
314
|
if reconnect and not kwargs.get("state_update"):
|
|
314
315
|
|
|
316
|
+
# The initial CONNECTION_OK edge can land after connect() returns
|
|
317
|
+
# (the cookie probe + hello finish in the background), so it must
|
|
318
|
+
# not be mistaken for a reconnect that needs a re-login.
|
|
319
|
+
initial_ok_seen = [False]
|
|
320
|
+
|
|
315
321
|
def stateUpdate(req, sub, state):
|
|
322
|
+
if state == ConnectionState.CONNECTION_OK and not initial_ok_seen[0]:
|
|
323
|
+
initial_ok_seen[0] = True
|
|
324
|
+
return
|
|
316
325
|
_reconnect_state_update(
|
|
317
326
|
req, sub, state,
|
|
318
327
|
motorcortex_types=motorcortex_types,
|
|
@@ -342,7 +351,12 @@ def connect(
|
|
|
342
351
|
)
|
|
343
352
|
# Open subscribe connection
|
|
344
353
|
sub = Subscribe(req, motorcortex_types, kwargs.get("sub_number_of_threads", 2))
|
|
345
|
-
|
|
354
|
+
sub_reply = sub.connect(makeUrl(sub_address, sub_port), **kwargs)
|
|
355
|
+
# Wait for the dial only: the cookie probe and hello carry on in
|
|
356
|
+
# the background (up to 2 s on an old server that never answers)
|
|
357
|
+
# while login and a cached tree load go ahead.
|
|
358
|
+
dialed = getattr(sub, "_dialed", None)
|
|
359
|
+
if not (dialed.result() if dialed is not None else sub_reply.get()):
|
|
346
360
|
raise McxConnectionError(
|
|
347
361
|
"Failed to establish subscribe connection: {}:{}".format(sub_address, sub_port)
|
|
348
362
|
)
|
|
@@ -360,7 +374,18 @@ def connect(
|
|
|
360
374
|
)
|
|
361
375
|
|
|
362
376
|
# Requesting a parameter tree
|
|
363
|
-
|
|
377
|
+
# On a cache miss, wait for the cookie-probe verdict so an old
|
|
378
|
+
# server (no compressed tree handler, silently drops the request)
|
|
379
|
+
# gets the plain tree straight away instead of waiting on a reply
|
|
380
|
+
# that never comes. A cache hit never calls this.
|
|
381
|
+
def compressed_hint():
|
|
382
|
+
try:
|
|
383
|
+
sub_reply.get(timeout_ms=_request_utils.CAPABILITY_PROBE_TIMEOUT_MS + 3000)
|
|
384
|
+
except Exception:
|
|
385
|
+
return None
|
|
386
|
+
return getattr(sub, "_server_has_routing", None)
|
|
387
|
+
|
|
388
|
+
param_tree_reply = req.getParameterTree(compressed=compressed_hint)
|
|
364
389
|
tree = param_tree_reply.get()
|
|
365
390
|
param_tree.load(tree)
|
|
366
391
|
except Exception:
|
|
@@ -21,7 +21,10 @@ import os
|
|
|
21
21
|
import tempfile
|
|
22
22
|
import zlib
|
|
23
23
|
from threading import Event
|
|
24
|
-
from typing import Any, Callable, Optional, Tuple
|
|
24
|
+
from typing import Any, Callable, Optional, Tuple, Union
|
|
25
|
+
|
|
26
|
+
from pynng._nng import lib # type: ignore[import-untyped]
|
|
27
|
+
from pynng.exceptions import check_err # type: ignore[import-untyped]
|
|
25
28
|
|
|
26
29
|
from motorcortex.exceptions import McxConnectionError
|
|
27
30
|
from motorcortex.setup_logger import logger
|
|
@@ -29,6 +32,12 @@ from motorcortex.setup_logger import logger
|
|
|
29
32
|
|
|
30
33
|
_CACHE_PREFIX = "mcx-python-pt"
|
|
31
34
|
|
|
35
|
+
#: Old servers silently drop unknown request hashes — cap how long a
|
|
36
|
+
#: capability probe (routing cookie, compressed tree) may wait so a
|
|
37
|
+
#: pre-2.36 server costs at most 2 s per probe. Mirrors
|
|
38
|
+
#: CAPABILITY_PROBE_TIMEOUT_MS in motorcortex-cpp and motorcortex-js.
|
|
39
|
+
CAPABILITY_PROBE_TIMEOUT_MS = 2000
|
|
40
|
+
|
|
32
41
|
|
|
33
42
|
def parse_connect_kwargs(
|
|
34
43
|
conn_timeout_ms: int = 0,
|
|
@@ -114,6 +123,7 @@ def fetch_parameter_tree(
|
|
|
114
123
|
protobuf_types: Any,
|
|
115
124
|
socket: Any,
|
|
116
125
|
url: Optional[str] = None,
|
|
126
|
+
compressed: Union[Optional[bool], Callable[[], Optional[bool]]] = None,
|
|
117
127
|
) -> Any:
|
|
118
128
|
"""Fetch the parameter tree, hitting the local cache first.
|
|
119
129
|
|
|
@@ -132,6 +142,19 @@ def fetch_parameter_tree(
|
|
|
132
142
|
:func:`send_and_recv`.
|
|
133
143
|
url: Connection URL — hashed into the cache filename so two
|
|
134
144
|
engines with the same ``tree_hash`` don't collide.
|
|
145
|
+
compressed: Server-capability hint from the routing-cookie probe
|
|
146
|
+
(same scheme as motorcortex-js). The cookie handler and the
|
|
147
|
+
compressed tree handler both landed in core 2.36, so:
|
|
148
|
+
``False`` — the server did not hand out a cookie, so it has no
|
|
149
|
+
compressed handler: skip straight to the plain request.
|
|
150
|
+
``True`` — the server is >= 2.36: request compressed with the
|
|
151
|
+
socket's own timeout, so a big reply on a slow link never
|
|
152
|
+
races a short cap.
|
|
153
|
+
``None`` — unknown (no Subscribe, raw ``Request`` use): try
|
|
154
|
+
compressed, capped at ``CAPABILITY_PROBE_TIMEOUT_MS``.
|
|
155
|
+
May also be a callable returning one of those; it is only
|
|
156
|
+
called on a cache miss, so a still-running probe never delays
|
|
157
|
+
a cached tree load.
|
|
135
158
|
|
|
136
159
|
Returns:
|
|
137
160
|
A ``ParameterTreeMsg`` — either the cached one or the freshly
|
|
@@ -152,10 +175,10 @@ def fetch_parameter_tree(
|
|
|
152
175
|
``ParameterTreeMsg`` type, so callers (and the cache write below)
|
|
153
176
|
can't tell the difference. Tried for every URL scheme (ipc, tcp,
|
|
154
177
|
ws, tls+tcp, wss) — the request is just bytes over
|
|
155
|
-
:func:`send_and_recv`.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
178
|
+
:func:`send_and_recv`. Old servers never answer the unknown hash, and
|
|
179
|
+
the socket's ``recv_timeout`` is infinite by default, so an uncapped
|
|
180
|
+
attempt would hang the connect forever — hence the ``compressed``
|
|
181
|
+
hint and the per-request probe cap.
|
|
159
182
|
"""
|
|
160
183
|
path = parameter_tree_cache_path(url, tree_hash)
|
|
161
184
|
cached = load_parameter_tree_file(path, protobuf_types)
|
|
@@ -164,11 +187,20 @@ def fetch_parameter_tree(
|
|
|
164
187
|
return cached
|
|
165
188
|
logger.debug("[REQUEST] Failed to find parameter tree in the cache")
|
|
166
189
|
|
|
190
|
+
if callable(compressed):
|
|
191
|
+
compressed = compressed()
|
|
192
|
+
if compressed is False:
|
|
193
|
+
logger.debug("[REQUEST] server predates the compressed tree — requesting plain")
|
|
194
|
+
request_msg = protobuf_types.createType("motorcortex.GetParameterTreeMsg")
|
|
195
|
+
handle = send_and_recv(socket, protobuf_types.encode(request_msg), protobuf_types)
|
|
196
|
+
return save_parameter_tree_file(path, handle)
|
|
197
|
+
|
|
167
198
|
try:
|
|
168
199
|
compressed_request = protobuf_types.createType(
|
|
169
200
|
"motorcortex.GetParameterTreeCompressedMsg")
|
|
170
201
|
compressed_reply = send_and_recv(
|
|
171
|
-
socket, protobuf_types.encode(compressed_request), protobuf_types
|
|
202
|
+
socket, protobuf_types.encode(compressed_request), protobuf_types,
|
|
203
|
+
timeout_ms=None if compressed else CAPABILITY_PROBE_TIMEOUT_MS)
|
|
172
204
|
if compressed_reply is not None \
|
|
173
205
|
and getattr(compressed_reply, "status", 1) == 0 \
|
|
174
206
|
and getattr(compressed_reply, "compressed", b""):
|
|
@@ -209,6 +241,7 @@ def send_and_recv(
|
|
|
209
241
|
socket: Any,
|
|
210
242
|
encoded_msg: Any,
|
|
211
243
|
protobuf_types: Optional[Any],
|
|
244
|
+
timeout_ms: Optional[int] = None,
|
|
212
245
|
) -> Any:
|
|
213
246
|
"""One-shot synchronous request/reply over an nng Req0 socket.
|
|
214
247
|
|
|
@@ -225,6 +258,12 @@ def send_and_recv(
|
|
|
225
258
|
encoded_msg: Bytes to send.
|
|
226
259
|
protobuf_types: If truthy, decoded wire reply is returned.
|
|
227
260
|
Otherwise the raw buffer is returned.
|
|
261
|
+
timeout_ms: Receive timeout for this one request, set on the nng
|
|
262
|
+
context so the worker thread is released when it expires
|
|
263
|
+
(``pynng.Timeout`` is raised). ``None`` keeps the socket's
|
|
264
|
+
``recv_timeout``. A ``Reply.get(timeout_ms=...)`` alone only
|
|
265
|
+
stops the caller waiting — the worker stays blocked in
|
|
266
|
+
``recv()`` for as long as the socket timeout allows.
|
|
228
267
|
|
|
229
268
|
Returns:
|
|
230
269
|
Decoded reply (when ``protobuf_types`` supplied and buffer is
|
|
@@ -232,6 +271,8 @@ def send_and_recv(
|
|
|
232
271
|
"""
|
|
233
272
|
ctx = socket.new_context()
|
|
234
273
|
try:
|
|
274
|
+
if timeout_ms is not None:
|
|
275
|
+
check_err(lib.nng_ctx_set_ms(ctx.context, b"recv-timeout", timeout_ms))
|
|
235
276
|
ctx.send(encoded_msg)
|
|
236
277
|
buffer = ctx.recv()
|
|
237
278
|
if buffer:
|
|
@@ -239,7 +280,10 @@ def send_and_recv(
|
|
|
239
280
|
return protobuf_types.decode(buffer)
|
|
240
281
|
return buffer
|
|
241
282
|
except Exception as e:
|
|
242
|
-
|
|
283
|
+
# A capped request timing out is an expected probe outcome on old
|
|
284
|
+
# servers, not an error worth shouting about.
|
|
285
|
+
log = logger.debug if timeout_ms is not None else logger.error
|
|
286
|
+
log("[SEND] Error during send/recv: %s: %s", type(e).__name__, e)
|
|
243
287
|
raise
|
|
244
288
|
finally:
|
|
245
289
|
ctx.close()
|
|
@@ -344,12 +344,16 @@ class Request:
|
|
|
344
344
|
self._callback_handler.stop()
|
|
345
345
|
self._pool.shutdown(wait=True)
|
|
346
346
|
|
|
347
|
-
def send(self, encoded_msg: Any, do_not_decode_reply: bool = False
|
|
347
|
+
def send(self, encoded_msg: Any, do_not_decode_reply: bool = False,
|
|
348
|
+
timeout_ms: Optional[int] = None) -> "Reply[Any]":
|
|
348
349
|
"""Send an encoded message to the server.
|
|
349
350
|
|
|
350
351
|
Args:
|
|
351
352
|
encoded_msg: Encoded protobuf message.
|
|
352
353
|
do_not_decode_reply: If True, do not decode the reply.
|
|
354
|
+
timeout_ms: Receive timeout for this request only; on expiry
|
|
355
|
+
the reply fails with ``pynng.Timeout``. Defaults to the
|
|
356
|
+
socket's ``recv_timeout_ms``.
|
|
353
357
|
|
|
354
358
|
Returns:
|
|
355
359
|
Reply: A promise for the server's reply.
|
|
@@ -368,7 +372,7 @@ class Request:
|
|
|
368
372
|
)
|
|
369
373
|
return Reply(self._pool.submit(
|
|
370
374
|
_request_utils.send_and_recv, self._socket, encoded_msg,
|
|
371
|
-
None if do_not_decode_reply else self._protobuf_types,
|
|
375
|
+
None if do_not_decode_reply else self._protobuf_types, timeout_ms,
|
|
372
376
|
))
|
|
373
377
|
|
|
374
378
|
def login(self, login: str, password: str) -> "Reply[_pb.StatusMsg]":
|
|
@@ -412,7 +416,7 @@ class Request:
|
|
|
412
416
|
# encoding and sending data
|
|
413
417
|
return self.send(self._protobuf_types.encode(param_tree_hash_msg))
|
|
414
418
|
|
|
415
|
-
def getSubCookie(self) -> "Reply[_pb.SubCookieMsg]":
|
|
419
|
+
def getSubCookie(self, timeout_ms: Optional[int] = None) -> "Reply[_pb.SubCookieMsg]":
|
|
416
420
|
"""Request a single-use pub/sub identity cookie (internal plumbing).
|
|
417
421
|
|
|
418
422
|
Doubles as the routing capability probe: old servers (<2.36.1)
|
|
@@ -420,16 +424,31 @@ class Request:
|
|
|
420
424
|
FAILED — both mean "no routing", not an error. Used by Subscribe
|
|
421
425
|
for the data-channel hello; not needed in application code.
|
|
422
426
|
|
|
427
|
+
Args:
|
|
428
|
+
timeout_ms: Receive timeout for this request (see ``send``).
|
|
429
|
+
|
|
423
430
|
Returns:
|
|
424
431
|
Reply: A promise for the sub cookie.
|
|
425
432
|
"""
|
|
426
433
|
msg = self._protobuf_types.createType('motorcortex.GetSubCookieMsg')
|
|
427
|
-
return self.send(self._protobuf_types.encode(msg))
|
|
434
|
+
return self.send(self._protobuf_types.encode(msg), timeout_ms=timeout_ms)
|
|
428
435
|
|
|
429
|
-
def getParameterTree(
|
|
436
|
+
def getParameterTree(
|
|
437
|
+
self,
|
|
438
|
+
compressed: Union[Optional[bool], Callable[[], Optional[bool]]] = None,
|
|
439
|
+
) -> "Reply[_pb.ParameterTreeMsg]":
|
|
430
440
|
"""
|
|
431
441
|
Request a parameter tree from the server.
|
|
432
442
|
|
|
443
|
+
Args:
|
|
444
|
+
compressed: Server-capability hint for the compressed tree
|
|
445
|
+
transport: ``True`` / ``False`` if the routing-cookie
|
|
446
|
+
probe showed the server is / is not >= 2.36, ``None``
|
|
447
|
+
(default) to try it with a short probe timeout, or a
|
|
448
|
+
callable returning one of those, evaluated only on a
|
|
449
|
+
cache miss. See
|
|
450
|
+
:func:`motorcortex._request_utils.fetch_parameter_tree`.
|
|
451
|
+
|
|
433
452
|
Returns:
|
|
434
453
|
Reply: A promise for the parameter tree.
|
|
435
454
|
"""
|
|
@@ -437,6 +456,7 @@ class Request:
|
|
|
437
456
|
return Reply(self._pool.submit(
|
|
438
457
|
self._getParameterTree,
|
|
439
458
|
self.getParameterTreeHash(), self._protobuf_types, self._socket, self._url,
|
|
459
|
+
compressed,
|
|
440
460
|
))
|
|
441
461
|
|
|
442
462
|
def save(self, path: str, file_name: str) -> "Reply[_pb.StatusMsg]":
|
|
@@ -692,6 +712,7 @@ class Request:
|
|
|
692
712
|
protobuf_types: MessageTypes,
|
|
693
713
|
socket: Req0,
|
|
694
714
|
url: Optional[str] = None,
|
|
715
|
+
compressed: Union[Optional[bool], Callable[[], Optional[bool]]] = None,
|
|
695
716
|
) -> Any:
|
|
696
717
|
"""Thunk that unwraps ``hash_reply`` and delegates to
|
|
697
718
|
:func:`motorcortex._request_utils.fetch_parameter_tree`.
|
|
@@ -702,6 +723,6 @@ class Request:
|
|
|
702
723
|
"""
|
|
703
724
|
tree_hash = hash_reply.get()
|
|
704
725
|
return _request_utils.fetch_parameter_tree(
|
|
705
|
-
tree_hash.hash, protobuf_types, socket, url,
|
|
726
|
+
tree_hash.hash, protobuf_types, socket, url, compressed,
|
|
706
727
|
)
|
|
707
728
|
|
|
@@ -25,10 +25,10 @@ if TYPE_CHECKING: # pragma: no cover
|
|
|
25
25
|
from motorcortex.message_types import MessageTypes
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
#:
|
|
29
|
-
#:
|
|
30
|
-
#:
|
|
31
|
-
CAPABILITY_PROBE_TIMEOUT_MS =
|
|
28
|
+
#: Re-exported from ``_request_utils`` (shared with the compressed-tree
|
|
29
|
+
#: probe) — old servers silently drop unknown request hashes, so a
|
|
30
|
+
#: pre-2.36.1 server costs at most 2 s per connect attempt.
|
|
31
|
+
CAPABILITY_PROBE_TIMEOUT_MS = _request_utils.CAPABILITY_PROBE_TIMEOUT_MS
|
|
32
32
|
|
|
33
33
|
#: First upstream message on a routed data-channel stream: magic + the
|
|
34
34
|
#: single-use cookie fetched via ``Request.getSubCookie``.
|
|
@@ -39,7 +39,12 @@ def _fetch_cookie(req: "Request") -> Optional[str]:
|
|
|
39
39
|
"""Best-effort single-use cookie fetch. None is not an error — it
|
|
40
40
|
selects the anonymous (pre-routing) subscribe path."""
|
|
41
41
|
try:
|
|
42
|
-
|
|
42
|
+
# The cap goes on the nng context, not only on Reply.get: an old
|
|
43
|
+
# server never answers, and with the default infinite socket
|
|
44
|
+
# recv_timeout the request worker would stay blocked for good —
|
|
45
|
+
# one leaked worker per (re)connect until the pool is exhausted.
|
|
46
|
+
msg = req.getSubCookie(timeout_ms=CAPABILITY_PROBE_TIMEOUT_MS).get(
|
|
47
|
+
timeout_ms=CAPABILITY_PROBE_TIMEOUT_MS + 1000)
|
|
43
48
|
except Exception as e:
|
|
44
49
|
logger.debug("[SUBSCRIBE] cookie probe failed/timed out: %s", e)
|
|
45
50
|
return None
|
|
@@ -117,6 +122,17 @@ class Subscribe:
|
|
|
117
122
|
self._url: Optional[str] = None
|
|
118
123
|
self._tls_config: Optional[TLSConfig] = None
|
|
119
124
|
self._conn_timeout_ms: int = 1000
|
|
125
|
+
#: Outcome of the last conclusive cookie probe: True if the server
|
|
126
|
+
#: handed out a cookie (core >= 2.36, which also has the compressed
|
|
127
|
+
#: tree handler), False if it did not, None if never probed. Passed
|
|
128
|
+
#: to ``Request.getParameterTree`` as the compressed-tree hint.
|
|
129
|
+
self._server_has_routing: Optional[bool] = None
|
|
130
|
+
#: Resolves with the initial dial's outcome, before the cookie
|
|
131
|
+
#: probe and hello. ``motorcortex.connect`` waits on this instead
|
|
132
|
+
#: of the full ``connect()`` Reply so the ~2 s probe against an
|
|
133
|
+
#: old server overlaps login and a cached tree load (mirrors
|
|
134
|
+
#: motorcortex-js, which backgrounds the routing hello).
|
|
135
|
+
self._dialed: "Future[bool]" = Future()
|
|
120
136
|
self._req: Request = req
|
|
121
137
|
self._protobuf_types: Any = protobuf_types
|
|
122
138
|
self._subscriptions: Dict[int, Subscription] = dict()
|
|
@@ -247,11 +263,19 @@ class Subscribe:
|
|
|
247
263
|
return Reply(future)
|
|
248
264
|
|
|
249
265
|
def _initial_connect(self) -> bool:
|
|
266
|
+
try:
|
|
267
|
+
return self._initial_connect_body()
|
|
268
|
+
finally:
|
|
269
|
+
# Every exit path must resolve it — connect() callers block on it.
|
|
270
|
+
if not self._dialed.done():
|
|
271
|
+
self._dialed.set_result(False)
|
|
272
|
+
|
|
273
|
+
def _initial_connect_body(self) -> bool:
|
|
250
274
|
if self._closed:
|
|
251
275
|
# close() beat this pool worker to it before it even started.
|
|
252
276
|
return False
|
|
253
277
|
|
|
254
|
-
ok = self._dial_and_hello(allow_cookie=True)
|
|
278
|
+
ok = self._dial_and_hello(allow_cookie=True, dialed=self._dialed)
|
|
255
279
|
|
|
256
280
|
if not ok:
|
|
257
281
|
self._connection_state = ConnectionState.CONNECTION_FAILED
|
|
@@ -276,7 +300,8 @@ class Subscribe:
|
|
|
276
300
|
return False
|
|
277
301
|
return True
|
|
278
302
|
|
|
279
|
-
def _dial_and_hello(self, allow_cookie: bool
|
|
303
|
+
def _dial_and_hello(self, allow_cookie: bool,
|
|
304
|
+
dialed: "Optional[Future[bool]]" = None) -> bool:
|
|
280
305
|
"""Dial FIRST, then fetch the single-use cookie, then hello.
|
|
281
306
|
|
|
282
307
|
Fetch-first would burn a cookie on every attempt against a dead
|
|
@@ -299,6 +324,8 @@ class Subscribe:
|
|
|
299
324
|
except Exception as e:
|
|
300
325
|
logger.debug("[SUBSCRIBE] dial failed: %s", e)
|
|
301
326
|
return False
|
|
327
|
+
if dialed is not None:
|
|
328
|
+
dialed.set_result(True)
|
|
302
329
|
|
|
303
330
|
cookie = None
|
|
304
331
|
if allow_cookie:
|
|
@@ -320,6 +347,7 @@ class Subscribe:
|
|
|
320
347
|
self._req.connectionState().name)
|
|
321
348
|
self._stream.close()
|
|
322
349
|
return False
|
|
350
|
+
self._server_has_routing = cookie is not None
|
|
323
351
|
if cookie:
|
|
324
352
|
try:
|
|
325
353
|
# Fire-and-forget by design (no ack on the wire): a send
|
|
@@ -469,7 +497,6 @@ class Subscribe:
|
|
|
469
497
|
|
|
470
498
|
self._connection_state = ConnectionState.DISCONNECTING
|
|
471
499
|
for s in self._subscriptions.values():
|
|
472
|
-
s.done()
|
|
473
500
|
s.stop() # stop the ordered dispatch consumer thread
|
|
474
501
|
self._subscriptions.clear()
|
|
475
502
|
|
|
@@ -525,8 +552,6 @@ class Subscribe:
|
|
|
525
552
|
# find and remove subscription
|
|
526
553
|
if sub_id in self._subscriptions:
|
|
527
554
|
sub = self._subscriptions[sub_id]
|
|
528
|
-
# stop sub update thread
|
|
529
|
-
sub.done()
|
|
530
555
|
sub.stop() # stop the ordered dispatch consumer thread
|
|
531
556
|
del self._subscriptions[sub_id]
|
|
532
557
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '1.2.1'
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '1.2.0'
|
|
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
|
{motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex_python.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{motorcortex_python-1.2.0 → motorcortex_python-1.2.1}/motorcortex_python.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|