core-async 0.6.1 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff90fa7ad80389d797bf7890df85843e32a6312eabee3ba15e62ff29eafe8d68
4
- data.tar.gz: 60d9229247f2a98fb08eea3961019bb2304dff3d9bb056676647923ea21628af
3
+ metadata.gz: 84f855320eba3545932e5019d840f3e7fbcc6c45bba6de79a60dd3101f9ef5e7
4
+ data.tar.gz: 27a66e580273043a1a3377be330f0365cf27f749320dd1d9108ec72ff01f40e3
5
5
  SHA512:
6
- metadata.gz: f4fddf435f4246ef8094317f658064e8e45e6ca8a30257b0bb4b9afea1658d429b0d87daf5a142a695228c1f85c61c5de33a3c0d3414edfa15b1a4504da79ebc
7
- data.tar.gz: b888a83b7fba674b0a1e3555295f1052d7044770df9f52bb06f9840160b5f65acfa0383f48bbe045349a950a8624ee4e85f0511c5ed62a7ab60b3dd815d59860
6
+ metadata.gz: da5c9211dbd194e0ad48b443635e10b93ef4937f1a677f4de441274b98b3b2509f9800aeb9305fa64ac769487a5085d95dff79cc9d58ff9ee15ea58cc970e05c
7
+ data.tar.gz: 90be0a94b5ec20888d9c2b6eb2c15e7bb1ea8a8745e28575ff5eaf17ca3ff97377fd4ce2a16e0def9c74830d3ca02ad6b277f227d938a19c401f72327dfde658
data/CHANGELOG.md CHANGED
@@ -1,3 +1,28 @@
1
+ ## [v0.10.0](https://github.com/metabahn/corerb/releases/tag/2021-11-02)
2
+
3
+ *released on 2021-11-02*
4
+
5
+ * `chg` [#97](https://github.com/metabahn/corerb/pull/97) Designate internal state with leading and trailing double underscores ([bryanp](https://github.com/bryanp))
6
+ * `chg` [#95](https://github.com/metabahn/corerb/pull/95) Change the approach to shutting down the scheduler on interrupt ([bryanp](https://github.com/bryanp))
7
+
8
+ ## [v0.9.0](https://github.com/metabahn/corerb/releases/tag/2021-10-24)
9
+
10
+ *released on 2021-10-24*
11
+
12
+ * `chg` [#82](https://github.com/metabahn/corerb/pull/82) Refactor to use a new fiber scheduler ([bryanp](https://github.com/bryanp))
13
+
14
+ ## [v0.8.0](https://github.com/metabahn/corerb/releases/tag/2021-07-15)
15
+
16
+ *released on 2021-07-15*
17
+
18
+ * `add` [#50](https://github.com/metabahn/corerb/pull/50) Make all async objects inspectable ([bryanp](https://github.com/bryanp))
19
+
20
+ ## [v0.7.0](https://github.com/metabahn/corerb/releases/tag/2021-07-07)
21
+
22
+ *released on 2021-07-07*
23
+
24
+ * `chg` [#38](https://github.com/metabahn/corerb/pull/38) Drop Ruby 2.6 support from core-async ([bryanp](https://github.com/bryanp))
25
+
1
26
  ## [v0.6.1](https://github.com/metabahn/corerb/releases/tag/2021-05-20.1)
2
27
 
3
28
  *released on 2021-05-20*
@@ -1,7 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "async"
3
+ require_relative "scheduler"
4
4
 
5
- # Turn the console logger off everywhere.
5
+ # Define a top-level scheduler.
6
6
  #
7
- ENV["CONSOLE_LEVEL"] ||= "5"
7
+ scheduler = Core::Async::Scheduler.new
8
+ Fiber.set_scheduler(scheduler)
9
+
10
+ # Automatically run the top-level scheduler at exit, blocking until all fibers are complete.
11
+ #
12
+ at_exit { scheduler.run }
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "is/inspectable"
4
+
5
+ module Core
6
+ module Async
7
+ # [public] Raised when the fiber is canceled.
8
+ #
9
+ class Cancel < RuntimeError
10
+ include Is::Inspectable
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "is/inspectable"
4
+
5
+ module Core
6
+ module Async
7
+ # [public] Channels let fibers wait on future published values.
8
+ #
9
+ class Channel
10
+ include Is::Inspectable
11
+ inspects without: [:@subscribers]
12
+
13
+ def initialize
14
+ @subscribers = []
15
+ end
16
+
17
+ # [public] Subscribe the current fiber, blocking until a value is published.
18
+ #
19
+ def subscribe
20
+ @subscribers << Fiber.current
21
+ Fiber.scheduler.transfer
22
+ end
23
+
24
+ # [public] Publish the given value to all subscribers.
25
+ #
26
+ def publish(value = nil)
27
+ subscribers = @subscribers
28
+ @subscribers = []
29
+
30
+ while (fiber = subscribers.shift)
31
+ if fiber.alive?
32
+ Fiber.scheduler.resume(fiber, value)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "is/inspectable"
4
+
3
5
  require_relative "../../is/async"
4
6
 
5
7
  module Core
@@ -11,13 +13,13 @@ module Core
11
13
  # [public] Builds a collection from an enumerable object.
12
14
  #
13
15
  def build(object)
14
- aware do
15
- errored, stopped = false
16
-
17
- values = object.map { |value|
18
- break if errored || stopped
16
+ errored = false
17
+ stopped = false
18
+ values = []
19
19
 
20
- async do
20
+ object.each do |value|
21
+ future = async {
22
+ begin
21
23
  if block_given?
22
24
  yield value
23
25
  else
@@ -31,13 +33,22 @@ module Core
31
33
  end
32
34
  }
33
35
 
34
- new(values) if values
36
+ unless stopped
37
+ values << future
38
+ end
39
+
40
+ if errored || stopped
41
+ break
42
+ end
35
43
  end
44
+
45
+ new(values)
36
46
  end
37
47
  end
38
48
 
39
49
  include Enumerable
40
50
  include Is::Async
51
+ include Is::Inspectable
41
52
 
42
53
  def initialize(values = [])
43
54
  unless values.respond_to?(:each)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "is/inspectable"
4
+
3
5
  require_relative "../../is/async"
4
6
 
5
7
  module Core
@@ -7,6 +9,7 @@ module Core
7
9
  class Enumerator
8
10
  include Enumerable
9
11
  include Is::Async
12
+ include Is::Inspectable
10
13
 
11
14
  def initialize(object)
12
15
  unless object.respond_to?(:each)
@@ -23,22 +26,24 @@ module Core
23
26
  return to_enum(:each)
24
27
  end
25
28
 
26
- await do
27
- errored, stopped = false
29
+ errored = false
30
+ stopped = false
31
+ futures = []
28
32
 
29
- @object.each do |value|
30
- break if errored || stopped
33
+ @object.each do |value|
34
+ break if errored || stopped
31
35
 
32
- async do
33
- yield value
34
- rescue LocalJumpError
35
- stopped = true
36
- rescue => error
37
- errored = true
38
- raise error
39
- end
36
+ futures << async do
37
+ yield value
38
+ rescue LocalJumpError
39
+ stopped = true
40
+ rescue => error
41
+ errored = true
42
+ raise error
40
43
  end
41
44
  end
45
+
46
+ futures.each(&:wait)
42
47
  end
43
48
  end
44
49
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "is/inspectable"
4
+
5
+ module Core
6
+ module Async
7
+ # [public] Raised internally to communicate a fiber error.
8
+ #
9
+ class Failure
10
+ include Is::Inspectable
11
+
12
+ def initialize(error)
13
+ @error = error
14
+ end
15
+
16
+ # [public] The original error.
17
+ #
18
+ attr_reader :error
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "is/inspectable"
4
+
5
+ require_relative "channel"
6
+
7
+ module Core
8
+ module Async
9
+ # Internal object that wraps a fiber.
10
+ #
11
+ class Filament
12
+ include Is::Inspectable
13
+ inspects without: [:@object, :@channel, :@children, :@resolved]
14
+
15
+ def initialize
16
+ @object = nil
17
+ @channel = nil
18
+ @children = nil
19
+ @resolved = false
20
+ @value = nil
21
+ end
22
+
23
+ attr_accessor :object
24
+ attr_reader :children
25
+
26
+ def add_child(fiber)
27
+ (@children ||= []) << fiber
28
+ end
29
+
30
+ def resolve(value = nil)
31
+ unless @resolved
32
+ @value = value
33
+ @resolved = true
34
+ @channel&.publish(value)
35
+ end
36
+ end
37
+
38
+ def wait
39
+ if @resolved == false && @object&.alive?
40
+ (@channel ||= Channel.new).subscribe
41
+ else
42
+ @value
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -1,51 +1,133 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "is/inspectable"
4
+
5
+ require_relative "cancel"
6
+ require_relative "failure"
7
+ require_relative "scheduler"
8
+
3
9
  module Core
4
10
  module Async
5
11
  # [public] Represents a future result.
6
12
  #
7
13
  class Future
8
- def initialize(task)
9
- @task = task
14
+ include Is::Inspectable
15
+ inspects without: [:@fiber, :@raised, :@scheduler, :@filament]
16
+
17
+ def initialize(&block)
10
18
  @error = nil
19
+ @result = nil
20
+ @status = :pending
21
+ @raised = false
22
+ @fiber = Fiber.schedule { |filament|
23
+ begin
24
+ @filament = filament
25
+ value = block.call
26
+ @result = resolve_value(value)
27
+ @status = :complete
28
+ @result
29
+ rescue Cancel
30
+ @status = :canceled
31
+ nil
32
+ rescue => error
33
+ @error = error
34
+ @status = :failed
35
+ raise error
36
+ end
37
+ }
11
38
  end
12
39
 
13
- # [public] Wait on the future to resolve, returning self.
40
+ # [public] The current status; one of :pending, :failed, :canceled, or :complete.
41
+ #
42
+ attr_reader :status
43
+
44
+ # [public] Wait on the future to resolve (including children), returning self.
14
45
  #
15
46
  def wait
16
- unless @error
17
- resolve
18
- end
47
+ resolve
19
48
 
20
49
  self
21
50
  end
22
51
 
23
- # [public] Attempt to cancel the future, returns true if successful.
52
+ # [public] Attempt to cancel the future, returning true if successful.
24
53
  #
25
54
  def cancel
26
55
  if pending?
27
- @task.stop
56
+ Fiber.scheduler.cancel(@fiber)
28
57
  end
29
58
 
30
59
  self
31
60
  end
32
61
 
33
- # [public] Return the result, blocking until available.
62
+ # [public] Wait until the result is available, returning the result. Only awaits explicitly awaited children.
34
63
  #
35
64
  def result
36
- @error || @result ||= resolve
37
- end
38
-
39
- private def resolve
40
- wait_all(@task)
65
+ resolve(false)
66
+ end
67
+
68
+ private def resolve(children = true)
69
+ case @status
70
+ when :pending
71
+ scheduler = Fiber.scheduler
72
+
73
+ result = catch :__corerb_async_future_failed__ do
74
+ if scheduler.running?
75
+ if children
76
+ resolve_all(@filament)
77
+ else
78
+ resolve_value(@filament.wait)
79
+ end
80
+ else
81
+ result = nil
82
+ finished = false
83
+
84
+ scheduler.fiber do
85
+ result = catch :__corerb_async_future_failed__ do
86
+ if children
87
+ resolve_all(@filament)
88
+ else
89
+ resolve_value(@filament.wait)
90
+ end
91
+ end
92
+
93
+ finished = true
94
+ end
95
+
96
+ scheduler.observe do
97
+ break if finished
98
+ end
99
+
100
+ resolve_value(result)
101
+ end
102
+ end
41
103
 
42
- resolve_value(@task.result)
104
+ case result
105
+ when Failure
106
+ @raised = true
107
+ raise result.error
108
+ else
109
+ result
110
+ end
111
+ when :failed
112
+ if @raised
113
+ @error
114
+ else
115
+ @raised = true
116
+ raise @error
117
+ end
118
+ else
119
+ @result
120
+ end
43
121
  rescue UncaughtThrowError => error
44
122
  throw error.tag, error.value
45
- rescue => error
46
- @error = error
123
+ end
124
+
125
+ private def resolve_all(filament)
126
+ filament.children&.each do |child|
127
+ resolve_all(child)
128
+ end
47
129
 
48
- raise error
130
+ resolve_value(filament.wait)
49
131
  end
50
132
 
51
133
  # [public] Return any error that occurred without re-raising, blocking until available.
@@ -61,60 +143,28 @@ module Core
61
143
  error
62
144
  end
63
145
 
64
- # [public] Return the status; one of :pending, :failed, or :complete.
65
- #
66
- def status
67
- if defined?(@status)
68
- @status
69
- else
70
- status = find_status
71
- if terminal_status?(status)
72
- @status = status
73
- end
74
-
75
- status
76
- end
77
- end
78
-
79
- private def find_status
80
- case @task.status
81
- when :running
82
- :pending
83
- when :stopped
84
- :canceled
85
- when :failed
86
- :failed
87
- when :complete
88
- :complete
89
- end
90
- end
91
-
92
- private def terminal_status?(status)
93
- status == :failed || status == :complete
94
- end
95
-
96
146
  # [public] Return `true` if pending.
97
147
  #
98
148
  def pending?
99
- status == :pending
149
+ @status == :pending
100
150
  end
101
151
 
102
152
  # [public] Return `true` if failed.
103
153
  #
104
154
  def failed?
105
- status == :failed
155
+ @status == :failed
106
156
  end
107
157
 
108
158
  # [public] Return `true` if canceled.
109
159
  #
110
160
  def canceled?
111
- status == :canceled
161
+ @status == :canceled
112
162
  end
113
163
 
114
164
  # [public] Return `true` if complete.
115
165
  #
116
166
  def complete?
117
- status == :complete
167
+ @status == :complete
118
168
  end
119
169
 
120
170
  # [public] Return `true` if failed or complete.
@@ -124,20 +174,15 @@ module Core
124
174
  end
125
175
 
126
176
  private def resolve_value(value)
127
- if value.is_a?(self.class)
177
+ case value
178
+ when self.class
128
179
  resolve_value(value.result)
180
+ when Failure
181
+ throw :__corerb_async_future_failed__, value
129
182
  else
130
183
  value
131
184
  end
132
185
  end
133
-
134
- private def wait_all(task)
135
- task.children&.each do |child|
136
- wait_all(child)
137
- end
138
-
139
- task.wait
140
- end
141
186
  end
142
187
  end
143
188
  end
@@ -1,50 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "is/inspectable"
4
+
3
5
  require_relative "../../is/async"
6
+ require_relative "scheduler"
4
7
 
5
8
  module Core
6
9
  module Async
7
- # [public] The top-level async context. Runs until all scheduled work is complete.
10
+ # [public] Creates a top-level async context that runs until all scheduled work is complete.
8
11
  #
9
- class Reactor
12
+ class Reactor < Scheduler
10
13
  include Is::Async
14
+ include Is::Inspectable
15
+ inspects without: [:@runnable]
11
16
 
12
17
  class << self
13
18
  # [public] Create a new reactor and immediately run it.
14
19
  #
15
20
  def run(&block)
16
21
  instance = new
22
+ original_scheduler = Fiber.scheduler
23
+ Fiber.set_scheduler(instance)
17
24
  instance.run(&block)
25
+ ensure
26
+ Fiber.set_scheduler(original_scheduler)
18
27
  end
19
28
  end
20
-
21
- # [public] Run the reactor, yielding within the async context.
22
- #
23
- def run
24
- if (task = ::Async::Task.current?)
25
- reference = task.async { |child|
26
- @runnable = child
27
-
28
- yield self
29
- }
30
-
31
- wait_all(reference)
32
- else
33
- @runnable = ::Async::Reactor.new
34
-
35
- @runnable.run {
36
- async {
37
- yield self
38
- }.result
39
- }.wait
40
- end
41
- end
42
-
43
- # [public] Stop the reactor.
44
- #
45
- def stop
46
- @runnable&.stop
47
- end
48
29
  end
49
30
  end
50
31
  end
@@ -0,0 +1,237 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "event"
4
+ require "fiber"
5
+ require "timers/group"
6
+ require "is/inspectable"
7
+
8
+ require_relative "cancel"
9
+ require_relative "failure"
10
+ require_relative "filament"
11
+ require_relative "timeout"
12
+
13
+ module Core
14
+ module Async
15
+ # [public] The fiber scheduler.
16
+ #
17
+ class Scheduler
18
+ include Is::Inspectable
19
+ inspects without: [:@selector, :@timers, :@fibers]
20
+
21
+ extend Forwardable
22
+
23
+ # [public] Transfer from the current fiber to the selector.
24
+ #
25
+ def_delegator :@selector, :transfer
26
+
27
+ # [public] Yield the current fiber, resuming on the next tick.
28
+ #
29
+ def_delegator :@selector, :yield
30
+
31
+ # [public] Raise an error in the given fiber.
32
+ #
33
+ def_delegator :@selector, :raise
34
+
35
+ # [public] Resume the given fiber with the given value.
36
+ #
37
+ def_delegator :@selector, :resume
38
+
39
+ def initialize
40
+ @selector = Event::Selector.new(Fiber.current)
41
+ @timers = Timers::Group.new
42
+ @closed = false
43
+ @running = false
44
+ @fibers = {}
45
+ end
46
+
47
+ # [public] Run until there's no more work to do.
48
+ #
49
+ def run
50
+ @running = true
51
+
52
+ yield self if block_given?
53
+
54
+ Thread.handle_interrupt(Interrupt => :never) do
55
+ result = nil
56
+
57
+ until Thread.pending_interrupt? || result == :finished
58
+ result = tick
59
+ end
60
+ end
61
+ ensure
62
+ @running = false
63
+ end
64
+
65
+ # [public] Run until there's no more work to do, yielding before each tick.
66
+ #
67
+ def observe
68
+ @running = true
69
+
70
+ result = nil
71
+ until result == :finished
72
+ yield self
73
+ result = tick
74
+ end
75
+ ensure
76
+ @running = false
77
+ end
78
+
79
+ # [public] Returns `true` if the scheduler is running.
80
+ #
81
+ def running?
82
+ @running == true
83
+ end
84
+
85
+ # [public] Run one tick.
86
+ #
87
+ def tick
88
+ interval = @timers.wait_interval
89
+
90
+ if interval.nil?
91
+ if @fibers.empty?
92
+ return :finished
93
+ end
94
+ elsif interval < 0
95
+ interval = 0
96
+ end
97
+
98
+ @selector.select(interval)
99
+
100
+ @timers.fire
101
+ rescue Errno::EINTR
102
+ # noop
103
+ end
104
+
105
+ # [public] Stop the scheduler, canceling each scheduled fiber.
106
+ #
107
+ def stop
108
+ signal = Cancel.new
109
+
110
+ @fibers.each_key do |fiber|
111
+ @selector.raise(fiber, signal)
112
+ end
113
+ end
114
+
115
+ # [public] Cancel the given fiber.
116
+ #
117
+ def cancel(fiber = Thread.current[:__corerb_async_current_fiber__])
118
+ self.raise(fiber, Cancel) if fiber
119
+ end
120
+
121
+ # [public] Yields to the given block, timing out after the given seconds.
122
+ #
123
+ def timeout(seconds)
124
+ if seconds && seconds > 0
125
+ fiber = Fiber.current
126
+
127
+ timer = @timers.after(seconds) {
128
+ if fiber.alive?
129
+ fiber.raise(Timeout)
130
+ end
131
+ }
132
+ end
133
+
134
+ yield
135
+ ensure
136
+ timer&.cancel
137
+ end
138
+
139
+ #######################
140
+ ### Scheduler Interface
141
+ #######################
142
+
143
+ def block(blocker, timeout = nil)
144
+ if timeout
145
+ timer = create_timer(timeout)
146
+ end
147
+
148
+ @selector.transfer
149
+ ensure
150
+ timer&.cancel
151
+ end
152
+
153
+ def close
154
+ run
155
+ ensure
156
+ unless @closed
157
+ @closed = true
158
+
159
+ Fiber.set_scheduler(nil)
160
+ end
161
+ end
162
+
163
+ def fiber(&block)
164
+ filament = Filament.new
165
+
166
+ fiber = Fiber.new(blocking: false) {
167
+ begin
168
+ Thread.current[:__corerb_async_current_fiber__] = filament
169
+ filament.object = fiber
170
+ result = block.call(filament)
171
+ rescue Cancel
172
+ result = nil
173
+ rescue => error
174
+ result = Failure.new(error)
175
+ ensure
176
+ @fibers.delete(fiber)
177
+ filament.resolve(result)
178
+ end
179
+ }
180
+
181
+ @fibers[fiber] = filament
182
+
183
+ if (parent = @fibers[Fiber.current])
184
+ parent.add_child(filament)
185
+ end
186
+
187
+ @selector.resume(fiber)
188
+
189
+ fiber
190
+ end
191
+
192
+ def io_wait(io, events, timeout = nil)
193
+ fiber = Fiber.current
194
+
195
+ if timeout
196
+ timer = @timers.after(timeout) {
197
+ fiber.raise(Timeout)
198
+ }
199
+ end
200
+
201
+ @selector.io_wait(fiber, io, events)
202
+ rescue Timeout
203
+ false
204
+ ensure
205
+ timer&.cancel
206
+ end
207
+
208
+ def kernel_sleep(duration = nil)
209
+ if duration
210
+ timer = create_timer(duration)
211
+ end
212
+
213
+ @selector.transfer
214
+ ensure
215
+ timer&.cancel
216
+ end
217
+
218
+ def process_wait(...)
219
+ @selector.process_wait(Fiber.current, ...)
220
+ end
221
+
222
+ def unblock(blocker, fiber)
223
+ @selector.push(fiber)
224
+ end
225
+
226
+ private def create_timer(duration)
227
+ fiber = Fiber.current
228
+
229
+ @timers.after(duration) {
230
+ if fiber.alive?
231
+ fiber.transfer(false)
232
+ end
233
+ }
234
+ end
235
+ end
236
+ end
237
+ end
@@ -1,10 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "is/inspectable"
4
+
3
5
  module Core
4
6
  module Async
5
7
  # [public] Raised when execution times out.
6
8
  #
7
9
  class Timeout < RuntimeError
10
+ include Is::Inspectable
8
11
  end
9
12
  end
10
13
  end
@@ -2,8 +2,10 @@
2
2
 
3
3
  module Core
4
4
  module Async
5
- VERSION = "0.6.1"
5
+ VERSION = "0.10.0"
6
6
 
7
+ # [public]
8
+ #
7
9
  def self.version
8
10
  VERSION
9
11
  end
@@ -13,15 +13,13 @@ module Core
13
13
  @target = target
14
14
  end
15
15
 
16
- def method_missing(name, *args, &block)
16
+ def method_missing(...)
17
17
  @target.async do
18
- @target.public_send(name, *args, &block)
18
+ @target.public_send(...)
19
19
  end
20
20
  end
21
21
 
22
- ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords)
23
-
24
- def respond_to_missing?(name, *)
22
+ def respond_to_missing?(...)
25
23
  true
26
24
  end
27
25
  end
data/lib/is/async.rb CHANGED
@@ -6,22 +6,14 @@ require_relative "../core/async/timeout"
6
6
  require_relative "../core/async/wrapper"
7
7
 
8
8
  module Is
9
- # [public] Makes Ruby objects async-aware.
9
+ # [public] Makes objects async-aware.
10
10
  #
11
11
  module Async
12
12
  # [public] Call behavior asychronously, returning a future.
13
13
  #
14
- def async
15
- if block_given?
16
- task = ::Async::Reactor.run { |current|
17
- begin
18
- yield
19
- ensure
20
- current.yield
21
- end
22
- }
23
-
24
- Core::Async::Future.new(task)
14
+ def async(&block)
15
+ if block
16
+ Core::Async::Future.new(&block)
25
17
  else
26
18
  Core::Async::Wrapper.new(self)
27
19
  end
@@ -30,85 +22,31 @@ module Is
30
22
  # [public] Call behavior synchronously but within an async context, waiting on the result.
31
23
  #
32
24
  private def await
33
- if (task = ::Async::Task.current?)
34
- reference = task.async { |current|
35
- begin
36
- yield
37
- ensure
38
- current.yield
39
- end
40
- }
41
-
42
- wait_all(reference)
43
- else
44
- ::Async::Reactor.run { |task|
45
- async {
46
- yield
47
- }.result
48
- }.wait
49
- end
50
- rescue UncaughtThrowError => error
51
- throw error.tag, error.value
52
- end
53
-
54
- # [public] Call behavior within an async context without additional nesting.
55
- #
56
- private def aware
57
- if ::Async::Task.current?
25
+ async {
58
26
  yield
59
- else
60
- await do
61
- yield
62
- end
63
- end
64
- end
65
-
66
- # [public] Sleeps for `seconds` in a proper async context.
67
- #
68
- private def sleep(seconds)
69
- internal_await do |task|
70
- task.sleep(seconds)
71
- end
27
+ }.wait.result
72
28
  end
73
29
 
74
30
  # [public] Call asynchonous behavior in a proper async context, wrapped in a timeout.
75
31
  #
76
32
  # Raises `Core::Async::Timeout` if execution exceeds `seconds`.
77
33
  #
78
- private def timeout(seconds)
79
- internal_await do |task|
80
- timed_task = internal_async {
81
- yield
82
- }
83
-
84
- if seconds && seconds > 0
85
- task.with_timeout(seconds, Core::Async::Timeout) do
86
- timed_task.wait
87
- end
88
- else
89
- timed_task.wait
90
- end
91
- ensure
92
- timed_task&.stop
93
- end
94
- rescue UncaughtThrowError => error
95
- throw error.tag, error.value
34
+ private def timeout(seconds, &block)
35
+ async {
36
+ Fiber.scheduler.timeout(seconds, &block)
37
+ }.wait
96
38
  end
97
39
 
98
40
  # [public] Yields control to allow other fibers to execute.
99
41
  #
100
42
  private def defer
101
- if (task = ::Async::Task.current?)
102
- task.yield
103
- end
43
+ Fiber.scheduler.yield
104
44
  end
105
45
 
106
46
  # [public] Cancels the current async behavior if in progress.
107
47
  #
108
48
  private def cancel
109
- if (task = ::Async::Task.current?)
110
- task.stop
111
- end
49
+ Fiber.scheduler.cancel
112
50
  end
113
51
 
114
52
  # [public] Resolves a potential future to a final result.
@@ -116,38 +54,10 @@ module Is
116
54
  private def resolve(value)
117
55
  case value
118
56
  when Core::Async::Future
119
- value.result
57
+ resolve(value.result)
120
58
  else
121
59
  value
122
60
  end
123
61
  end
124
-
125
- private def internal_async
126
- ::Async::Reactor.run do |task|
127
- yield task
128
- end
129
- end
130
-
131
- private def internal_await
132
- if (task = ::Async::Task.current?)
133
- reference = task.async {
134
- yield task
135
- }
136
-
137
- wait_all(reference)
138
- else
139
- ::Async::Reactor.run { |task|
140
- yield task
141
- }.wait
142
- end
143
- end
144
-
145
- private def wait_all(task)
146
- task.children&.each do |child|
147
- wait_all(child)
148
- end
149
-
150
- task.wait
151
- end
152
62
  end
153
63
  end
metadata CHANGED
@@ -1,29 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: core-async
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryan Powell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-20 00:00:00.000000000 Z
11
+ date: 2021-11-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: async
14
+ name: event
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.28'
19
+ version: '1.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.28'
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: timers
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: core-inspect
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.0'
27
55
  description: Makes Ruby objects async-aware.
28
56
  email: bryan@metabahn.com
29
57
  executables: []
@@ -34,10 +62,15 @@ files:
34
62
  - LICENSE
35
63
  - lib/core/async.rb
36
64
  - lib/core/async/bootstrap.rb
65
+ - lib/core/async/cancel.rb
66
+ - lib/core/async/channel.rb
37
67
  - lib/core/async/collection.rb
38
68
  - lib/core/async/enumerator.rb
69
+ - lib/core/async/failure.rb
70
+ - lib/core/async/filament.rb
39
71
  - lib/core/async/future.rb
40
72
  - lib/core/async/reactor.rb
73
+ - lib/core/async/scheduler.rb
41
74
  - lib/core/async/timeout.rb
42
75
  - lib/core/async/version.rb
43
76
  - lib/core/async/wrapper.rb
@@ -54,14 +87,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
54
87
  requirements:
55
88
  - - ">="
56
89
  - !ruby/object:Gem::Version
57
- version: 2.6.7
90
+ version: '3.0'
58
91
  required_rubygems_version: !ruby/object:Gem::Requirement
59
92
  requirements:
60
93
  - - ">="
61
94
  - !ruby/object:Gem::Version
62
95
  version: '0'
63
96
  requirements: []
64
- rubygems_version: 3.2.15
97
+ rubygems_version: 3.2.22
65
98
  signing_key:
66
99
  specification_version: 4
67
100
  summary: Makes Ruby objects async-aware.