From 318d5a2cf925b5b77afe5fd84fbb93984d411b55 Mon Sep 17 00:00:00 2001 From: Joseph Cloutier Date: Sat, 6 Apr 2024 02:06:35 -0400 Subject: [PATCH 1/5] Fix null pointer error when canceling an already-canceled job. --- src/lime/system/ThreadPool.hx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lime/system/ThreadPool.hx b/src/lime/system/ThreadPool.hx index abb35f6832..845b0133f9 100644 --- a/src/lime/system/ThreadPool.hx +++ b/src/lime/system/ThreadPool.hx @@ -658,7 +658,8 @@ abstract JobList(List) public inline function remove(job:JobData):Bool { - return this.remove(job) || removeByID(job.id); + return this.remove(job) + || job != null && removeByID(job.id); } public inline function removeByID(id:Int):Bool From e6bd610e15f3f674bdc1d697948b217a90f2f1d4 Mon Sep 17 00:00:00 2001 From: Joseph Cloutier Date: Sat, 6 Apr 2024 02:09:12 -0400 Subject: [PATCH 2/5] Clean up event listener once all jobs are done. The `completed` variable is only set if the final job succeeds, but we want to clean up even if it fails. --- src/lime/system/ThreadPool.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lime/system/ThreadPool.hx b/src/lime/system/ThreadPool.hx index 845b0133f9..fd16595a05 100644 --- a/src/lime/system/ThreadPool.hx +++ b/src/lime/system/ThreadPool.hx @@ -570,7 +570,7 @@ class ThreadPool extends WorkOutput activeJob = null; } - if (completed) + if (activeJobs == 0 && __jobQueue.isEmpty()) { Application.current.onUpdate.remove(__update); } From f797895c152fe4b77c0290e81da8e2fd46333a68 Mon Sep 17 00:00:00 2001 From: Joseph Cloutier Date: Sat, 6 Apr 2024 02:14:37 -0400 Subject: [PATCH 3/5] Implement `ThreadPool.onUncaughtError`. This restores the behavior of `onError` to what it was in 8.1.0. --- src/lime/system/ThreadPool.hx | 69 ++++++++++++++++++++++++++++------- src/lime/system/WorkOutput.hx | 28 ++++++++++++-- 2 files changed, 80 insertions(+), 17 deletions(-) diff --git a/src/lime/system/ThreadPool.hx b/src/lime/system/ThreadPool.hx index fd16595a05..412047b965 100644 --- a/src/lime/system/ThreadPool.hx +++ b/src/lime/system/ThreadPool.hx @@ -13,6 +13,9 @@ import neko.vm.Thread; #elseif html5 import lime._internal.backend.html5.HTML5Thread as Thread; #end +#if (haxe_ver >= 4.1) +import haxe.Exception; +#end /** A simple and thread-safe way to run a one or more asynchronous jobs. It @@ -138,6 +141,15 @@ class ThreadPool extends WorkOutput once per job. **/ public var onRun(default, null) = new EventVoid>(); + #if (haxe_ver >= 4.1) + /** + Dispatched on the main thread when `doWork` throws an error. Dispatched + at most once per job. + + If no listeners have been added, instead the error will be rethrown. + **/ + public var onUncaughtError(default, null) = new EventVoid>(); + #end @:deprecated("Instead pass the callback to ThreadPool.run().") @:noCompletion @:dox(hide) public var doWork(get, never):PseudoEvent; @@ -400,9 +412,9 @@ class ThreadPool extends WorkOutput event.job.doWork.dispatch(event.job.state, output); } } - catch (e:#if (haxe_ver >= 4.1) haxe.Exception #else Dynamic #end) + catch (e:#if (haxe_ver >= 4.1) Exception #else Dynamic #end) { - output.sendError(e); + output.sendUncaughtError(e); } output.activeJob = null; @@ -494,9 +506,9 @@ class ThreadPool extends WorkOutput } while (!__jobComplete.value && timeElapsed < __workPerFrame); } - catch (e:#if (haxe_ver >= 4.1) haxe.Exception #else Dynamic #end) + catch (e:#if (haxe_ver >= 4.1) Exception #else Dynamic #end) { - sendError(e); + sendUncaughtError(e); } activeJob.duration += timeElapsed; @@ -533,16 +545,7 @@ class ThreadPool extends WorkOutput case PROGRESS: onProgress.dispatch(threadEvent.message); - case COMPLETE, ERROR: - if (threadEvent.event == COMPLETE) - { - onComplete.dispatch(threadEvent.message); - } - else - { - onError.dispatch(threadEvent.message); - } - + case COMPLETE, ERROR, UNCAUGHT_ERROR: __activeJobs.remove(activeJob); #if lime_threads @@ -564,6 +567,44 @@ class ThreadPool extends WorkOutput completed = threadEvent.event == COMPLETE && activeJobs == 0 && __jobQueue.isEmpty(); + if (threadEvent.event == COMPLETE) + { + onComplete.dispatch(threadEvent.message); + } + else if (threadEvent.event == ERROR) + { + onError.dispatch(threadEvent.message); + } + else + { + var message:String; + + #if (haxe_ver >= 4.1) + if (Std.isOfType(threadEvent.message, Exception)) + { + if (onUncaughtError.__listeners.length > 0) + { + onUncaughtError.dispatch(threadEvent.message); + message = null; + } + else + { + message = (threadEvent.message:Exception).details(); + } + } + else + #end + { + message = Std.string(threadEvent.message); + } + + if (message != null) + { + activeJob = null; + Log.error(message); + } + } + default: } diff --git a/src/lime/system/WorkOutput.hx b/src/lime/system/WorkOutput.hx index 433fad10e3..a0adb95c02 100644 --- a/src/lime/system/WorkOutput.hx +++ b/src/lime/system/WorkOutput.hx @@ -67,14 +67,14 @@ class WorkOutput private var __jobOutput:Deque = new Deque(); /** Thread-local storage. Tracks whether `sendError()` or `sendComplete()` - was called by this job. + was called by this job, or if the job threw an "uncaught" error. **/ private var __jobComplete:Tls = new Tls(); /** The job that is currently running on this thread, or the job that - triggered the ongoing `onComplete`, `onError`, or `onProgress` event. - Will be null in all other cases. + triggered the ongoing `onComplete`, `onError`, `onUncaughtError`, or + `onProgress` event. Will be null in all other cases. **/ public var activeJob(get, set):Null; @:noCompletion private var __activeJob:Tls = new Tls(); @@ -139,6 +139,24 @@ class WorkOutput } } + private function sendUncaughtError(message:Dynamic):Void + { + if (!__jobComplete.value) + { + __jobComplete.value = true; + + #if (lime_threads && html5) + if (mode == MULTI_THREADED) + { + activeJob.doWork.makePortable(); + Thread.returnMessage(new ThreadEvent(UNCAUGHT_ERROR, message, activeJob), transferList); + } + else + #end + __jobOutput.add(new ThreadEvent(UNCAUGHT_ERROR, message, activeJob)); + } + } + /** Dispatches `onProgress` on the main thread, with the given message. This can be called any number of times per job. @@ -351,6 +369,10 @@ class JobData Sent by the background thread, indicating failure. **/ var ERROR = "ERROR"; + /** + Sent by the background thread, indicating failure. + **/ + var UNCAUGHT_ERROR = "UNCAUGHT_ERROR"; /** Sent by the background thread. **/ From 9b5e1892096a2ebfc4eea3ea0a0d2616690dcd1d Mon Sep 17 00:00:00 2001 From: player-03 Date: Mon, 8 Apr 2024 17:34:12 -0400 Subject: [PATCH 4/5] Remove reference to undefined variable. --- src/lime/system/WorkOutput.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lime/system/WorkOutput.hx b/src/lime/system/WorkOutput.hx index a0adb95c02..7da20f75dc 100644 --- a/src/lime/system/WorkOutput.hx +++ b/src/lime/system/WorkOutput.hx @@ -149,7 +149,7 @@ class WorkOutput if (mode == MULTI_THREADED) { activeJob.doWork.makePortable(); - Thread.returnMessage(new ThreadEvent(UNCAUGHT_ERROR, message, activeJob), transferList); + Thread.returnMessage(new ThreadEvent(UNCAUGHT_ERROR, message, activeJob)); } else #end From 2a20ebe6342212543fa3c686adf4a7ec7608c8a5 Mon Sep 17 00:00:00 2001 From: Joseph Cloutier Date: Tue, 2 Jul 2024 17:13:29 -0400 Subject: [PATCH 5/5] Fix reference to function that no longer exists. --- src/lime/system/ThreadPool.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lime/system/ThreadPool.hx b/src/lime/system/ThreadPool.hx index 1d33d2a898..ce4da474b4 100644 --- a/src/lime/system/ThreadPool.hx +++ b/src/lime/system/ThreadPool.hx @@ -640,7 +640,7 @@ class ThreadPool extends WorkOutput activeJob = null; } - if (activeJobs == 0 && __jobQueue.isEmpty()) + if (activeJobs == 0 && __jobQueue.length == 0) { Application.current.onUpdate.remove(__update); }