@@ -38,6 +38,7 @@ const enum State {
3838 * ```
3939 */
4040const emnapiTSFN = {
41+ _liveSet : { } as Set < number > ,
4142 offset : {
4243 __size__ : 0 ,
4344 /* napi_ref */ resource : 0 ,
@@ -65,6 +66,7 @@ const emnapiTSFN = {
6566 /* int32_t */ cond : 0 ,
6667 } ,
6768 init ( ) {
69+ emnapiTSFN . _liveSet = new Set < number > ( )
6870// #if MEMORY64
6971 emnapiTSFN . offset . __size__ = NapiTSFNOffset64 . __size__
7072 emnapiTSFN . offset . resource = NapiTSFNOffset64 . async_resource_resource
@@ -530,6 +532,7 @@ const emnapiTSFN = {
530532 }
531533 } ,
532534 destroy ( func : number ) {
535+ emnapiTSFN . _liveSet . delete ( func )
533536 emnapiTSFN . destroyQueue ( func )
534537 emnapiTSFN . releaseResources ( func )
535538 _free ( to64 ( 'func' ) as number )
@@ -748,6 +751,9 @@ const emnapiTSFN = {
748751 // actual TSFN drain after nearby Send/Signal calls have had a chance to
749752 // collapse into the shared AsyncProgressWorker state.
750753 emnapiCtx . features . setImmediate ( ( ) => {
754+ if ( ! emnapiTSFN . _liveSet . has ( func ) ) {
755+ return
756+ }
751757 if ( Atomics . load ( i32a , pending >>> 2 ) === 0 ) {
752758 Atomics . store ( i32a , scheduled >>> 2 , 0 )
753759 return
@@ -760,13 +766,20 @@ const emnapiTSFN = {
760766 if ( Atomics . exchange ( i32a , pending >>> 2 , 0 ) === 0 ) {
761767 return
762768 }
769+ // After destroy(), the func address is freed. Skip dispatch
770+ // to avoid use-after-free (JS-side lifecycle check).
771+ if ( ! emnapiTSFN . _liveSet . has ( func ) ) {
772+ return
773+ }
763774 emnapiTSFN . dispatch ( func )
764775 } finally {
765776 // Allow a later wakeup to schedule a new drain chain. If another
766777 // worker-thread send raced with this drain, enqueue one more turn.
767- Atomics . store ( i32a , scheduled >>> 2 , 0 )
768- if ( Atomics . load ( i32a , pending >>> 2 ) !== 0 ) {
769- emnapiTSFN . enqueue ( func )
778+ if ( emnapiTSFN . _liveSet . has ( func ) ) {
779+ Atomics . store ( i32a , scheduled >>> 2 , 0 )
780+ if ( Atomics . load ( i32a , pending >>> 2 ) !== 0 ) {
781+ emnapiTSFN . enqueue ( func )
782+ }
770783 }
771784 }
772785 } )
@@ -901,6 +914,7 @@ export function napi_create_threadsafe_function (
901914 makeSetValue ( 'tsfn' , 'emnapiTSFN.offset.finalize_cb' , 'thread_finalize_cb' , '*' )
902915 makeSetValue ( 'tsfn' , 'emnapiTSFN.offset.call_js_cb' , 'call_js_cb' , '*' )
903916 emnapiCtx . addCleanupHook ( envObject , emnapiTSFN . cleanup , tsfn as number )
917+ emnapiTSFN . _liveSet . add ( tsfn as number )
904918 envObject . ref ( )
905919
906920 __emnapi_runtime_keepalive_push ( )
0 commit comments