remoteRF-server 1.1.0.dev48__py3-none-any.whl → 1.1.0.dev50__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.
@@ -54,22 +54,39 @@ logger = logging.getLogger("remoteRF_server.direct")
54
54
  async def close_ice(ice) -> None:
55
55
  """Close an aioice Connection without the library's after-close noise.
56
56
 
57
- aioice leaves STUN retry timers armed and connectivity-check tasks waiting
58
- on futures nobody resolves; the timers then fire on closed sockets and the
59
- tasks die "pending" when the loop stops. Fail those transactions first so
60
- everything finishes cleanly, then close."""
61
- from aioice import stun
62
-
57
+ A cancelled ``connect()`` (a punch that timed out) never reaches aioice's
58
+ own "cancel remaining checks" loop, so its check tasks keep running and
59
+ its STUN timers stay armed against sockets close() is about to take. Stop
60
+ both before closing."""
61
+ # Cancel the connectivity checks first. Failing their STUN transactions
62
+ # instead only makes aioice start replacement checks, which then hang on
63
+ # a closed socket and die "pending" when they are collected.
64
+ checks = [pair.task for pair in list(getattr(ice, "_check_list", [])) if pair.task is not None]
65
+ for task in checks:
66
+ task.cancel()
67
+ if checks:
68
+ await asyncio.gather(*checks, return_exceptions=True)
63
69
  for protocol in list(getattr(ice, "_protocols", [])):
64
70
  for transaction in list(getattr(protocol, "transactions", {}).values()):
65
71
  handle = getattr(transaction, "_Transaction__timeout_handle", None)
66
72
  if handle is not None:
67
- handle.cancel()
68
- future = getattr(transaction, "_Transaction__future", None)
69
- if future is not None and not future.done():
70
- future.set_exception(stun.TransactionTimeout())
73
+ handle.cancel() # a retry would fire on the socket close() is about to take
71
74
  await ice.close()
72
- await asyncio.sleep(0) # let the check tasks see their failures and exit
75
+
76
+
77
+ async def cancel_tasks(tasks) -> None:
78
+ """Cancel these tasks and wait for them to finish.
79
+
80
+ A task that is cancelled but never awaited can be collected while still
81
+ pending, and Python prints a traceback for it that nobody can act on.
82
+ The caller's own task is never cancelled, so this is safe to call from
83
+ inside one of the tasks being torn down."""
84
+ current = asyncio.current_task()
85
+ tasks = [task for task in tasks if task is not None and task is not current and not task.done()]
86
+ for task in tasks:
87
+ task.cancel()
88
+ if tasks:
89
+ await asyncio.gather(*tasks, return_exceptions=True)
73
90
 
74
91
 
75
92
  def quiet_aioice_errors(loop) -> None:
@@ -246,6 +263,7 @@ class _Path:
246
263
  self.calls = 0
247
264
  self.task: Optional[asyncio.Task] = None
248
265
  self.transport: Optional[_IceTransport] = None
266
+ self.streams: set[asyncio.Task] = set()
249
267
 
250
268
  async def run(self) -> None:
251
269
  protocol = None
@@ -264,8 +282,7 @@ class _Path:
264
282
  printf(f"Direct path open: {self.user} from {self.peer[0]}:{self.peer[1]} ({where})", Sty.GRAY)
265
283
  await asyncio.wait({pump, closed}, return_when=asyncio.FIRST_COMPLETED)
266
284
  finally:
267
- pump.cancel()
268
- closed.cancel()
285
+ await cancel_tasks((pump, closed))
269
286
  except Exception as exc: # noqa: BLE001 - a failed punch is expected on some networks
270
287
  printf(f"Direct path failed: {self.user}: {type(exc).__name__}: {exc}", Sty.GRAY)
271
288
  finally:
@@ -306,7 +323,11 @@ class _Path:
306
323
  protocol.datagram_received(data, self.peer)
307
324
 
308
325
  def _stream(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
309
- asyncio.ensure_future(self._serve(reader, writer))
326
+ # Tracked, not fire-and-forget: a stream holds a device call, and the
327
+ # path may close under it.
328
+ task = asyncio.ensure_future(self._serve(reader, writer))
329
+ self.streams.add(task)
330
+ task.add_done_callback(self.streams.discard)
310
331
 
311
332
  async def _serve(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
312
333
  bad_frame = None
@@ -342,6 +363,7 @@ class _Path:
342
363
  task.cancel()
343
364
  with contextlib.suppress(asyncio.CancelledError):
344
365
  await task
366
+ await cancel_tasks(list(self.streams))
345
367
  await close_ice(self.ice)
346
368
 
347
369
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoteRF-server
3
- Version: 1.1.0.dev48
3
+ Version: 1.1.0.dev50
4
4
  Summary: RemoteRF server-side control package
5
5
  License: GPL-3.0-or-later
6
6
  Classifier: Programming Language :: Python :: 3
@@ -56,7 +56,7 @@ remoteRF_server/server/commands.py,sha256=ccb_Ab1mbIkosBsFGJnDSccrjWtR_Lymvvn3Iq
56
56
  remoteRF_server/server/console.py,sha256=J91LfwPVmBgielOxfE740NOYduekggyZkqi-47y_A4w,4472
57
57
  remoteRF_server/server/control.py,sha256=Uq1Fk0zIoHHX_EXfz3OuNle8urvO_UhAiB5Lk5cs2e4,5177
58
58
  remoteRF_server/server/device_manager.py,sha256=PPuCwlPSmjfVpCqy_hCaOr9WvFgE8mqtLMhWOAgXNDA,39281
59
- remoteRF_server/server/direct_path.py,sha256=ry4wRbqMQdSSh-HVFlbbMdTCNM0XksTl-h9MORJ0PLM,16781
59
+ remoteRF_server/server/direct_path.py,sha256=TrL0aWCUXN9Lmuww-v_AXtLIOqTkckm2vPW91xcnOLI,17875
60
60
  remoteRF_server/server/dynamic_v2.py,sha256=AMhQEFhNAvCNcF-PdfCCQQLmeoaR7s1Os8et1KFGn_8,70814
61
61
  remoteRF_server/server/global_onboarding.py,sha256=NcKG8TYhCJEiaCtdXL1jEgVm-R_xWpOIj5KI4bWfkr0,39942
62
62
  remoteRF_server/server/grpc_server.py,sha256=91FpJubzN_Wfuk6fW_owIEzNCukd333sAGW68FUU8pk,46787
@@ -90,9 +90,9 @@ remoteRF_server/tools/gen_certs.py,sha256=Vbyu8JjRu__UYPVmyCRXt5SOw0Za1HV7g1md3G
90
90
  remoteRF_server/tools/gist_status.py,sha256=SFv2iQgQrIKT9gxKrU-_Dr7S0p0rE4wQM6J3OC6FcoM,4649
91
91
  remoteRF_server/tools/gist_status_testing.py,sha256=IYw7UkPjAUpKDTtIlnYGb5g6SJkwz4TRGUfJJ6f4LhQ,2690
92
92
  remoteRF_server/tools/introspect_uhd.py,sha256=vUpPb0rM9tsHPxWadS3EfMDE-gMd-TvNV0TjrZOQqBY,4531
93
- remoterf_server-1.1.0.dev48.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
94
- remoterf_server-1.1.0.dev48.dist-info/METADATA,sha256=ZhdOC9w67CZe59Nn38BVHXVFsnyGESpLxDE5izAzi5s,42069
95
- remoterf_server-1.1.0.dev48.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
96
- remoterf_server-1.1.0.dev48.dist-info/entry_points.txt,sha256=_bbKk65PfYi-n6zUjqOiOwcalf_Ws1TCGATIe1TukxE,136
97
- remoterf_server-1.1.0.dev48.dist-info/top_level.txt,sha256=NuYWe8dSx3qdbxshWBwOgx68EHmJ9I8Zvxam4ARsQ_I,30
98
- remoterf_server-1.1.0.dev48.dist-info/RECORD,,
93
+ remoterf_server-1.1.0.dev50.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
94
+ remoterf_server-1.1.0.dev50.dist-info/METADATA,sha256=jy_xvhBrCSFGJ5x5I_ZBgDynd1Hwe7uFv-1KaC-Fs60,42069
95
+ remoterf_server-1.1.0.dev50.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
96
+ remoterf_server-1.1.0.dev50.dist-info/entry_points.txt,sha256=_bbKk65PfYi-n6zUjqOiOwcalf_Ws1TCGATIe1TukxE,136
97
+ remoterf_server-1.1.0.dev50.dist-info/top_level.txt,sha256=NuYWe8dSx3qdbxshWBwOgx68EHmJ9I8Zvxam4ARsQ_I,30
98
+ remoterf_server-1.1.0.dev50.dist-info/RECORD,,