@@ -764,6 +764,54 @@ async def slow_cleanup(error):
764764 finally :
765765 _cleanup_wrapper (cli )
766766
767+ @pytest .mark .asyncio
768+ async def test_dispose_waits_for_cleanup_after_state_flips_disconnected (self ):
769+ # `_cleanup()` flips state to DISCONNECTED before awaiting
770+ # `process.wait()`. dispose() must still wait for that cleanup task
771+ # instead of short-circuiting and returning while teardown is in
772+ # flight.
773+ cli = _mock_cli_bin ({'handshake' : 'ok' })
774+ try :
775+ transport = AsyncHostTransport (cli , startup_timeout_ms = 5_000 )
776+ await transport .connect ()
777+ process = transport ._process
778+ assert process is not None
779+
780+ wait_started = asyncio .Event ()
781+ release = asyncio .Event ()
782+ real_wait = process .wait
783+
784+ async def slow_wait ():
785+ wait_started .set ()
786+ await release .wait ()
787+ return await real_wait ()
788+
789+ process .wait = slow_wait # type: ignore[assignment]
790+
791+ transport ._schedule_cleanup (
792+ SuperDocError ('reader-overflow' , code = HOST_PROTOCOL_ERROR ),
793+ )
794+ cleanup_task = transport ._cleanup_task
795+ assert cleanup_task is not None
796+
797+ await asyncio .wait_for (wait_started .wait (), timeout = 2.0 )
798+ assert transport .state == 'DISCONNECTED'
799+ assert transport ._process is None
800+ assert not cleanup_task .done ()
801+
802+ dispose_task = asyncio .create_task (transport .dispose ())
803+ await asyncio .sleep (0.05 )
804+ assert not dispose_task .done ()
805+
806+ release .set ()
807+ await dispose_task
808+ assert transport .state == 'DISCONNECTED'
809+ assert transport ._cleanup_task is None
810+ await process .wait ()
811+ assert process .returncode is not None
812+ finally :
813+ _cleanup_wrapper (cli )
814+
767815 @pytest .mark .asyncio
768816 async def test_ensure_connected_drains_in_flight_cleanup_before_spawn (self ):
769817 # Round-3 regression: without this drain, `_start_host` reassigns
0 commit comments