async 2.14.2 → 2.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/async/barrier.md +1 -1
- data/lib/async/barrier.rb +5 -5
- data/lib/async/clock.rb +10 -1
- data/lib/async/condition.md +1 -1
- data/lib/async/condition.rb +4 -4
- data/lib/async/console.rb +42 -0
- data/lib/async/idler.rb +4 -2
- data/lib/async/limited_queue.rb +7 -0
- data/lib/async/list.rb +1 -1
- data/lib/async/node.rb +29 -3
- data/lib/async/notification.rb +3 -3
- data/lib/async/queue.rb +17 -7
- data/lib/async/reactor.rb +2 -2
- data/lib/async/scheduler.rb +202 -75
- data/lib/async/semaphore.rb +3 -3
- data/lib/async/task.rb +59 -24
- data/lib/async/variable.rb +11 -6
- data/lib/async/version.rb +1 -1
- data/lib/async/waiter.rb +2 -1
- data/lib/async/worker_pool.rb +182 -0
- data/lib/async/wrapper.rb +3 -1
- data/lib/async.rb +1 -1
- data/lib/kernel/async.rb +4 -2
- data/lib/kernel/sync.rb +12 -5
- data/lib/metrics/provider/async/task.rb +20 -0
- data/lib/metrics/provider/async.rb +6 -0
- data/lib/traces/provider/async/barrier.rb +17 -0
- data/lib/traces/provider/async/task.rb +40 -0
- data/lib/traces/provider/async.rb +7 -0
- data/license.md +2 -1
- data/readme.md +46 -12
- data/releases.md +121 -0
- data.tar.gz.sig +0 -0
- metadata +45 -24
- metadata.gz.sig +0 -0
data/lib/async/variable.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2021-
|
4
|
+
# Copyright, 2021-2024, by Samuel Williams.
|
5
5
|
|
6
|
-
require_relative
|
6
|
+
require_relative "condition"
|
7
7
|
|
8
8
|
module Async
|
9
9
|
# A synchronization primitive that allows one task to wait for another task to resolve a value.
|
@@ -31,6 +31,11 @@ module Async
|
|
31
31
|
condition.signal(value)
|
32
32
|
end
|
33
33
|
|
34
|
+
# Alias for {#resolve}.
|
35
|
+
def value=(value)
|
36
|
+
self.resolve(value)
|
37
|
+
end
|
38
|
+
|
34
39
|
# Whether the value has been resolved.
|
35
40
|
#
|
36
41
|
# @returns [Boolean] Whether the value has been resolved.
|
@@ -41,14 +46,14 @@ module Async
|
|
41
46
|
# Wait for the value to be resolved.
|
42
47
|
#
|
43
48
|
# @returns [Object] The resolved value.
|
44
|
-
def
|
49
|
+
def wait
|
45
50
|
@condition&.wait
|
46
51
|
return @value
|
47
52
|
end
|
48
53
|
|
49
|
-
# Alias for {#
|
50
|
-
def
|
51
|
-
self.
|
54
|
+
# Alias for {#wait}.
|
55
|
+
def value
|
56
|
+
self.wait
|
52
57
|
end
|
53
58
|
end
|
54
59
|
end
|
data/lib/async/version.rb
CHANGED
data/lib/async/waiter.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2022, by Samuel Williams.
|
4
|
+
# Copyright, 2022-2024, by Samuel Williams.
|
5
|
+
# Copyright, 2024, by Patrik Wenger.
|
5
6
|
|
6
7
|
module Async
|
7
8
|
# A composable synchronization primitive, which allows one task to wait for a number of other tasks to complete. It can be used in conjunction with {Semaphore} and/or {Barrier}.
|
@@ -0,0 +1,182 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2024, by Samuel Williams.
|
5
|
+
|
6
|
+
require "etc"
|
7
|
+
|
8
|
+
module Async
|
9
|
+
# A simple work pool that offloads work to a background thread.
|
10
|
+
#
|
11
|
+
# @private
|
12
|
+
class WorkerPool
|
13
|
+
# Used to augment the scheduler to add support for blocking operations.
|
14
|
+
module BlockingOperationWait
|
15
|
+
# Wait for the given work to be executed.
|
16
|
+
#
|
17
|
+
# @public Since *Async v2.19* and *Ruby v3.4*.
|
18
|
+
# @asynchronous May be non-blocking.
|
19
|
+
#
|
20
|
+
# @parameter work [Proc] The work to execute on a background thread.
|
21
|
+
# @returns [Object] The result of the work.
|
22
|
+
def blocking_operation_wait(work)
|
23
|
+
@worker_pool.call(work)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Execute the given work in a background thread.
|
28
|
+
class Promise
|
29
|
+
# Create a new promise.
|
30
|
+
#
|
31
|
+
# @parameter work [Proc] The work to be done.
|
32
|
+
def initialize(work)
|
33
|
+
@work = work
|
34
|
+
@state = :pending
|
35
|
+
@value = nil
|
36
|
+
@guard = ::Mutex.new
|
37
|
+
@condition = ::ConditionVariable.new
|
38
|
+
@thread = nil
|
39
|
+
end
|
40
|
+
|
41
|
+
# Execute the work and resolve the promise.
|
42
|
+
def call
|
43
|
+
work = nil
|
44
|
+
|
45
|
+
@guard.synchronize do
|
46
|
+
@thread = ::Thread.current
|
47
|
+
|
48
|
+
return unless work = @work
|
49
|
+
end
|
50
|
+
|
51
|
+
resolve(work.call)
|
52
|
+
rescue Exception => error
|
53
|
+
reject(error)
|
54
|
+
end
|
55
|
+
|
56
|
+
private def resolve(value)
|
57
|
+
@guard.synchronize do
|
58
|
+
@work = nil
|
59
|
+
@thread = nil
|
60
|
+
@value = value
|
61
|
+
@state = :resolved
|
62
|
+
@condition.broadcast
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private def reject(error)
|
67
|
+
@guard.synchronize do
|
68
|
+
@work = nil
|
69
|
+
@thread = nil
|
70
|
+
@value = error
|
71
|
+
@state = :failed
|
72
|
+
@condition.broadcast
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Cancel the work and raise an exception in the background thread.
|
77
|
+
def cancel
|
78
|
+
return unless @work
|
79
|
+
|
80
|
+
@guard.synchronize do
|
81
|
+
@work = nil
|
82
|
+
@state = :cancelled
|
83
|
+
@thread&.raise(Interrupt)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Wait for the work to be done.
|
88
|
+
#
|
89
|
+
# @returns [Object] The result of the work.
|
90
|
+
def wait
|
91
|
+
@guard.synchronize do
|
92
|
+
while @state == :pending
|
93
|
+
@condition.wait(@guard)
|
94
|
+
end
|
95
|
+
|
96
|
+
if @state == :failed
|
97
|
+
raise @value
|
98
|
+
else
|
99
|
+
return @value
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# A background worker thread.
|
106
|
+
class Worker
|
107
|
+
# Create a new worker.
|
108
|
+
def initialize
|
109
|
+
@work = ::Thread::Queue.new
|
110
|
+
@thread = ::Thread.new(&method(:run))
|
111
|
+
end
|
112
|
+
|
113
|
+
# Execute work until the queue is closed.
|
114
|
+
def run
|
115
|
+
while work = @work.pop
|
116
|
+
work.call
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Close the worker thread.
|
121
|
+
def close
|
122
|
+
if thread = @thread
|
123
|
+
@thread = nil
|
124
|
+
thread.kill
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Call the work and notify the scheduler when it is done.
|
129
|
+
def call(work)
|
130
|
+
promise = Promise.new(work)
|
131
|
+
|
132
|
+
@work.push(promise)
|
133
|
+
|
134
|
+
begin
|
135
|
+
return promise.wait
|
136
|
+
ensure
|
137
|
+
promise.cancel
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Create a new work pool.
|
143
|
+
#
|
144
|
+
# @parameter size [Integer] The number of threads to use.
|
145
|
+
def initialize(size: Etc.nprocessors)
|
146
|
+
@ready = ::Thread::Queue.new
|
147
|
+
|
148
|
+
size.times do
|
149
|
+
@ready.push(Worker.new)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Close the work pool. Kills all outstanding work.
|
154
|
+
def close
|
155
|
+
if ready = @ready
|
156
|
+
@ready = nil
|
157
|
+
ready.close
|
158
|
+
|
159
|
+
while worker = ready.pop
|
160
|
+
worker.close
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Offload work to a thread.
|
166
|
+
#
|
167
|
+
# @parameter work [Proc] The work to be done.
|
168
|
+
def call(work)
|
169
|
+
if ready = @ready
|
170
|
+
worker = ready.pop
|
171
|
+
|
172
|
+
begin
|
173
|
+
worker.call(work)
|
174
|
+
ensure
|
175
|
+
ready.push(worker)
|
176
|
+
end
|
177
|
+
else
|
178
|
+
raise RuntimeError, "No worker available!"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
data/lib/async/wrapper.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2017-
|
4
|
+
# Copyright, 2017-2024, by Samuel Williams.
|
5
5
|
# Copyright, 2017, by Kent Gruber.
|
6
6
|
|
7
|
+
warn "Async::Wrapper is deprecated and will be removed on 2025-03-31. Please use native interfaces instead.", uplevel: 1, category: :deprecated
|
8
|
+
|
7
9
|
module Async
|
8
10
|
# Represents an asynchronous IO within a reactor.
|
9
11
|
# @deprecated With no replacement. Prefer native interfaces.
|
data/lib/async.rb
CHANGED
data/lib/kernel/async.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2024, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative "../async/reactor"
|
7
7
|
|
@@ -19,11 +19,13 @@ module Kernel
|
|
19
19
|
# @yields {|task| ...} The block that will execute asynchronously.
|
20
20
|
# @parameter task [Async::Task] The task that is executing the given block.
|
21
21
|
#
|
22
|
-
# @public Since
|
22
|
+
# @public Since *Async v1*.
|
23
23
|
# @asynchronous May block until given block completes executing.
|
24
24
|
def Async(...)
|
25
25
|
if current = ::Async::Task.current?
|
26
26
|
return current.async(...)
|
27
|
+
elsif scheduler = Fiber.scheduler
|
28
|
+
::Async::Task.run(scheduler, ...)
|
27
29
|
else
|
28
30
|
# This calls Fiber.set_scheduler(self):
|
29
31
|
reactor = ::Async::Reactor.new
|
data/lib/kernel/sync.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2024, by Samuel Williams.
|
5
5
|
# Copyright, 2020, by Brian Morearty.
|
6
|
+
# Copyright, 2024, by Patrik Wenger.
|
6
7
|
|
7
8
|
require_relative "../async/reactor"
|
8
9
|
|
@@ -13,17 +14,23 @@ module Kernel
|
|
13
14
|
# @yields {|task| ...} The block that will execute asynchronously.
|
14
15
|
# @parameter task [Async::Task] The task that is executing the given block.
|
15
16
|
#
|
16
|
-
# @public Since
|
17
|
+
# @public Since *Async v1*.
|
17
18
|
# @asynchronous Will block until given block completes executing.
|
18
|
-
def Sync(&block)
|
19
|
+
def Sync(annotation: nil, &block)
|
19
20
|
if task = ::Async::Task.current?
|
20
|
-
|
21
|
+
if annotation
|
22
|
+
task.annotate(annotation) {yield task}
|
23
|
+
else
|
24
|
+
yield task
|
25
|
+
end
|
26
|
+
elsif scheduler = Fiber.scheduler
|
27
|
+
::Async::Task.run(scheduler, &block).wait
|
21
28
|
else
|
22
29
|
# This calls Fiber.set_scheduler(self):
|
23
30
|
reactor = Async::Reactor.new
|
24
31
|
|
25
32
|
begin
|
26
|
-
return reactor.run(finished: ::Async::Condition.new, &block).wait
|
33
|
+
return reactor.run(annotation: annotation, finished: ::Async::Condition.new, &block).wait
|
27
34
|
ensure
|
28
35
|
Fiber.set_scheduler(nil)
|
29
36
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2024, by Samuel Williams.
|
5
|
+
|
6
|
+
require_relative "../../../async/task"
|
7
|
+
require "metrics/provider"
|
8
|
+
|
9
|
+
Metrics::Provider(Async::Task) do
|
10
|
+
ASYNC_TASK_SCHEDULED = Metrics.metric("async.task.scheduled", :counter, description: "The number of tasks scheduled.")
|
11
|
+
ASYNC_TASK_FINISHED = Metrics.metric("async.task.finished", :counter, description: "The number of tasks finished.")
|
12
|
+
|
13
|
+
def schedule(&block)
|
14
|
+
ASYNC_TASK_SCHEDULED.emit(1)
|
15
|
+
|
16
|
+
super(&block)
|
17
|
+
ensure
|
18
|
+
ASYNC_TASK_FINISHED.emit(1)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2022, by Samuel Williams.
|
5
|
+
|
6
|
+
require_relative "../../../async/barrier"
|
7
|
+
require "traces/provider"
|
8
|
+
|
9
|
+
Traces::Provider(Async::Barrier) do
|
10
|
+
def wait
|
11
|
+
attributes = {
|
12
|
+
"size" => self.size
|
13
|
+
}
|
14
|
+
|
15
|
+
Traces.trace("async.barrier.wait", attributes: attributes) {super}
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2022, by Samuel Williams.
|
5
|
+
|
6
|
+
require_relative "../../../async/task"
|
7
|
+
require "traces/provider"
|
8
|
+
|
9
|
+
Traces::Provider(Async::Task) do
|
10
|
+
def schedule(&block)
|
11
|
+
# If we are not actively tracing anything, then we can skip this:
|
12
|
+
unless Traces.active?
|
13
|
+
return super(&block)
|
14
|
+
end
|
15
|
+
|
16
|
+
unless self.transient?
|
17
|
+
trace_context = Traces.trace_context
|
18
|
+
end
|
19
|
+
|
20
|
+
attributes = {
|
21
|
+
# We use the instance variable as it corresponds to the user-provided block.
|
22
|
+
"block" => @block,
|
23
|
+
"transient" => self.transient?,
|
24
|
+
}
|
25
|
+
|
26
|
+
# Run the trace in the context of the child task:
|
27
|
+
super do
|
28
|
+
Traces.trace_context = trace_context
|
29
|
+
|
30
|
+
if annotation = self.annotation
|
31
|
+
attributes["annotation"] = annotation
|
32
|
+
end
|
33
|
+
|
34
|
+
Traces.trace("async.task", attributes: attributes) do
|
35
|
+
# Yes, this is correct, we already called super above:
|
36
|
+
yield
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/license.md
CHANGED
@@ -11,7 +11,7 @@ Copyright, 2020-2023, by Olle Jonsson.
|
|
11
11
|
Copyright, 2020, by Salim Semaoune.
|
12
12
|
Copyright, 2020, by Brian Morearty.
|
13
13
|
Copyright, 2020, by Stefan Wrobel.
|
14
|
-
Copyright, 2020, by Patrik Wenger.
|
14
|
+
Copyright, 2020-2024, by Patrik Wenger.
|
15
15
|
Copyright, 2020, by Ken Muryoi.
|
16
16
|
Copyright, 2020, by Jun Jiang.
|
17
17
|
Copyright, 2020-2022, by Bruno Sutic.
|
@@ -26,6 +26,7 @@ Copyright, 2023, by Math Ieu.
|
|
26
26
|
Copyright, 2023, by Emil Tin.
|
27
27
|
Copyright, 2023, by Gert Goet.
|
28
28
|
Copyright, 2024, by Dimitar Peychinov.
|
29
|
+
Copyright, 2024, by Jamie McCarthy.
|
29
30
|
|
30
31
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
31
32
|
of this software and associated documentation files (the "Software"), to deal
|
data/readme.md
CHANGED
@@ -23,7 +23,7 @@ Please see the [project documentation](https://socketry.github.io/async/) for mo
|
|
23
23
|
|
24
24
|
- [Asynchronous Tasks](https://socketry.github.io/async/guides/asynchronous-tasks/index) - This guide explains how asynchronous tasks work and how to use them.
|
25
25
|
|
26
|
-
- [
|
26
|
+
- [Scheduler](https://socketry.github.io/async/guides/scheduler/index) - This guide gives an overview of how the scheduler is implemented.
|
27
27
|
|
28
28
|
- [Compatibility](https://socketry.github.io/async/guides/compatibility/index) - This guide gives an overview of the compatibility of Async with Ruby and other frameworks.
|
29
29
|
|
@@ -31,23 +31,39 @@ Please see the [project documentation](https://socketry.github.io/async/) for mo
|
|
31
31
|
|
32
32
|
- [Debugging](https://socketry.github.io/async/guides/debugging/index) - This guide explains how to debug issues with programs that use Async.
|
33
33
|
|
34
|
-
##
|
34
|
+
## Releases
|
35
35
|
|
36
|
-
|
36
|
+
Please see the [project releases](https://socketry.github.io/async/releases/index) for all releases.
|
37
37
|
|
38
|
-
|
39
|
-
2. Create your feature branch (`git checkout -b my-new-feature`).
|
40
|
-
3. Commit your changes (`git commit -am 'Add some feature'`).
|
41
|
-
4. Push to the branch (`git push origin my-new-feature`).
|
42
|
-
5. Create new Pull Request.
|
38
|
+
### v2.23.0
|
43
39
|
|
44
|
-
|
40
|
+
- Rename `ASYNC_SCHEDULER_DEFAULT_WORKER_POOL` to `ASYNC_SCHEDULER_WORKER_POOL`.
|
41
|
+
- [Fiber Stall Profiler](https://socketry.github.io/async/releases/index#fiber-stall-profiler)
|
42
|
+
|
43
|
+
### v2.21.1
|
44
|
+
|
45
|
+
- [Worker Pool](https://socketry.github.io/async/releases/index#worker-pool)
|
46
|
+
|
47
|
+
### v2.20.0
|
48
|
+
|
49
|
+
- [Traces and Metrics Providers](https://socketry.github.io/async/releases/index#traces-and-metrics-providers)
|
50
|
+
|
51
|
+
### v2.19.0
|
52
|
+
|
53
|
+
- [Async::Scheduler Debugging](https://socketry.github.io/async/releases/index#async::scheduler-debugging)
|
54
|
+
- [Console Shims](https://socketry.github.io/async/releases/index#console-shims)
|
55
|
+
|
56
|
+
### v2.18.0
|
57
|
+
|
58
|
+
- Add support for `Sync(annotation:)`, so that you can annotate the block with a description of what it does, even if it doesn't create a new task.
|
45
59
|
|
46
|
-
|
60
|
+
### v2.17.0
|
47
61
|
|
48
|
-
|
62
|
+
- Introduce `Async::Queue#push` and `Async::Queue#pop` for compatibility with `::Queue`.
|
49
63
|
|
50
|
-
|
64
|
+
### v2.16.0
|
65
|
+
|
66
|
+
- [Better Handling of Async and Sync in Nested Fibers](https://socketry.github.io/async/releases/index#better-handling-of-async-and-sync-in-nested-fibers)
|
51
67
|
|
52
68
|
## See Also
|
53
69
|
|
@@ -57,3 +73,21 @@ This project is governed by the [Contributor Covenant](https://www.contributor-c
|
|
57
73
|
- [falcon](https://github.com/socketry/falcon) — A rack compatible server built on top of `async-http`.
|
58
74
|
- [rubydns](https://github.com/ioquatix/rubydns) — An easy to use Ruby DNS server.
|
59
75
|
- [slack-ruby-bot](https://github.com/slack-ruby/slack-ruby-bot) — A client for making slack bots.
|
76
|
+
|
77
|
+
## Contributing
|
78
|
+
|
79
|
+
We welcome contributions to this project.
|
80
|
+
|
81
|
+
1. Fork it.
|
82
|
+
2. Create your feature branch (`git checkout -b my-new-feature`).
|
83
|
+
3. Commit your changes (`git commit -am 'Add some feature'`).
|
84
|
+
4. Push to the branch (`git push origin my-new-feature`).
|
85
|
+
5. Create new Pull Request.
|
86
|
+
|
87
|
+
### Developer Certificate of Origin
|
88
|
+
|
89
|
+
In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed.
|
90
|
+
|
91
|
+
### Community Guidelines
|
92
|
+
|
93
|
+
This project is best served by a collaborative and respectful environment. Treat each other professionally, respect differing viewpoints, and engage constructively. Harassment, discrimination, or harmful behavior is not tolerated. Communicate clearly, listen actively, and support one another. If any issues arise, please inform the project maintainers.
|
data/releases.md
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
# Releases
|
2
|
+
|
3
|
+
## v2.23.0
|
4
|
+
|
5
|
+
- Rename `ASYNC_SCHEDULER_DEFAULT_WORKER_POOL` to `ASYNC_SCHEDULER_WORKER_POOL`.
|
6
|
+
|
7
|
+
### Fiber Stall Profiler
|
8
|
+
|
9
|
+
After several iterations of experimentation, we are officially introducing the fiber stall profiler, implemented using the optional `fiber-profiler` gem. This gem is not included by default, but can be added to your project:
|
10
|
+
|
11
|
+
``` bash
|
12
|
+
$ bundle add fiber-profiler
|
13
|
+
```
|
14
|
+
|
15
|
+
After adding the gem, you can enable the fiber stall profiler by setting the `FIBER_PROFILER_CAPTURE=true` environment variable:
|
16
|
+
|
17
|
+
``` bash
|
18
|
+
$ FIBER_PROFILER_CAPTURE=true bundle exec ruby -rasync -e 'Async{Fiber.blocking{sleep 0.1}}'
|
19
|
+
Fiber stalled for 0.105 seconds
|
20
|
+
-e:1 in c-call '#<Class:Fiber>#blocking' (0.105s)
|
21
|
+
-e:1 in c-call 'Kernel#sleep' (0.105s)
|
22
|
+
Skipped 1 calls that were too short to be meaningful.
|
23
|
+
```
|
24
|
+
|
25
|
+
The fiber profiler will help you find problems with your code that cause the event loop to stall, which can be a common source of performance issues in asynchronous code.
|
26
|
+
|
27
|
+
## v2.21.1
|
28
|
+
|
29
|
+
### Worker Pool
|
30
|
+
|
31
|
+
Ruby 3.4 will feature a new fiber scheduler hook, `blocking_operation_wait` which allows the scheduler to redirect the work given to `rb_nogvl` to a worker pool.
|
32
|
+
|
33
|
+
The Async scheduler optionally supports this feature using a worker pool, by using the following environment variable:
|
34
|
+
|
35
|
+
ASYNC_SCHEDULER_WORKER_POOL=true
|
36
|
+
|
37
|
+
This will cause the scheduler to use a worker pool for general blocking operations, rather than blocking the event loop.
|
38
|
+
|
39
|
+
It should be noted that this isn't a net win, as the overhead of using a worker pool can be significant compared to the `rb_nogvl` work. As such, it is recommended to benchmark your application with and without the worker pool to determine if it is beneficial.
|
40
|
+
|
41
|
+
## v2.20.0
|
42
|
+
|
43
|
+
### Traces and Metrics Providers
|
44
|
+
|
45
|
+
Async now has [traces](https://github.com/socketry/traces) and [metrics](https://github.com/socketry/metrics) providers for various core classes. This allows you to emit traces and metrics to a suitable backend (including DataDog, New Relic, OpenTelemetry, etc.) for monitoring and debugging purposes.
|
46
|
+
|
47
|
+
To take advantage of this feature, you will need to introduce your own `config/traces.rb` and `config/metrics.rb`. Async's own repository includes these files for testing purposes, you could copy them into your own project and modify them as needed.
|
48
|
+
|
49
|
+
## v2.19.0
|
50
|
+
|
51
|
+
### Async::Scheduler Debugging
|
52
|
+
|
53
|
+
Occasionally on issues, I encounter people asking for help and I need more information. Pressing Ctrl-C to exit a hung program is common, but it usually doesn't provide enough information to diagnose the problem. Setting the `CONSOLE_LEVEL=debug` environment variable will now print additional information about the scheduler when you interrupt it, including a backtrace of the current tasks.
|
54
|
+
|
55
|
+
> CONSOLE_LEVEL=debug bundle exec ruby ./test.rb
|
56
|
+
^C 0.0s debug: Async::Reactor [oid=0x974] [ec=0x988] [pid=9116] [2024-11-08 14:12:03 +1300]
|
57
|
+
| Scheduler interrupted: Interrupt
|
58
|
+
| #<Async::Reactor:0x0000000000000974 1 children (running)>
|
59
|
+
| #<Async::Task:0x000000000000099c /Users/samuel/Developer/socketry/async/lib/async/scheduler.rb:185:in `transfer' (running)>
|
60
|
+
| → /Users/samuel/Developer/socketry/async/lib/async/scheduler.rb:185:in `transfer'
|
61
|
+
| /Users/samuel/Developer/socketry/async/lib/async/scheduler.rb:185:in `block'
|
62
|
+
| /Users/samuel/Developer/socketry/async/lib/async/scheduler.rb:207:in `kernel_sleep'
|
63
|
+
| /Users/samuel/Developer/socketry/async/test.rb:7:in `sleep'
|
64
|
+
| /Users/samuel/Developer/socketry/async/test.rb:7:in `sleepy'
|
65
|
+
| /Users/samuel/Developer/socketry/async/test.rb:12:in `block in <top (required)>'
|
66
|
+
| /Users/samuel/Developer/socketry/async/lib/async/task.rb:197:in `block in run'
|
67
|
+
| /Users/samuel/Developer/socketry/async/lib/async/task.rb:420:in `block in schedule'
|
68
|
+
/Users/samuel/Developer/socketry/async/lib/async/scheduler.rb:317:in `select': Interrupt
|
69
|
+
... (backtrace continues) ...
|
70
|
+
|
71
|
+
This gives better visibility into what the scheduler is doing, and should help diagnose issues.
|
72
|
+
|
73
|
+
### Console Shims
|
74
|
+
|
75
|
+
The `async` gem depends on `console` gem, because my goal was to have good logging by default without thinking about it too much. However, some users prefer to avoid using the `console` gem for logging, so I've added an experimental set of shims which should allow you to bypass the `console` gem entirely.
|
76
|
+
|
77
|
+
``` ruby
|
78
|
+
require 'async/console'
|
79
|
+
require 'async'
|
80
|
+
|
81
|
+
Async{raise "Boom"}
|
82
|
+
```
|
83
|
+
|
84
|
+
Will now use `Kernel#warn` to print the task failure warning:
|
85
|
+
|
86
|
+
#<Async::Task:0x00000000000012d4 /home/samuel/Developer/socketry/async/lib/async/task.rb:104:in `backtrace' (running)>
|
87
|
+
Task may have ended with unhandled exception.
|
88
|
+
(irb):4:in `block in <top (required)>': Boom (RuntimeError)
|
89
|
+
from /home/samuel/Developer/socketry/async/lib/async/task.rb:197:in `block in run'
|
90
|
+
from /home/samuel/Developer/socketry/async/lib/async/task.rb:420:in `block in schedule'
|
91
|
+
|
92
|
+
## v2.18.0
|
93
|
+
|
94
|
+
- Add support for `Sync(annotation:)`, so that you can annotate the block with a description of what it does, even if it doesn't create a new task.
|
95
|
+
|
96
|
+
## v2.17.0
|
97
|
+
|
98
|
+
- Introduce `Async::Queue#push` and `Async::Queue#pop` for compatibility with `::Queue`.
|
99
|
+
|
100
|
+
## v2.16.0
|
101
|
+
|
102
|
+
### Better Handling of Async and Sync in Nested Fibers
|
103
|
+
|
104
|
+
Interleaving bare fibers within `Async` and `Sync` blocks should not cause problems, but it presents a number of issues in the current implementation. Tracking the parent-child relationship between tasks, when they are interleaved with bare fibers, is difficult. The current implementation assumes that if there is no parent task, then it should create a new reactor. This is not always the case, as the parent task might not be visible due to nested Fibers. As a result, `Async` will create a new reactor, trying to stop the existing one, causing major internal consistency issues.
|
105
|
+
|
106
|
+
I encountered this issue when trying to use `Async` within a streaming response in Rails. The `protocol-rack` [uses a normal fiber to wrap streaming responses](https://github.com/socketry/protocol-rack/blob/cb1ca44e9deadb9369bdb2ea03416556aa927c5c/lib/protocol/rack/body/streaming.rb#L24-L28), and if you try to use `Async` within it, it will create a new reactor, causing the server to lock up.
|
107
|
+
|
108
|
+
Ideally, `Async` and `Sync` helpers should work when any `Fiber.scheduler` is defined. Right now, it's unrealistic to expect `Async::Task` to work in any scheduler, but at the very least, the following should work:
|
109
|
+
|
110
|
+
``` ruby
|
111
|
+
reactor = Async::Reactor.new # internally calls Fiber.set_scheduler
|
112
|
+
|
113
|
+
# This should run in the above reactor, rather than creating a new one.
|
114
|
+
Async do
|
115
|
+
puts "Hello World"
|
116
|
+
end
|
117
|
+
```
|
118
|
+
|
119
|
+
In order to do this, bare `Async` and `Sync` blocks should use `Fiber.scheduler` as a parent if possible.
|
120
|
+
|
121
|
+
See <https://github.com/socketry/async/pull/340> for more details.
|
data.tar.gz.sig
CHANGED
Binary file
|