@@ -30,6 +30,9 @@ export const emnapiTSFN = {
3030 /* size_t */ queue_size : 0 ,
3131 /* bool */ is_some : 0 ,
3232 /* void* */ queue : 0 ,
33+ // Reuse uv_async_t storage as JS-side wakeup state: pending event + scheduled drain.
34+ async_pending : 0 ,
35+ async_u_fd : 0 ,
3336 /* size_t */ thread_count : 0 ,
3437 /* int32_t */ state : 0 ,
3538 /* atomic_uchar */ dispatch_state : 0 ,
@@ -54,6 +57,8 @@ export const emnapiTSFN = {
5457 emnapiTSFN . offset . queue_size = NapiTSFNOffset64 . queue_size
5558 emnapiTSFN . offset . is_some = NapiTSFNOffset64 . async_resource_is_some
5659 emnapiTSFN . offset . queue = NapiTSFNOffset64 . queue
60+ emnapiTSFN . offset . async_pending = NapiTSFNOffset64 . async_pending
61+ emnapiTSFN . offset . async_u_fd = NapiTSFNOffset64 . async_u_fd
5762 emnapiTSFN . offset . thread_count = NapiTSFNOffset64 . thread_count
5863 emnapiTSFN . offset . state = NapiTSFNOffset64 . state
5964 emnapiTSFN . offset . dispatch_state = NapiTSFNOffset64 . dispatch_state
@@ -76,6 +81,8 @@ export const emnapiTSFN = {
7681 emnapiTSFN . offset . queue_size = NapiTSFNOffset32 . queue_size
7782 emnapiTSFN . offset . is_some = NapiTSFNOffset32 . async_resource_is_some
7883 emnapiTSFN . offset . queue = NapiTSFNOffset32 . queue
84+ emnapiTSFN . offset . async_pending = NapiTSFNOffset32 . async_pending
85+ emnapiTSFN . offset . async_u_fd = NapiTSFNOffset32 . async_u_fd
7986 emnapiTSFN . offset . thread_count = NapiTSFNOffset32 . thread_count
8087 emnapiTSFN . offset . state = NapiTSFNOffset32 . state
8188 emnapiTSFN . offset . dispatch_state = NapiTSFNOffset32 . dispatch_state
@@ -113,7 +120,10 @@ export const emnapiTSFN = {
113120 const type = __emnapi__ . type
114121 const payload = __emnapi__ . payload
115122 if ( type === 'tsfn-send' ) {
116- emnapiTSFN . dispatch ( payload . tsfn )
123+ const pendng = payload . tsfn + emnapiTSFN . offset . async_pending
124+ if ( Atomics . load ( new Int32Array ( wasmMemory . buffer ) , pendng >>> 2 ) !== 0 ) {
125+ emnapiTSFN . enqueue ( payload . tsfn )
126+ }
117127 }
118128 }
119129 }
@@ -708,24 +718,77 @@ export const emnapiTSFN = {
708718 emnapiTSFN . send ( func )
709719 }
710720 } ,
721+ enqueue ( func : number ) : void {
722+ // `pending` means a worker thread has requested a wakeup that has not
723+ // been drained on the main thread yet.
724+ const pending = func + emnapiTSFN . offset . async_pending
725+ // `scheduled` prevents queueing the same main-thread drain chain more than
726+ // once while a previous wakeup is still in flight.
727+ const scheduled = func + emnapiTSFN . offset . async_u_fd
728+ const i32a = new Int32Array ( wasmMemory . buffer )
729+ if ( Atomics . exchange ( i32a , scheduled >>> 2 , 1 ) !== 0 ) {
730+ return
731+ }
732+
733+ // Match uv_async_send-style coalescing in JS: the first turn represents
734+ // the wakeup reaching the main thread, and the second turn performs the
735+ // actual TSFN drain after nearby Send/Signal calls have had a chance to
736+ // collapse into the shared AsyncProgressWorker state.
737+ emnapiCtx . feature . setImmediate ( ( ) => {
738+ if ( Atomics . load ( i32a , pending >>> 2 ) === 0 ) {
739+ Atomics . store ( i32a , scheduled >>> 2 , 0 )
740+ return
741+ }
742+
743+ emnapiCtx . feature . setImmediate ( ( ) => {
744+ try {
745+ // Consume the coalesced wakeup once, then let dispatch() observe any
746+ // queue mutations through dispatch_state like the C implementation.
747+ if ( Atomics . exchange ( i32a , pending >>> 2 , 0 ) === 0 ) {
748+ return
749+ }
750+ emnapiTSFN . dispatch ( func )
751+ } finally {
752+ // Allow a later wakeup to schedule a new drain chain. If another
753+ // worker-thread send raced with this drain, enqueue one more turn.
754+ Atomics . store ( i32a , scheduled >>> 2 , 0 )
755+ if ( Atomics . load ( i32a , pending >>> 2 ) !== 0 ) {
756+ emnapiTSFN . enqueue ( func )
757+ }
758+ }
759+ } )
760+ } )
761+ } ,
711762 send ( func : number ) : void {
712763 const current_state = Atomics . or ( new Uint32Array ( wasmMemory . buffer ) , ( func + emnapiTSFN . offset . dispatch_state ) >>> 2 , 1 << 1 )
713764 if ( ( current_state & 1 ) === 1 ) {
714765 return
715766 }
716- if ( ( typeof ENVIRONMENT_IS_PTHREAD !== 'undefined' ) && ENVIRONMENT_IS_PTHREAD ) {
717- postMessage ( {
718- __emnapi__ : {
719- type : 'tsfn-send' ,
720- payload : {
721- tsfn : func
767+
768+ const pendng = func + emnapiTSFN . offset . async_pending
769+ // A wakeup is already pending, so this send only needs to leave the queued
770+ // work in the TSFN queue and let the existing drain pick it up.
771+ if ( Atomics . load ( new Int32Array ( wasmMemory . buffer ) , pendng >>> 2 ) !== 0 ) {
772+ return
773+ }
774+
775+ if ( Atomics . exchange ( new Int32Array ( wasmMemory . buffer ) , pendng >>> 2 , 1 ) === 0 ) {
776+ if ( ( typeof ENVIRONMENT_IS_PTHREAD !== 'undefined' ) && ENVIRONMENT_IS_PTHREAD ) {
777+ // Worker threads only post a wakeup token. Main-thread draining is
778+ // serialized by enqueue() once the message is received.
779+ postMessage ( {
780+ __emnapi__ : {
781+ type : 'tsfn-send' ,
782+ payload : {
783+ tsfn : func
784+ }
722785 }
723- }
724- } )
725- } else {
726- emnapiCtx . feature . setImmediate ( ( ) => {
727- emnapiTSFN . dispatch ( func )
728- } )
786+ } )
787+ } else {
788+ // On the main thread we can skip the cross-thread hop and schedule the
789+ // coalesced drain chain directly.
790+ emnapiTSFN . enqueue ( func )
791+ }
729792 }
730793 }
731794}
0 commit comments