async 2.24.0 → 2.27.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.
@@ -1,182 +0,0 @@
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.21* 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