async 2.21.1 → 2.23.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6dda1de8f976c85bf6dcbd0156c27c30f85f88d5ffaf32f3397b9838aa920bab
4
- data.tar.gz: 0f09cde08880db4456bc03e9a4351903b9a96ca7099e7305c76fdee7f8edc0ed
3
+ metadata.gz: dedd26d802fb259e0eff2223a86a54f42b46753ab43014b091a83b97610346a0
4
+ data.tar.gz: 682ab4c4b3798df642e3937b902f4a0d0762ce0dfd91f5c2430205775b2bbe8a
5
5
  SHA512:
6
- metadata.gz: 980cd5e5832ddd71846785e10b23d18d11cb1f0eee33ccbdf33ed40feb4ac51dd99f7cf3d7f93055d0ad52de2abe0103c1b433d41f5bac1728c04435e35655db
7
- data.tar.gz: d9c7c1c69e49acd89738f44900502bc83aea08341b84a9f8d9e02588261889a24a935dfa6509394514a575a4b17d8dcf0ca512518e8b0e089369a8fd37b44d52
6
+ metadata.gz: d45d3a428c1403b999be51a831e44206ed1636bbfd7603cee7c4e71d03ec2e2e3941b329deb30b18aa4b1021f0b0360726898017ee48933a2262ebaf6c1980df
7
+ data.tar.gz: da44d07b913e0efff2aff07ff545f5996806601b33ca69b707c5e462d0858ad85e613b5ad6b8d4d53cdd21dd1d80852acf5b71b7359b3b26c2ab10437730873c
checksums.yaml.gz.sig CHANGED
Binary file
data/lib/async/barrier.md CHANGED
@@ -18,7 +18,7 @@ Sync do
18
18
  # Sleep sort the numbers:
19
19
  numbers.each do |number|
20
20
  barrier.async do |task|
21
- task.sleep(number)
21
+ sleep(number)
22
22
  sorted << number
23
23
  end
24
24
  end
data/lib/async/clock.rb CHANGED
@@ -61,5 +61,14 @@ module Async
61
61
 
62
62
  return total
63
63
  end
64
+
65
+ # Reset the total elapsed time. If the clock is currently running, reset the start time to now.
66
+ def reset!
67
+ @total = 0
68
+
69
+ if @started
70
+ @started = Clock.now
71
+ end
72
+ end
64
73
  end
65
74
  end
@@ -15,7 +15,7 @@ Sync do
15
15
  end
16
16
 
17
17
  Async do |task|
18
- task.sleep(1)
18
+ sleep(1)
19
19
  Console.info "Signalling condition..."
20
20
  condition.signal("Hello World")
21
21
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2024, by Samuel Williams.
5
+
6
+ # The implementation lives in `queue.rb` but later we may move it here for better autoload/inference.
7
+ require_relative "queue"
data/lib/async/queue.rb CHANGED
@@ -45,7 +45,9 @@ module Async
45
45
  end
46
46
 
47
47
  # Compatibility with {::Queue#push}.
48
- alias << push
48
+ def <<(item)
49
+ self.push(item)
50
+ end
49
51
 
50
52
  # Add multiple items to the queue.
51
53
  def enqueue(*items)
@@ -64,7 +66,9 @@ module Async
64
66
  end
65
67
 
66
68
  # Compatibility with {::Queue#pop}.
67
- alias pop dequeue
69
+ def pop
70
+ self.dequeue
71
+ end
68
72
 
69
73
  # Process each item in the queue.
70
74
  #
@@ -125,7 +129,7 @@ module Async
125
129
  # If the queue is full, this method will block until there is space available.
126
130
  #
127
131
  # @parameter item [Object] The item to add to the queue.
128
- def <<(item)
132
+ def push(item)
129
133
  while limited?
130
134
  @full.wait
131
135
  end
@@ -15,9 +15,17 @@ require "console"
15
15
  require "resolv"
16
16
 
17
17
  module Async
18
+ begin
19
+ require "fiber/profiler"
20
+ Profiler = Fiber::Profiler
21
+ rescue LoadError
22
+ # Fiber::Profiler is not available.
23
+ Profiler = nil
24
+ end
25
+
18
26
  # Handles scheduling of fibers. Implements the fiber scheduler interface.
