Explorar o código

EventLoop: run repeatable events in order by time (fixes #10567)

Aleksandr Kuzmenko %!s(int64=3) %!d(string=hai) anos
pai
achega
23aa07fa3f
Modificáronse 3 ficheiros con 72 adicións e 17 borrados
  1. 1 0
      extra/CHANGES.txt
  2. 47 17
      std/sys/thread/EventLoop.hx
  3. 24 0
      tests/threads/src/cases/TestEvents.hx

+ 1 - 0
extra/CHANGES.txt

@@ -7,6 +7,7 @@
 	Bugfixes:
 
 	all : fixed compiler crash in complex constraints chains (#10445)
+	all : fixed timers execution order for timers with small time delta (#10567)
 	js : fixed constructors with rest arguments when compiling for ES3, ES5 (#10490)
 	php : excluded E_DEPRECATED notices from error reporting (#10502)
 	nullsafety : fixed false error on extern var fields without initialization (#10448)

+ 47 - 17
std/sys/thread/EventLoop.hx

@@ -38,16 +38,41 @@ class EventLoop {
 		mutex.acquire();
 		var interval = 0.001 * intervalMs;
 		var event = new RegularEvent(event, Sys.time() + interval, interval);
+		inline insertEventByTime(event);
+		waitLock.release();
+		mutex.release();
+		return event;
+	}
+
+	function insertEventByTime(event:RegularEvent):Void {
 		switch regularEvents {
 			case null:
+				regularEvents = event;
 			case current:
-				event.next = current;
-				current.previous = event;
+				var previous = null;
+				while(true) {
+					if(current == null) {
+						previous.next = event;
+						event.previous = previous;
+						break;
+					} else if(event.nextRunTime < current.nextRunTime) {
+						event.next = current;
+						current.previous = event;
+						switch previous {
+							case null:
+								regularEvents = event;
+								case _:
+								event.previous = previous;
+								previous.next = event;
+								current.previous = event;
+						}
+						break;
+					} else {
+						previous = current;
+						current = current.next;
+					}
+				}
 		}
-		regularEvents = event;
-		waitLock.release();
-		mutex.release();
-		return event;
 	}
 
 	/**
@@ -56,6 +81,7 @@ class EventLoop {
 	public function cancel(eventHandler:EventHandler):Void {
 		mutex.acquire();
 		var event:RegularEvent = eventHandler;
+		event.cancelled = true;
 		if(regularEvents == event) {
 			regularEvents = event.next;
 		}
@@ -111,7 +137,7 @@ class EventLoop {
 		not be called from event callbacks.
 	**/
 	public function progress():NextEventTime {
-		return switch __progress(Sys.time(), []) {
+		return switch __progress(Sys.time(), [], []) {
 			case {nextEventAt:-2}: Now;
 			case {nextEventAt:-1, anyTime:false}: Never;
 			case {nextEventAt:-1, anyTime:true}: AnyTime(null);
@@ -145,9 +171,10 @@ class EventLoop {
 		not be called from event callbacks.
 	**/
 	public function loop():Void {
-		var events = [];
+		var recycleRegular = [];
+		var recycleOneTimers = [];
 		while(true) {
-			var r = __progress(Sys.time(), events);
+			var r = __progress(Sys.time(), recycleRegular, recycleOneTimers);
 			switch r {
 				case {nextEventAt:-2}:
 				case {nextEventAt:-1, anyTime:false}:
@@ -169,8 +196,8 @@ class EventLoop {
 		* -2 - now
 		* other values - at specified time
 	**/
-	inline function __progress(now:Float, recycle:Array<()->Void>):{nextEventAt:Float, anyTime:Bool} {
-		var eventsToRun = recycle;
+	inline function __progress(now:Float, recycleRegular:Array<RegularEvent>, recycleOneTimers:Array<()->Void>):{nextEventAt:Float, anyTime:Bool} {
+		var regularsToRun = recycleRegular;
 		var eventsToRunIdx = 0;
 		// When the next event is expected to run
 		var nextEventAt:Float = -1;
@@ -182,7 +209,7 @@ class EventLoop {
 		var current = regularEvents;
 		while(current != null) {
 			if(current.nextRunTime <= now) {
-				eventsToRun[eventsToRunIdx++] = current.run;
+				regularsToRun[eventsToRunIdx++] = current;
 				current.nextRunTime += current.interval;
 				nextEventAt = -2;
 			} else if(nextEventAt == -1 || current.nextRunTime < nextEventAt) {
@@ -194,11 +221,13 @@ class EventLoop {
 
 		// Run regular events
 		for(i in 0...eventsToRunIdx) {
-			eventsToRun[i]();
-			eventsToRun[i] = null;
+			if(!regularsToRun[i].cancelled) 
+				regularsToRun[i].run();
+			regularsToRun[i] = null;
 		}
 		eventsToRunIdx = 0;
 
+		var oneTimersToRun = recycleOneTimers;
 		// Collect pending one-time events
 		mutex.acquire();
 		for(i => event in oneTimeEvents) {
@@ -206,7 +235,7 @@ class EventLoop {
 				case null:
 					break;
 				case _:
-					eventsToRun[eventsToRunIdx++] = event;
+					oneTimersToRun[eventsToRunIdx++] = event;
 					oneTimeEvents[i] = null;
 			}
 		}
@@ -216,8 +245,8 @@ class EventLoop {
 
 		//run events
 		for(i in 0...eventsToRunIdx) {
-			eventsToRun[i]();
-			eventsToRun[i] = null;
+			oneTimersToRun[i]();
+			oneTimersToRun[i] = null;
 		}
 
 		// Some events were executed. They could add new events to run.
@@ -236,6 +265,7 @@ private class RegularEvent {
 	public final run:()->Void;
 	public var next:Null<RegularEvent>;
 	public var previous:Null<RegularEvent>;
+	public var cancelled:Bool = false;
 
 	public function new(run:()->Void, nextRunTime:Float, interval:Float) {
 		this.run = run;

+ 24 - 0
tests/threads/src/cases/TestEvents.hx

@@ -3,6 +3,30 @@ package cases;
 @:timeout(2000)
 class TestEvents extends utest.Test {
 
+	function testIssue10567_runEventsInOrderByTime(async:Async) {
+		var events = Thread.current().events;
+		var checks = [];
+		var e3 = null;
+		var e2 = null;
+		var e1 = null;
+		e2 = events.repeat(() -> {
+			checks.push(2);
+			events.cancel(e1);
+			events.cancel(e2);
+			events.cancel(e3);
+		}, 2);
+		e1 = events.repeat(() -> checks.push(1), 1);
+		e3 = events.repeat(() -> checks.push(3), 3);
+		Sys.sleep(0.1);
+
+		var checker = null;
+		checker = events.repeat(() -> {
+			same([1, 2], checks);
+			async.done();
+			events.cancel(checker);
+		}, 100);
+	}
+
 	function testThreadRunWithEventLoop() {
 		var eventExecuted = false;
 		var lock = new sys.thread.Lock();