@@ -89,3 +89,54 @@ async def recording_listen(
8989 stock .close ()
9090 async for _event in sub :
9191 raise NotImplementedError # unreachable: nothing was published
92+
93+
94+ @requirement ("subscriptions:listen:client:concurrent-demux" )
95+ @requirement ("protocol:request-id:caller-supplied" )
96+ async def test_concurrent_listen_streams_each_receive_their_own_ack (connect : Connect ) -> None :
97+ """Two subscriptions opened concurrently each surface the honored filter of their own request:
98+ ack frames route by subscription id, not broadcast to every open route.
99+
100+ The server gates both acks until both listen requests have arrived, so both client routes are
101+ live and unacknowledged when the first ack lands - a client that broadcast subscription frames
102+ would cross-pollute that ack into both handles.
103+ """
104+ bus = InMemorySubscriptionBus ()
105+ stock = ListenHandler (bus )
106+ arrived : list [types .RequestId ] = []
107+ both_arrived = anyio .Event ()
108+
109+ async def gated_listen (
110+ ctx : ServerRequestContext [Any , Any ], params : types .SubscriptionsListenRequestParams
111+ ) -> types .SubscriptionsListenResult :
112+ assert ctx .request_id is not None
113+ arrived .append (ctx .request_id )
114+ if len (arrived ) == 2 :
115+ both_arrived .set ()
116+ with anyio .fail_after (10 ):
117+ await both_arrived .wait ()
118+ return await stock (ctx , params )
119+
120+ server = Server ("subs" , on_subscriptions_listen = gated_listen )
121+ honored : dict [str , types .SubscriptionFilter ] = {}
122+
123+ async with connect (server ) as client :
124+
125+ async def open_tools () -> None :
126+ async with client .listen (tools_list_changed = True ) as sub :
127+ honored ["tools" ] = sub .honored
128+
129+ async def open_prompts () -> None :
130+ async with client .listen (prompts_list_changed = True ) as sub :
131+ honored ["prompts" ] = sub .honored
132+
133+ with anyio .fail_after (10 ):
134+ async with anyio .create_task_group () as tg : # pragma: no branch
135+ tg .start_soon (open_tools )
136+ tg .start_soon (open_prompts )
137+
138+ assert honored == {
139+ "tools" : types .SubscriptionFilter (tools_list_changed = True ),
140+ "prompts" : types .SubscriptionFilter (prompts_list_changed = True ),
141+ }
142+ assert len (set (arrived )) == 2
0 commit comments