19
27
  class Scheduler < Node
20
- DEFAULT_WORKER_POOL = ENV.fetch("ASYNC_SCHEDULER_DEFAULT_WORKER_POOL", nil).then do |value|
28
+ WORKER_POOL = ENV.fetch("ASYNC_SCHEDULER_WORKER_POOL", nil).then do |value|
21
29
  value == "true" ? true : nil
22
30
  end
23
31
 
@@ -42,10 +50,12 @@ module Async
42
50
  # @public Since *Async v1*.
43
51
  # @parameter parent [Node | Nil] The parent node to use for task hierarchy.
44
52
  # @parameter selector [IO::Event::Selector] The selector to use for event handling.
45
- def initialize(parent = nil, selector: nil, worker_pool: DEFAULT_WORKER_POOL)
53
+ def initialize(parent = nil, selector: nil, profiler: Profiler&.default, worker_pool: WORKER_POOL)
46
54
  super(parent)
47
55
 
48
56
  @selector = selector || ::IO::Event::Selector.new(Fiber.current)
57
+ @profiler = profiler
58
+
49
59
  @interrupted = false
50
60
 
51
61
  @blocked = 0
@@ -492,13 +502,19 @@ module Async
492
502
  def run(...)
493
503
  Kernel.raise ClosedError if @selector.nil?
494
504
 
495
- initial_task = self.async(...) if block_given?
496
-
497
- self.run_loop do
498
- run_once
505
+ begin
506
+ @profiler&.start
507
+
508
+ initial_task = self.async(...) if block_given?
509
+
510
+ self.run_loop do
511
+ run_once
512
+ end
513
+
514
+ return initial_task
515
+ ensure
516
+ @profiler&.stop
499
517
  end
500
-
501
- return initial_task
502
518
  end
503
519
 
504
520
  # Start an asynchronous task within the specified reactor. The task will be executed until the first blocking call, at which point it will yield and and this method will return.
data/lib/async/task.rb CHANGED
@@ -181,7 +181,10 @@ module Async
181
181
  @status == :completed
182
182
  end
183
183
 
184
- alias complete? completed?
184
+ # Alias for {#completed?}.
185
+ def complete?
186
+ self.completed?
187
+ end
185
188
 
186
189
  # @attribute [Symbol] The status of the execution of the task, one of `:initialized`, `:running`, `:complete`, `:stopped` or `:failed`.
187
190
  attr :status
@@ -31,7 +31,10 @@ module Async
31
31
  condition.signal(value)
32
32
  end
33
33
 
34
- alias value= resolve
34
+ # Alias for {#resolve}.
35
+ def value=(value)
36
+ self.resolve(value)
37
+ end
35
38
 
36
39
  # Whether the value has been resolved.
37
40
  #
@@ -48,6 +51,9 @@ module Async
48
51
  return @value
49
52
  end
50
53
 
51
- alias value wait
54
+ # Alias for {#wait}.
55
+ def value
56
+ self.wait
57
+ end
52
58
  end
53
59
  end
data/lib/async/version.rb CHANGED
@@ -4,5 +4,5 @@
4
4
  # Copyright, 2017-2024, by Samuel Williams.
5
5
 
6
6
  module Async
7
- VERSION = "2.21.1"
7
+ VERSION = "2.23.0"
8
8
  end
@@ -10,6 +10,7 @@ module Async
10
10
  #
11
11
  # @private
12
12
  class WorkerPool
13
+ # Used to augment the scheduler to add support for blocking operations.
13
14
  module BlockingOperationWait
14
15
  # Wait for the given work to be executed.
15
16
  #
@@ -23,7 +24,11 @@ module Async
23
24
  end
24
25
  end
25
26
 
27
+ # Execute the given work in a background thread.
26
28
  class Promise
29
+ # Create a new promise.
30
+ #
31
+ # @parameter work [Proc] The work to be done.
27
32
  def initialize(work)
28
33
  @work = work
29
34
  @state = :pending
@@ -33,6 +38,7 @@ module Async
33
38
  @thread = nil
34
39
  end
35
40
 
41
+ # Execute the work and resolve the promise.
36
42
  def call
37
43
  work = nil
38
44
 
@@ -67,6 +73,7 @@ module Async
67
73
  end
68
74
  end
