11package ox
22
3+ import ox .internal .currentScope
4+
5+ import java .util .concurrent .CompletableFuture
6+ import java .util .concurrent .Semaphore
37import java .util .concurrent .atomic .AtomicBoolean
4- import java .util .concurrent .{CompletableFuture , Semaphore }
58import scala .concurrent .ExecutionException
69import scala .util .control .NonFatal
710
@@ -34,15 +37,17 @@ def forkError[E, F[_], T](using OxError[E, F])(f: => F[T]): Fork[T] =
3437 val oxError = summon[OxError [E , F ]]
3538 // the separate result future is needed to wait for the result, as there's no .join on individual tasks (only whole scopes can be joined)
3639 val result = new CompletableFuture [T ]()
37- oxError.scope.fork { () =>
40+
41+ oxError.herd.startThread {
42+ currentScope.set(oxError) // propagating the current scope
3843 val supervisor = oxError.supervisor
3944 try
4045 val resultOrError = f
4146 val errorMode = oxError.errorMode
4247 if errorMode.isError(resultOrError) then
4348 // result is never completed, the supervisor should end the scope
4449 supervisor.forkAppError(errorMode.getError(resultOrError))
45- else result.complete(errorMode.getT(resultOrError))
50+ else result.complete(errorMode.getT(resultOrError)).discard
4651 catch
4752 case e : Throwable =>
4853 // we notify the supervisor first, so that if this is the first failing fork in the scope, the supervisor will
@@ -52,6 +57,7 @@ def forkError[E, F[_], T](using OxError[E, F])(f: => F[T]): Fork[T] =
5257 if ! supervisor.forkException(e) then result.completeExceptionally(e).discard
5358 end try
5459 }
60+
5561 new ForkUsingResult (result) {}
5662end forkError
5763
@@ -81,7 +87,9 @@ def forkUserError[E, F[_], T](using OxError[E, F])(f: => F[T]): Fork[T] =
8187 val oxError = summon[OxError [E , F ]]
8288 val result = new CompletableFuture [T ]()
8389 oxError.supervisor.forkStarts()
84- oxError.scope.fork { () =>
90+
91+ oxError.herd.startThread:
92+ currentScope.set(oxError) // propagating the current scope
8593 val supervisor = oxError.supervisor.asInstanceOf [DefaultSupervisor [E ]]
8694 try
8795 val resultOrError = f
@@ -96,7 +104,7 @@ def forkUserError[E, F[_], T](using OxError[E, F])(f: => F[T]): Fork[T] =
96104 case e : Throwable =>
97105 if ! supervisor.forkException(e) then result.completeExceptionally(e).discard
98106 end try
99- }
107+
100108 new ForkUsingResult (result) {}
101109end forkUserError
102110
@@ -111,12 +119,16 @@ end forkUserError
111119 * For alternate behaviors, see [[fork ]], [[forkUser ]] and [[forkCancellable ]].
112120 */
113121def forkUnsupervised [T ](f : => T )(using OxUnsupervised ): UnsupervisedFork [T ] =
122+ val oxUnsupervised = summon[OxUnsupervised ]
114123 val result = new CompletableFuture [T ]()
115- summon[OxUnsupervised ].scope.fork { () =>
116- try result.complete(f)
117- catch case e : Throwable => result.completeExceptionally(e)
118- }
124+
125+ oxUnsupervised.herd.startThread:
126+ currentScope.set(oxUnsupervised) // propagating the current scope
127+ try result.complete(f).discard
128+ catch case e : Throwable => result.completeExceptionally(e).discard
129+
119130 new ForkUsingResult (result) with UnsupervisedFork [T ] {}
131+ end forkUnsupervised
120132
121133/** For each thunk in the given sequence, starts a fork using [[fork ]]. All forks are guaranteed to complete before the enclosing
122134 * [[supervised ]] or [[unsupervised ]] block completes.
@@ -149,21 +161,28 @@ def forkCancellable[T](f: => T)(using OxUnsupervised): CancellableFork[T] =
149161 // interrupt signal
150162 val done = new Semaphore (0 )
151163 val ox = summon[OxUnsupervised ]
152- ox.scope.fork { () =>
153- val nestedOx = OxError (NoOpSupervisor , NoErrorMode )
154- scopedWithCapability(nestedOx) {
155- nestedOx.scope.fork { () =>
156- // "else" means that the fork is already cancelled, so doing nothing in that case
157- if ! started.getAndSet(true ) then
158- try result.complete(f).discard
159- catch case e : Throwable => result.completeExceptionally(e).discard
160-
161- done.release() // the nested scope can now finish
164+
165+ ox.herd.startThread:
166+ try
167+ val nestedOx = OxError (NoOpSupervisor , NoErrorMode , Some (ox), ox.locals)
168+ scopedWithCapability(nestedOx) {
169+ nestedOx.herd.startThread {
170+ currentScope.set(nestedOx) // propagating the nested scope, which has the current scope set as a parent
171+ // "else" means that the fork is already cancelled, so doing nothing in that case
172+ if ! started.getAndSet(true ) then
173+ try result.complete(f).discard
174+ catch case e : Throwable => result.completeExceptionally(e).discard
175+
176+ done.release() // the nested scope can now finish
177+ }.discard
178+
179+ done.acquire()
162180 }
181+ catch
182+ // if this thread was interrupted, any context is already captured as part of the thread started in the nested scope
183+ // hence, ignoring this exception, so that it's not logged by the uncaught exception handler
184+ case e : InterruptedException =>
163185
164- done.acquire()
165- }
166- }
167186 new ForkUsingResult (result) with CancellableFork [T ]:
168187 override def cancel (): Either [Throwable , T ] =
169188 cancelNow()
0 commit comments