@@ -90,7 +90,7 @@ maybeSkip(describe)('AsyncHooks-based CLS', () => {
9090 } , 'modified' ) ;
9191 } ) ;
9292
93- describe ( 'Using AsyncResource API' , ( ) => {
93+ describe ( 'Compatibility with AsyncResource API' , ( ) => {
9494 it ( 'Supports context propagation without trigger ID' , async ( ) => {
9595 let res ! : asyncHooksModule . AsyncResource ;
9696 await cls . runWithContext ( async ( ) => {
@@ -115,4 +115,111 @@ maybeSkip(describe)('AsyncHooks-based CLS', () => {
115115 } ) ;
116116 } ) ;
117117 } ) ;
118+
119+ describe ( 'Memory consumption with Promises' , ( ) => {
120+ const createdPromiseIDs : number [ ] = [ ] ;
121+ let hook : asyncHooksModule . AsyncHook ;
122+
123+ before ( ( ) => {
124+ hook = asyncHooks
125+ . createHook ( {
126+ init : ( uid : number , type : string ) => {
127+ if ( type === 'PROMISE' ) {
128+ createdPromiseIDs . push ( uid ) ;
129+ }
130+ }
131+ } )
132+ . enable ( ) ;
133+ } ) ;
134+
135+ after ( ( ) => {
136+ hook . disable ( ) ;
137+ } ) ;
138+
139+ const testCases :
140+ Array < { description : string ; skip ?: boolean ; fn : ( ) => { } } > = [
141+ { description : 'a no-op async function' , fn : async ( ) => { } } , {
142+ description : 'an async function that throws' ,
143+ fn : async ( ) => {
144+ throw new Error ( ) ;
145+ }
146+ } ,
147+ {
148+ description : 'an async function that awaits a rejected value' ,
149+ fn : async ( ) => {
150+ await new Promise ( reject => setImmediate ( reject ) ) ;
151+ }
152+ } ,
153+ {
154+ description : 'an async function with awaited values' ,
155+ fn : async ( ) => {
156+ await 0 ;
157+ await new Promise ( resolve => resolve ( ) ) ;
158+ await new Promise ( resolve => setImmediate ( resolve ) ) ;
159+ }
160+ } ,
161+ {
162+ description : 'an async function that awaits another async function' ,
163+ fn : async ( ) => {
164+ await ( async ( ) => {
165+ await Promise . resolve ( ) ;
166+ } ) ( ) ;
167+ }
168+ } ,
169+ {
170+ description : 'a plain function that returns a Promise' ,
171+ fn : ( ) => Promise . resolve ( )
172+ } ,
173+ {
174+ description :
175+ 'a plain function that returns a Promise that will reject' ,
176+ fn : ( ) => Promise . reject ( )
177+ } ,
178+ {
179+ description : 'an async function with spread args' ,
180+ // TODO(kjin): A possible bug in exists that causes an extra Promise
181+ // async resource to be initialized when an async function with
182+ // spread args is invoked. promiseResolve is not called for this
183+ // async resource. Fix this bug and then remove this skip directive.
184+ skip : true ,
185+ fn : async ( ...args : number [ ] ) => args
186+ }
187+ ] ;
188+
189+ for ( const testCase of testCases ) {
190+ const skipIfTestSpecifies = ! ! testCase . skip ? it . skip : it ;
191+ skipIfTestSpecifies (
192+ `Doesn't retain stale references when running ${
193+ testCase . description } in a context`,
194+ async ( ) => {
195+ createdPromiseIDs . length = 0 ;
196+ try {
197+ // Run the test function in a new context.
198+ await cls . runWithContext ( testCase . fn , 'will-be-stale' ) ;
199+ } catch ( e ) {
200+ // Ignore errors; they aren't important for this test.
201+ } finally {
202+ // At this point, Promises created from invoking the test function
203+ // should have had either their destroy or promiseResolve hook
204+ // called. We observe this by iterating through the Promises that
205+ // were created in this context, and checking to see that getting
206+ // the current context in the scope of an async resource that
207+ // references any of these Promises as its trigger parent doesn't
208+ // yield the stale context value from before. The only way this is
209+ // possible is if the CLS implementation internally kept a stale
210+ // reference to a context-local value keyed on the ID of a PROMISE
211+ // resource that should have been disposed.
212+ const stalePromiseIDs = createdPromiseIDs . filter ( ( id ) => {
213+ const a = new AsyncResource ( 'test' , id ) ;
214+ const result = a . runInAsyncScope ( ( ) => {
215+ return cls . getContext ( ) === 'will-be-stale' ;
216+ } ) ;
217+ a . emitDestroy ( ) ;
218+ return result ;
219+ } ) ;
220+ assert . strictEqual ( stalePromiseIDs . length , 0 ) ;
221+ }
222+ } ) ;
223+ }
224+ } ) ;
118225} ) ;
0 commit comments