@@ -608,6 +608,19 @@ comptime {
608
608
pub const DeferredRepeatingTask = * const (fn (* anyopaque ) bool );
609
609
pub const EventLoop = struct {
610
610
tasks : if (JSC .is_bindgen ) void else Queue = undefined ,
611
+
612
+ /// setImmediate() gets it's own two task queues
613
+ /// When you call `setImmediate` in JS, it queues to the start of the next tick
614
+ /// This is confusing, but that is how it works in Node.js.
615
+ ///
616
+ /// So we have two queues:
617
+ /// - next_immediate_tasks: tasks that will run on the next tick
618
+ /// - immediate_tasks: tasks that will run on the current tick
619
+ ///
620
+ /// Having two queues avoids infinite loops creating by calling `setImmediate` in a `setImmediate` callback.
621
+ immediate_tasks : Queue = undefined ,
622
+ next_immediate_tasks : Queue = undefined ,
623
+
611
624
concurrent_tasks : ConcurrentTask.Queue = ConcurrentTask.Queue {},
612
625
global : * JSGlobalObject = undefined ,
613
626
virtual_machine : * JSC.VirtualMachine = undefined ,
@@ -670,11 +683,11 @@ pub const EventLoop = struct {
670
683
}
671
684
}
672
685
673
- pub fn tickWithCount (this : * EventLoop ) u32 {
686
+ pub fn tickQueueWithCount (this : * EventLoop , comptime queue_name : [] const u8 ) u32 {
674
687
var global = this .global ;
675
688
var global_vm = global .vm ();
676
689
var counter : usize = 0 ;
677
- while (this . tasks .readItem ()) | task | {
690
+ while (@field ( this , queue_name ) .readItem ()) | task | {
678
691
defer counter += 1 ;
679
692
switch (task .tag ()) {
680
693
.Microtask = > {
@@ -922,10 +935,18 @@ pub const EventLoop = struct {
922
935
this .drainMicrotasksWithGlobal (global );
923
936
}
924
937
925
- this . tasks . head = if (this . tasks . count == 0 ) 0 else this . tasks .head ;
938
+ @field ( this , queue_name ). head = if (@field ( this , queue_name ). count == 0 ) 0 else @field ( this , queue_name ) .head ;
926
939
return @as (u32 , @truncate (counter ));
927
940
}
928
941
942
+ pub fn tickWithCount (this : * EventLoop ) u32 {
943
+ return this .tickQueueWithCount ("tasks" );
944
+ }
945
+
946
+ pub fn tickImmediateTasks (this : * EventLoop ) void {
947
+ _ = this .tickQueueWithCount ("immediate_tasks" );
948
+ }
949
+
929
950
pub fn tickConcurrent (this : * EventLoop ) void {
930
951
_ = this .tickConcurrentWithCount ();
931
952
}
@@ -994,6 +1015,8 @@ pub const EventLoop = struct {
994
1015
995
1016
ctx .onAfterEventLoop ();
996
1017
// this.afterUSocketsTick();
1018
+ } else {
1019
+ this .flushImmediateQueue ();
997
1020
}
998
1021
}
999
1022
@@ -1016,8 +1039,25 @@ pub const EventLoop = struct {
1016
1039
if (loop .num_polls > 0 or loop .active > 0 ) {
1017
1040
this .processGCTimer ();
1018
1041
loop .tickWithTimeout (timeoutMs , ctx .jsc );
1042
+ this .flushImmediateQueue ();
1019
1043
ctx .onAfterEventLoop ();
1020
1044
// this.afterUSocketsTick();
1045
+ } else {
1046
+ this .flushImmediateQueue ();
1047
+ }
1048
+ }
1049
+
1050
+ pub fn flushImmediateQueue (this : * EventLoop ) void {
1051
+ // If we can get away with swapping the queues, do that rather than copying the data
1052
+ if (this .immediate_tasks .count > 0 ) {
1053
+ this .immediate_tasks .write (this .next_immediate_tasks .readableSlice (0 )) catch unreachable ;
1054
+ this .next_immediate_tasks .head = 0 ;
1055
+ this .next_immediate_tasks .count = 0 ;
1056
+ } else if (this .next_immediate_tasks .count > 0 ) {
1057
+ var prev_immediate = this .immediate_tasks ;
1058
+ var next_immediate = this .next_immediate_tasks ;
1059
+ this .immediate_tasks = next_immediate ;
1060
+ this .next_immediate_tasks = prev_immediate ;
1021
1061
}
1022
1062
}
1023
1063
@@ -1041,6 +1081,7 @@ pub const EventLoop = struct {
1041
1081
1042
1082
this .processGCTimer ();
1043
1083
loop .tick (ctx .jsc );
1084
+
1044
1085
ctx .onAfterEventLoop ();
1045
1086
this .tickConcurrent ();
1046
1087
this .tick ();
@@ -1064,8 +1105,11 @@ pub const EventLoop = struct {
1064
1105
if (loop .active > 0 ) {
1065
1106
this .processGCTimer ();
1066
1107
loop .tick (ctx .jsc );
1108
+ this .flushImmediateQueue ();
1067
1109
ctx .onAfterEventLoop ();
1068
1110
// this.afterUSocketsTick();
1111
+ } else {
1112
+ this .flushImmediateQueue ();
1069
1113
}
1070
1114
}
1071
1115
@@ -1078,7 +1122,7 @@ pub const EventLoop = struct {
1078
1122
1079
1123
var ctx = this .virtual_machine ;
1080
1124
this .tickConcurrent ();
1081
-
1125
+ this . tickImmediateTasks ();
1082
1126
this .processGCTimer ();
1083
1127
1084
1128
var global = ctx .global ;
@@ -1149,6 +1193,11 @@ pub const EventLoop = struct {
1149
1193
this .tasks .writeItem (task ) catch unreachable ;
1150
1194
}
1151
1195
1196
+ pub fn enqueueImmediateTask (this : * EventLoop , task : Task ) void {
1197
+ JSC .markBinding (@src ());
1198
+ this .next_immediate_tasks .writeItem (task ) catch unreachable ;
1199
+ }
1200
+
1152
1201
pub fn enqueueTaskWithTimeout (this : * EventLoop , task : Task , timeout : i32 ) void {
1153
1202
// TODO: make this more efficient!
1154
1203
var loop = this .virtual_machine .event_loop_handle orelse @panic ("EventLoop.enqueueTaskWithTimeout: uSockets event loop is not initialized" );
0 commit comments