@@ -213,6 +213,7 @@ def __init__(
213213 * ,
214214 peer_cancel_mode : PeerCancelMode = "interrupt" ,
215215 raise_handler_exceptions : bool = False ,
216+ inline_methods : frozenset [str ] = frozenset (),
216217 ) -> None : ...
217218 @overload
218219 def __init__ (
@@ -223,6 +224,7 @@ def __init__(
223224 transport_builder : Callable [[MessageMetadata ], TransportT ],
224225 peer_cancel_mode : PeerCancelMode = "interrupt" ,
225226 raise_handler_exceptions : bool = False ,
227+ inline_methods : frozenset [str ] = frozenset (),
226228 ) -> None : ...
227229 def __init__ (
228230 self ,
@@ -232,6 +234,7 @@ def __init__(
232234 transport_builder : Callable [[MessageMetadata ], TransportT ] | None = None ,
233235 peer_cancel_mode : PeerCancelMode = "interrupt" ,
234236 raise_handler_exceptions : bool = False ,
237+ inline_methods : frozenset [str ] = frozenset (),
235238 ) -> None :
236239 self ._read_stream = read_stream
237240 self ._write_stream = write_stream
@@ -244,6 +247,13 @@ def __init__(
244247 )
245248 self ._peer_cancel_mode : PeerCancelMode = peer_cancel_mode
246249 self ._raise_handler_exceptions = raise_handler_exceptions
250+ self ._inline_methods = inline_methods
251+ """Request methods handled inline in the read loop (awaited before the
252+ next message is dequeued) instead of spawned concurrently. Use for
253+ methods whose side effects must be observable to the next message,
254+ e.g. `initialize`, so a pipelined follow-up sees the initialized state.
255+ Only suitable for handlers that complete quickly, since inline handling
256+ blocks dequeuing."""
247257
248258 self ._next_id = 0
249259 self ._pending : dict [RequestId , _Pending ] = {}
@@ -384,7 +394,7 @@ async def run(
384394 sender_ctx : contextvars .Context | None = getattr (
385395 self ._read_stream , "last_context" , None
386396 )
387- self ._dispatch (item , on_request , on_notify , sender_ctx )
397+ await self ._dispatch (item , on_request , on_notify , sender_ctx )
388398 except anyio .ClosedResourceError :
389399 # The transport closed our receive end and we looped
390400 # back to `__anext__` on the now-closed stream
@@ -409,17 +419,19 @@ async def run(
409419 self ._tg = None
410420 self ._fan_out_closed ()
411421
412- def _dispatch (
422+ async def _dispatch (
413423 self ,
414424 item : SessionMessage | Exception ,
415425 on_request : OnRequest ,
416426 on_notify : OnNotify ,
417427 sender_ctx : contextvars .Context | None ,
418428 ) -> None :
419- """Route one inbound item. Synchronous: never awaits.
429+ """Route one inbound item.
420430
421- Everything here is `send_nowait` or `_spawn`. An `await` would let one
422- slow message head-of-line block the entire read loop.
431+ Everything here is `send_nowait` or `_spawn`; the only `await` is for
432+ `inline_methods` requests, which deliberately block dequeuing until
433+ handled. Any other `await` would let one slow message head-of-line
434+ block the entire read loop.
423435 """
424436 if isinstance (item , Exception ):
425437 logger .debug ("transport yielded exception: %r" , item )
@@ -428,7 +440,7 @@ def _dispatch(
428440 msg = item .message
429441 match msg :
430442 case JSONRPCRequest ():
431- self ._dispatch_request (msg , metadata , on_request , sender_ctx )
443+ await self ._dispatch_request (msg , metadata , on_request , sender_ctx )
432444 case JSONRPCNotification ():
433445 self ._dispatch_notification (msg , metadata , on_notify , sender_ctx )
434446 case JSONRPCResponse ():
@@ -439,7 +451,7 @@ def _dispatch(
439451 # on this final case is unreachable.
440452 self ._resolve_pending (msg .id , msg .error )
441453
442- def _dispatch_request (
454+ async def _dispatch_request (
443455 self ,
444456 req : JSONRPCRequest ,
445457 metadata : MessageMetadata ,
@@ -462,7 +474,10 @@ def _dispatch_request(
462474 )
463475 scope = anyio .CancelScope ()
464476 self ._in_flight [req .id ] = _InFlight (scope = scope , dctx = dctx )
465- self ._spawn (self ._handle_request , req , dctx , scope , on_request , sender_ctx = sender_ctx )
477+ if req .method in self ._inline_methods :
478+ await self ._handle_request (req , dctx , scope , on_request )
479+ else :
480+ self ._spawn (self ._handle_request , req , dctx , scope , on_request , sender_ctx = sender_ctx )
466481
467482 def _dispatch_notification (
468483 self ,
0 commit comments