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
@@ -19,13 +19,45 @@
19
19
moduleAsync
20
20
# Raised when a task is explicitly stopped.
21
21
classStop < Exception
22
+
# Represents the source of the stop operation.
23
+
classCause < Exception
24
+
ifRUBY_VERSION >= "3.4"
25
+
# @returns [Array(Thread::Backtrace::Location)] The backtrace of the caller.
26
+
defself.backtrace
27
+
caller_locations(2..-1)
28
+
end
29
+
else
30
+
# @returns [Array(String)] The backtrace of the caller.
31
+
defself.backtrace
32
+
caller(2..-1)
33
+
end
34
+
end
35
+
36
+
# Create a new cause of the stop operation, with the given message.
37
+
#
38
+
# @parameter message [String] The error message.
39
+
# @returns [Cause] The cause of the stop operation.
40
+
defself.for(message="Task was stopped")
41
+
instance=self.new(message)
42
+
instance.set_backtrace(self.backtrace)
43
+
returninstance
44
+
end
45
+
end
46
+
47
+
# Create a new stop operation.
48
+
definitialize(message="Task was stopped")
49
+
super(message)
50
+
end
51
+
22
52
# Used to defer stopping the current task until later.
23
53
classLater
24
54
# Create a new stop later operation.
25
55
#
26
56
# @parameter task [Task] The task to stop later.
27
-
definitialize(task)
57
+
# @parameter cause [Exception] The cause of the stop operation.
58
+
definitialize(task,cause=nil)
28
59
@task=task
60
+
@cause=cause
29
61
end
30
62
31
63
# @returns [Boolean] Whether the task is alive.
@@ -35,7 +67,7 @@ def alive?
35
67
36
68
# Transfer control to the operation - this will stop the task.
37
69
deftransfer
38
-
@task.stop
70
+
@task.stop(false,cause: @cause)
39
71
end
40
72
end
41
73
end
@@ -271,7 +303,13 @@ def wait
271
303
# 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.
272
304
#
273
305
# @parameter later [Boolean] Whether to stop the task later, or immediately.
274
-
defstop(later=false)
306
+
# @parameter cause [Exception] The cause of the stop operation.
307
+
defstop(later=false,cause: $!)
308
+
# If no cause is given, we generate one from the current call stack:
309
+
unlesscause
310
+
cause=Stop::Cause.for("Stopping task!")
311
+
end
312
+
275
313
ifself.stopped?
276
314
# 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.
277
315
returnstopped!
@@ -285,27 +323,27 @@ def stop(later = false)
285
323
# If we are deferring stop...
286
324
if@defer_stop == false
287
325
# Don't stop now... but update the state so we know we need to stop later.
288
-
@defer_stop=true
326
+
@defer_stop=cause
289
327
returnfalse
290
328
end
291
329
292
330
ifself.current?
293
331
# 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`:
294
332
iflater
295
333
# If the fiber is the current fiber and we want to stop it later, schedule it:
296
-
Fiber.scheduler.push(Stop::Later.new(self))
334
+
Fiber.scheduler.push(Stop::Later.new(self,cause))
297
335
else
298
336
# Otherwise, raise the exception directly:
299
-
raiseStop,"Stopping current task!"
337
+
raiseStop,"Stopping current task!",cause: cause
300
338
end
301
339
else
302
340
# If the fiber is not curent, we can raise the exception directly:
303
341
begin
304
342
# 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.
305
-
Fiber.scheduler.raise(@fiber,Stop)
343
+
Fiber.scheduler.raise(@fiber,Stop,cause: cause)
306
344
rescueFiberError
307
345
# In some cases, this can cause a FiberError (it might be resumed already), so we schedule it to be stopped later:
308
-
Fiber.scheduler.push(Stop::Later.new(self))
346
+
Fiber.scheduler.push(Stop::Later.new(self,cause))
309
347
end
310
348
end
311
349
else
@@ -345,7 +383,7 @@ def defer_stop
345
383
346
384
# If we were asked to stop, we should do so now:
347
385
ifdefer_stop
348
-
raiseStop,"Stopping current task (was deferred)!"
386
+
raiseStop,"Stopping current task (was deferred)!",cause: defer_stop
349
387
end
350
388
end
351
389
else
@@ -356,7 +394,7 @@ def defer_stop
356
394
357
395
# @returns [Boolean] Whether stop has been deferred.
358
396
defstop_deferred?
359
-
@defer_stop
397
+
!!@defer_stop
360
398
end
361
399
362
400
# Lookup the {Task} for the current fiber. Raise `RuntimeError` if none is available.
0 commit comments