core-async 0.6.1 → 0.10.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: 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.