You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: lib/async/task.rb
+48-10Lines changed: 48 additions & 10 deletions
Original file line number
Diff line number
Diff line change
@@ -18,13 +18,45 @@
18
18
moduleAsync
19
19
# Raised when a task is explicitly stopped.
20
20
classStop < Exception
21
+
# Represents the source of the stop operation.
22
+
classCause < Exception
23
+
ifRUBY_VERSION >= "3.4"
24
+
# @returns [Array(Thread::Backtrace::Location)] The backtrace of the caller.
25
+
defself.backtrace
26
+
caller_locations(2..-1)
27
+
end
28
+
else
29
+
# @returns [Array(String)] The backtrace of the caller.
30
+
defself.backtrace
31
+
caller(2..-1)
32
+
end
33
+
end
34
+
35
+
# Create a new cause of the stop operation, with the given message.
36
+
#
37
+
# @parameter message [String] The error message.
38
+
# @returns [Cause] The cause of the stop operation.
39
+
defself.for(message="Task was stopped")
40
+
instance=self.new(message)
41
+
instance.set_backtrace(self.backtrace)
42
+
returninstance
43
+
end
44
+
end
45
+
46
+
# Create a new stop operation.
47
+
definitialize(message="Task was stopped")
48
+
super(message)
49
+
end
50
+
21
51
# Used to defer stopping the current task until later.
22
52
classLater
23
53
# Create a new stop later operation.
24
54
#
25
55
# @parameter task [Task] The task to stop later.
26
-
definitialize(task)
56
+
# @parameter cause [Exception] The cause of the stop operation.
57
+
definitialize(task,cause=nil)
27
58
@task=task
59
+
@cause=cause
28
60
end
29
61
30
62
# @returns [Boolean] Whether the task is alive.
@@ -34,7 +66,7 @@ def alive?
34
66
35
67
# Transfer control to the operation - this will stop the task.
36
68
deftransfer
37
-
@task.stop
69
+
@task.stop(false,cause: @cause)
38
70
end
39
71
end
40
72
end
@@ -266,7 +298,13 @@ def wait
266
298
# If `later` is false, it means that `stop` has been invoked directly. When `later` is true, it means that `stop` is invoked by `stop_children` or some other indirect mechanism. In that case, if we encounter the "current" fiber, we can't stop it right away, as it's currently performing `#stop`. Stopping it immediately would interrupt the current stop traversal, so we need to schedule the stop to occur later.
267
299
#
268
300
# @parameter later [Boolean] Whether to stop the task later, or immediately.
269
-
defstop(later=false)
301
+
# @parameter cause [Exception] The cause of the stop operation.
302
+
defstop(later=false,cause: $!)
303
+
# If no cause is given, we generate one from the current call stack:
304
+
unlesscause
305
+
cause=Stop::Cause.for("Stopping task!")
306
+
end
307
+
270
308
ifself.stopped?
271
309
# If the task is already stopped, a `stop` state transition re-enters the same state which is a no-op. However, we will also attempt to stop any running children too. This can happen if the children did not stop correctly the first time around. Doing this should probably be considered a bug, but it's better to be safe than sorry.
272
310
returnstopped!
@@ -280,27 +318,27 @@ def stop(later = false)
280
318
# If we are deferring stop...
281
319
if@defer_stop == false
282
320
# Don't stop now... but update the state so we know we need to stop later.
283
-
@defer_stop=true
321
+
@defer_stop=cause
284
322
returnfalse
285
323
end
286
324
287
325
ifself.current?
288
326
# If the fiber is current, and later is `true`, we need to schedule the fiber to be stopped later, as it's currently invoking `stop`:
289
327
iflater
290
328
# If the fiber is the current fiber and we want to stop it later, schedule it:
291
-
Fiber.scheduler.push(Stop::Later.new(self))
329
+
Fiber.scheduler.push(Stop::Later.new(self,cause))
292
330
else
293
331
# Otherwise, raise the exception directly:
294
-
raiseStop,"Stopping current task!"
332
+
raiseStop,"Stopping current task!",cause: cause
295
333
end
296
334
else
297
335
# If the fiber is not curent, we can raise the exception directly:
298
336
begin
299
337
# There is a chance that this will stop the fiber that originally called stop. If that happens, the exception handling in `#stopped` will rescue the exception and re-raise it later.
300
-
Fiber.scheduler.raise(@fiber,Stop)
338
+
Fiber.scheduler.raise(@fiber,Stop,cause: cause)
301
339
rescueFiberError
302
340
# In some cases, this can cause a FiberError (it might be resumed already), so we schedule it to be stopped later:
303
-
Fiber.scheduler.push(Stop::Later.new(self))
341
+
Fiber.scheduler.push(Stop::Later.new(self,cause))
304
342
end
305
343
end
306
344
else
@@ -340,7 +378,7 @@ def defer_stop
340
378
341
379
# If we were asked to stop, we should do so now:
342
380
ifdefer_stop
343
-
raiseStop,"Stopping current task (was deferred)!"
381
+
raiseStop,"Stopping current task (was deferred)!",cause: defer_stop
344
382
end
345
383
end
346
384
else
@@ -351,7 +389,7 @@ def defer_stop
351
389
352
390
# @returns [Boolean] Whether stop has been deferred.
353
391
defstop_deferred?
354
-
@defer_stop
392
+
!!@defer_stop
355
393
end
356
394
357
395
# Lookup the {Task} for the current fiber. Raise `RuntimeError` if none is available.
0 commit comments