69
75
 
76
+ # Cancel the work and raise an exception in the background thread.
70
77
  def cancel
71
78
  return unless @work
72
79
 
@@ -77,6 +84,9 @@ module Async
77
84
  end
78
85
  end
79
86
 
87
+ # Wait for the work to be done.
88
+ #
89
+ # @returns [Object] The result of the work.
80
90
  def wait
81
91
  @guard.synchronize do
82
92
  while @state == :pending
@@ -92,19 +102,22 @@ module Async
92
102
  end
93
103
  end
94
104
 
95
- # A handle to the work being done.
105
+ # A background worker thread.
96
106
  class Worker
107
+ # Create a new worker.
97
108
  def initialize
98
109
  @work = ::Thread::Queue.new
99
110
  @thread = ::Thread.new(&method(:run))
100
111
  end
101
112
 
113
+ # Execute work until the queue is closed.
102
114
  def run
103
115
  while work = @work.pop
104
116
  work.call
105
117
  end
106
118
  end
107
119
 
120
+ # Close the worker thread.
108
121
  def close
109
122
  if thread = @thread
110
123
  @thread = nil
@@ -8,10 +8,13 @@ require "metrics/provider"
8
8
 
9
9
  Metrics::Provider(Async::Task) do
10
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.")
11
12
 
12
13
  def schedule(&block)
13
14
  ASYNC_TASK_SCHEDULED.emit(1)
14
15
 
15
16
  super(&block)
17
+ ensure
18
+ ASYNC_TASK_FINISHED.emit(1)
16
19
  end
17
20
  end
@@ -8,20 +8,31 @@ require "traces/provider"
8
8
 
9
9
  Traces::Provider(Async::Task) do
10
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
+
11
16
  unless self.transient?
12
17
  trace_context = Traces.trace_context
13
18
  end
14
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:
15
27
  super do
16
28
  Traces.trace_context = trace_context
17
29
 
18
30
  if annotation = self.annotation
19
- attributes = {
20
- "annotation" => annotation
21
- }
31
+ attributes["annotation"] = annotation
22
32
  end
23
33
 
24
34
  Traces.trace("async.task", attributes: attributes) do
35
+ # Yes, this is correct, we already called super above:
25
36
  yield
26
37
  end
27
38
  end
