@@ -185,6 +185,30 @@ let debug = debuglog("net", (fn) => {
185185const kLastWriteQueueSize = Symbol ( "lastWriteQueueSize" ) ;
186186const kBytesRead = Symbol ( "kBytesRead" ) ;
187187const kBytesWritten = Symbol ( "kBytesWritten" ) ;
188+ // Holds the async context (AsyncLocalStorage / async_hooks) snapshot captured
189+ // when a connect request or listening handle is set up. The native libuv
190+ // callbacks in tcp_wrap/pipe_wrap invoke the completion callbacks directly,
191+ // outside of any microtask or nextTick, so the snapshot has to be captured at
192+ // registration time and restored before dispatching. Otherwise
193+ // `AsyncLocalStorage.getStore()` returns `undefined` inside `connect` and
194+ // `connection` handlers. See https://github.com/denoland/deno/issues/35154.
195+ const kAsyncContext = Symbol ( "kAsyncContext" ) ;
196+
197+ // Runs `run` with the async context snapshot that was active when the
198+ // operation was initiated, restoring the previous context afterwards.
199+ function _runInAsyncContext ( snapshot : any , run : ( ) => void ) {
200+ if ( snapshot === undefined ) {
201+ run ( ) ;
202+ return ;
203+ }
204+ const prior = core . getAsyncContext ( ) ;
205+ core . setAsyncContext ( snapshot ) ;
206+ try {
207+ run ( ) ;
208+ } finally {
209+ core . setAsyncContext ( prior ) ;
210+ }
211+ }
188212
189213const DEFAULT_IPV4_ADDR = "0.0.0.0" ;
190214const DEFAULT_IPV6_ADDR = "::" ;
@@ -468,6 +492,19 @@ function _afterConnect(
468492 req : PipeConnectWrap | TCPConnectWrap ,
469493 readable : boolean ,
470494 writable : boolean ,
495+ ) {
496+ _runInAsyncContext (
497+ handle ?. [ kAsyncContext ] ,
498+ ( ) => _afterConnectImpl ( status , handle , req , readable , writable ) ,
499+ ) ;
500+ }
501+
502+ function _afterConnectImpl (
503+ status : number ,
504+ handle : any ,
505+ req : PipeConnectWrap | TCPConnectWrap ,
506+ readable : boolean ,
507+ writable : boolean ,
471508) {
472509 let socket = handle [ ownerSymbol ] ;
473510
@@ -1619,6 +1656,11 @@ Socket.prototype.connect = function (...args) {
16191656 _initSocketHandle ( this ) ;
16201657 }
16211658
1659+ // Capture the async context now, while we are still running synchronously
1660+ // inside the caller's context. The DNS lookup that precedes the actual
1661+ // connect is async and would otherwise drop it before `_afterConnect` runs.
1662+ this . _handle [ kAsyncContext ] = core . getAsyncContext ( ) ;
1663+
16221664 if ( cb !== null ) {
16231665 this . once ( "connect" , cb ) ;
16241666 }
@@ -2568,6 +2610,15 @@ function _emitListeningNT(server: Server) {
25682610}
25692611
25702612function _onconnection ( this : any , err : number , clientHandle ?: Handle ) {
2613+ // deno-lint-ignore no-this-alias
2614+ const handle = this ;
2615+ _runInAsyncContext (
2616+ handle [ kAsyncContext ] ,
2617+ ( ) => FunctionPrototypeCall ( _onconnectionImpl , handle , err , clientHandle ) ,
2618+ ) ;
2619+ }
2620+
2621+ function _onconnectionImpl ( this : any , err : number , clientHandle ?: Handle ) {
25712622 // deno-lint-ignore no-this-alias
25722623 const handle = this ;
25732624 const self = handle [ ownerSymbol ] ;
@@ -2684,6 +2735,7 @@ function _setupListenHandle(
26842735
26852736 this [ asyncIdSymbol ] = _getNewAsyncId ( this . _handle ) ;
26862737 this . _handle . onconnection = _onconnection ;
2738+ this . _handle [ kAsyncContext ] = core . getAsyncContext ( ) ;
26872739 this . _handle [ ownerSymbol ] = this ;
26882740
26892741 // For TCP and Pipe handles, wrap the onconnection callback to create
0 commit comments