phoenix-channels-python-client 0.4.3__tar.gz → 0.4.4__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.
- {phoenix_channels_python_client-0.4.3/phoenix_channels_python_client.egg-info → phoenix_channels_python_client-0.4.4}/PKG-INFO +1 -1
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/phoenix_channels_python_client/__init__.py +1 -1
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/phoenix_channels_python_client/client.py +2 -1
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4/phoenix_channels_python_client.egg-info}/PKG-INFO +1 -1
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/pyproject.toml +1 -1
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/tests/test_shutdown_races.py +107 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/LICENSE +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/MANIFEST.in +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/README.md +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/phoenix_channels_python_client/authentication.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/phoenix_channels_python_client/client_state_machine.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/phoenix_channels_python_client/client_types.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/phoenix_channels_python_client/default_decider.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/phoenix_channels_python_client/diagnostics.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/phoenix_channels_python_client/exceptions.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/phoenix_channels_python_client/phx_messages.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/phoenix_channels_python_client/protocol_handler.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/phoenix_channels_python_client/reconnect_controller.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/phoenix_channels_python_client/shutdown_signals.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/phoenix_channels_python_client/supervisor.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/phoenix_channels_python_client/topic_runtime.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/phoenix_channels_python_client/topic_subscription.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/phoenix_channels_python_client/utils.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/phoenix_channels_python_client.egg-info/SOURCES.txt +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/phoenix_channels_python_client.egg-info/dependency_links.txt +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/phoenix_channels_python_client.egg-info/requires.txt +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/phoenix_channels_python_client.egg-info/top_level.txt +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/setup.cfg +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/tests/test_cancel_and_wait.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/tests/test_channel_recovery.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/tests/test_cleanup_ownership.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/tests/test_client_state.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/tests/test_connection.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/tests/test_heartbeat.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/tests/test_heartbeat_timeout.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/tests/test_join_installation.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/tests/test_join_lifecycle.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/tests/test_join_responses.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/tests/test_optional_lifecycle.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/tests/test_optional_reconnect.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/tests/test_protocol_handler.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/tests/test_reconnect.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/tests/test_reconnect_decider.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/tests/test_reconnect_policy_invariants.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/tests/test_reconnect_stress.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/tests/test_run_forever_signals.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/tests/test_security.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/tests/test_subscriptions.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/tests/test_supervisor_internals.py +0 -0
- {phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/tests/test_topic_runtime_internals.py +0 -0
|
@@ -241,7 +241,8 @@ class PHXChannelsClient(SupervisorMixin, TopicRuntimeMixin, ReconnectControllerM
|
|
|
241
241
|
|
|
242
242
|
try:
|
|
243
243
|
await self._initial_connection_future
|
|
244
|
-
except Exception:
|
|
244
|
+
except (Exception, asyncio.CancelledError):
|
|
245
|
+
# Not BaseException: awaiting cleanup on GeneratorExit raises RuntimeError.
|
|
245
246
|
await self.shutdown("Initial connection failed")
|
|
246
247
|
raise
|
|
247
248
|
|
|
@@ -47,6 +47,9 @@ HELD_BACKOFF = derive_policy(
|
|
|
47
47
|
FAST_RECONNECT, base_delay_s=HELD_BACKOFF_S, max_delay_s=HELD_BACKOFF_S
|
|
48
48
|
)
|
|
49
49
|
|
|
50
|
+
# More refusals than any test reaches, so only the client's own stop ends them.
|
|
51
|
+
REFUSE_EVERY_HANDSHAKE = 1_000_000
|
|
52
|
+
|
|
50
53
|
|
|
51
54
|
class HeldCallback:
|
|
52
55
|
"""A topic callback that, once cancelled, holds its unwinding until released.
|
|
@@ -378,3 +381,107 @@ async def test_shutdown_during_a_close_still_closes_the_socket(
|
|
|
378
381
|
await asyncio.wait_for(client.shutdown(STOP_REASON), ASYNC_TIMEOUT_S)
|
|
379
382
|
|
|
380
383
|
assert socket.state is State.CLOSED
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
async def a_handshake_was_refused(server: FakePhoenixServer) -> None:
|
|
387
|
+
assert await wait_for_condition(lambda: server.refused_handshakes >= 1)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
async def expire_on_first_refusal(
|
|
391
|
+
server: FakePhoenixServer, client: PHXChannelsClient, deadline: asyncio.Timeout
|
|
392
|
+
) -> asyncio.Task[None] | None:
|
|
393
|
+
"""Expire ``deadline`` once a handshake was refused; return entry's supervisor."""
|
|
394
|
+
await a_handshake_was_refused(server)
|
|
395
|
+
supervisor = client._supervisor_task
|
|
396
|
+
deadline.reschedule(asyncio.get_running_loop().time())
|
|
397
|
+
return supervisor
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
async def time_out_entry_on_first_refusal(
|
|
401
|
+
server: FakePhoenixServer, client: PHXChannelsClient
|
|
402
|
+
) -> asyncio.Task[None] | None:
|
|
403
|
+
"""Enter under a deadline that expires on the first refused handshake.
|
|
404
|
+
|
|
405
|
+
Returns the supervisor entry started; fails unless the deadline ended entry.
|
|
406
|
+
The deadline starts at ``ASYNC_TIMEOUT_S``, so a missing refusal fails here
|
|
407
|
+
instead of leaving entry waiting forever.
|
|
408
|
+
"""
|
|
409
|
+
expiry: asyncio.Task[asyncio.Task[None] | None] | None = None
|
|
410
|
+
try:
|
|
411
|
+
async with asyncio.timeout(ASYNC_TIMEOUT_S) as deadline:
|
|
412
|
+
expiry = asyncio.create_task(
|
|
413
|
+
expire_on_first_refusal(server, client, deadline)
|
|
414
|
+
)
|
|
415
|
+
await client.__aenter__()
|
|
416
|
+
except TimeoutError:
|
|
417
|
+
assert expiry is not None
|
|
418
|
+
return expiry.result()
|
|
419
|
+
finally:
|
|
420
|
+
if expiry is not None:
|
|
421
|
+
expiry.cancel()
|
|
422
|
+
pytest.fail("entry ended before its deadline")
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
async def test_a_timed_out_entry_stops_connecting(
|
|
426
|
+
phoenix_server: FakePhoenixServer,
|
|
427
|
+
) -> None:
|
|
428
|
+
client = make_client(phoenix_server, reconnect_policy=FAST_RECONNECT)
|
|
429
|
+
try:
|
|
430
|
+
with phoenix_server.refusing(REFUSE_EVERY_HANDSHAKE):
|
|
431
|
+
supervisor = await time_out_entry_on_first_refusal(phoenix_server, client)
|
|
432
|
+
|
|
433
|
+
assert supervisor is not None
|
|
434
|
+
assert supervisor.done()
|
|
435
|
+
assert client._state is ClientState.CLOSED
|
|
436
|
+
|
|
437
|
+
async with client: # the stopped client enters again once the server accepts
|
|
438
|
+
assert client.connection is not None
|
|
439
|
+
finally:
|
|
440
|
+
await asyncio.wait_for(client.shutdown(STOP_REASON), ASYNC_TIMEOUT_S)
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
async def test_a_second_cancel_during_entry_cleanup_still_stops_connecting(
|
|
444
|
+
phoenix_server: FakePhoenixServer,
|
|
445
|
+
) -> None:
|
|
446
|
+
client = make_client(phoenix_server, reconnect_policy=FAST_RECONNECT)
|
|
447
|
+
try:
|
|
448
|
+
with phoenix_server.refusing(REFUSE_EVERY_HANDSHAKE):
|
|
449
|
+
entering = asyncio.create_task(client.__aenter__())
|
|
450
|
+
await a_handshake_was_refused(phoenix_server)
|
|
451
|
+
supervisor = client._supervisor_task
|
|
452
|
+
assert supervisor is not None
|
|
453
|
+
|
|
454
|
+
entering.cancel()
|
|
455
|
+
await asyncio.sleep(0) # the cancelled entry starts its cleanup shutdown
|
|
456
|
+
assert client._state is ClientState.SHUTTING_DOWN
|
|
457
|
+
|
|
458
|
+
entering.cancel()
|
|
459
|
+
with pytest.raises(asyncio.CancelledError):
|
|
460
|
+
await asyncio.wait_for(entering, ASYNC_TIMEOUT_S)
|
|
461
|
+
|
|
462
|
+
# Joins the cleanup the entry started; it was not abandoned.
|
|
463
|
+
await asyncio.wait_for(client.shutdown(STOP_REASON), ASYNC_TIMEOUT_S)
|
|
464
|
+
assert supervisor.done()
|
|
465
|
+
assert client._state is ClientState.CLOSED
|
|
466
|
+
assert client.connection is None
|
|
467
|
+
finally:
|
|
468
|
+
await asyncio.wait_for(client.shutdown(STOP_REASON), ASYNC_TIMEOUT_S)
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
async def test_closing_a_pending_entry_is_silent(
|
|
472
|
+
phoenix_server: FakePhoenixServer,
|
|
473
|
+
) -> None:
|
|
474
|
+
client = make_client(phoenix_server)
|
|
475
|
+
phoenix_server.handshake_gate.clear()
|
|
476
|
+
entry = client.__aenter__()
|
|
477
|
+
try:
|
|
478
|
+
entry.send(None) # suspends at the initial-connection await
|
|
479
|
+
entry.close() # raises if entry awaits while handling GeneratorExit
|
|
480
|
+
finally:
|
|
481
|
+
phoenix_server.handshake_gate.set()
|
|
482
|
+
await asyncio.wait_for(client.shutdown(STOP_REASON), ASYNC_TIMEOUT_S)
|
|
483
|
+
|
|
484
|
+
# The closed entry no longer awaits its outcome; retrieve it here.
|
|
485
|
+
initial_connection = client._initial_connection_future
|
|
486
|
+
assert initial_connection is not None
|
|
487
|
+
assert isinstance(initial_connection.exception(), PHXConnectionError)
|
|
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
|
|
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
|
{phoenix_channels_python_client-0.4.3 → phoenix_channels_python_client-0.4.4}/tests/test_security.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|