data/readme.md CHANGED
@@ -7,6 +7,7 @@ Async is a composable asynchronous I/O framework for Ruby based on [io-event](ht
7
7
  > beautifully designed." *– [janko](https://github.com/janko)*
8
8
 
9
9
  [![Development Status](https://github.com/socketry/async/workflows/Test/badge.svg)](https://github.com/socketry/async/actions?workflow=Test)
10
+ [<img src="https://api.gitsponsors.com/api/badge/img?id=87380483" height="20"/>](https://api.gitsponsors.com/api/badge/link?p=U4gCxvzG7eUksiJSe0MSlPHWWhBYryqj6i48tx5L7/r/2NgkAToKb6dEm31bAftU3H+7BVwk3VhUBtE4GHqHJTPfWPR6xo2BQVoT15rFAGAsLFgdT2kKopIfCGV/QDOm7BrkodS2//R7NUMksAdaCQ==)
10
11
 
11
12
  ## Features
12
13
 
@@ -23,7 +24,7 @@ Please see the [project documentation](https://socketry.github.io/async/) for mo
23
24
 
24
25
  - [Asynchronous Tasks](https://socketry.github.io/async/guides/asynchronous-tasks/index) - This guide explains how asynchronous tasks work and how to use them.
25
26
 
26
- - [Event Loop](https://socketry.github.io/async/guides/event-loop/index) - This guide gives an overview of how the event loop is implemented.
27
+ - [Scheduler](https://socketry.github.io/async/guides/scheduler/index) - This guide gives an overview of how the scheduler is implemented.
27
28
 
28
29
  - [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
30
 
@@ -35,6 +36,11 @@ Please see the [project documentation](https://socketry.github.io/async/) for mo
35
36
 
36
37
  Please see the [project releases](https://socketry.github.io/async/releases/index) for all releases.
37
38
 
39
+ ### v2.23.0
40
+
41
+ - Rename `ASYNC_SCHEDULER_DEFAULT_WORKER_POOL` to `ASYNC_SCHEDULER_WORKER_POOL`.
42
+ - [Fiber Stall Profiler](https://socketry.github.io/async/releases/index#fiber-stall-profiler)
43
+
38
44
  ### v2.21.1
39
45
 
40
46
  - [Worker Pool](https://socketry.github.io/async/releases/index#worker-pool)
data/releases.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Releases
2
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
+
3
27
  ## v2.21.1
4
28
 
5
29
  ### Worker Pool
@@ -8,7 +32,7 @@ Ruby 3.4 will feature a new fiber scheduler hook, `blocking_operation_wait` whic
8
32
 
9
33
  The Async scheduler optionally supports this feature using a worker pool, by using the following environment variable:
10
34
 
11
- ASYNC_SCHEDULER_DEFAULT_WORKER_POOL=true
35
+ ASYNC_SCHEDULER_WORKER_POOL=true
12
36
 
13
37
  This will cause the scheduler to use a worker pool for general blocking operations, rather than blocking the event loop.
14
38
 
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.21.1
4
+ version: 2.23.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -31,7 +31,6 @@ authors:
31
31
  - Sokolov Yura
32
32
  - Stefan Wrobel
33
33
  - Trevor Turk
34
- autorequire:
35
34
  bindir: bin
36
35
  cert_chain:
37
36
  - |
@@ -63,7 +62,7 @@ cert_chain:
63
62
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
64
63
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
65
64
  -----END CERTIFICATE-----
66
- date: 2024-11-27 00:00:00.000000000 Z
65
+ date: 2025-02-13 00:00:00.000000000 Z
67
66
  dependencies:
68
67
  - !ruby/object:Gem::Dependency
69
68
  name: console
@@ -99,22 +98,42 @@ dependencies:
99
98
  requirements:
100
99
  - - "~>"
101
100
  - !ruby/object:Gem::Version
102
- version: '1.6'
103
- - - ">="
101
+ version: '1.9'
102
+ type: :runtime
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - "~>"
107
+ - !ruby/object:Gem::Version
108
+ version: '1.9'
109
+ - !ruby/object:Gem::Dependency
110
+ name: traces
111
+ requirement: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - "~>"
104
114
  - !ruby/object:Gem::Version
105
- version: 1.6.5
115
+ version: '0.15'
106
116
  type: :runtime
107
117
  prerelease: false
108
118
  version_requirements: !ruby/object:Gem::Requirement
109
119
  requirements:
110
120
  - - "~>"
111
121
  - !ruby/object:Gem::Version
112
- version: '1.6'
113
- - - ">="
122
+ version: '0.15'
123
+ - !ruby/object:Gem::Dependency
124
+ name: metrics
125
+ requirement: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - "~>"
128
+ - !ruby/object:Gem::Version
129
+ version: '0.12'
130
+ type: :runtime
131
+ prerelease: false
132
+ version_requirements: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - "~>"
114
135
  - !ruby/object:Gem::Version
115
- version: 1.6.5
116
- description:
117
- email:
136
+ version: '0.12'
118
137
  executables: []
119
138
  extensions: []
120
139
  extra_rdoc_files: []
@@ -127,6 +146,7 @@ files:
127
146
  - lib/async/condition.rb
128
147
  - lib/async/console.rb
129
148
  - lib/async/idler.rb
149
+ - lib/async/limited_queue.rb
130
150
  - lib/async/list.rb
131
151
  - lib/async/node.rb
132
152
  - lib/async/notification.rb
@@ -160,7 +180,6 @@ metadata:
160
180
  documentation_uri: https://socketry.github.io/async/
161
181
  funding_uri: https://github.com/sponsors/ioquatix/
162
182
  source_code_uri: https://github.com/socketry/async.git
163
- post_install_message:
164
183
  rdoc_options: []
165
184
  require_paths:
166
185
  - lib
@@ -175,8 +194,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
175
194
  - !ruby/object:Gem::Version
176
195
  version: '0'
177
196
  requirements: []
178
- rubygems_version: 3.5.22
179
- signing_key:
197
+ rubygems_version: 3.6.2
180
198
  specification_version: 4
181
199
  summary: A concurrency framework for Ruby.
182
200
  test_files: []
metadata.gz.sig CHANGED
Binary file