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 +4 -4
- data/CHANGELOG.md +25 -0
- data/lib/core/async/bootstrap.rb +8 -3
- data/lib/core/async/cancel.rb +13 -0
- data/lib/core/async/channel.rb +38 -0
- data/lib/core/async/collection.rb +18 -7
- data/lib/core/async/enumerator.rb +17 -12
- data/lib/core/async/failure.rb +21 -0
- data/lib/core/async/filament.rb +47 -0
- data/lib/core/async/future.rb +108 -63
- data/lib/core/async/reactor.rb +11 -30
- data/lib/core/async/scheduler.rb +237 -0
- data/lib/core/async/timeout.rb +3 -0
- data/lib/core/async/version.rb +3 -1
- data/lib/core/async/wrapper.rb +3 -5
- data/lib/is/async.rb +13 -103
- metadata +40 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 84f855320eba3545932e5019d840f3e7fbcc6c45bba6de79a60dd3101f9ef5e7
|
4
|
+
data.tar.gz: 27a66e580273043a1a3377be330f0365cf27f749320dd1d9108ec72ff01f40e3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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*
|
data/lib/core/async/bootstrap.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require_relative "scheduler"
|
4
4
|
|
5
|
-
#
|
5
|
+
# Define a top-level scheduler.
|
6
6
|
#
|
7
|
-
|
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,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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
values = object.map { |value|
|
18
|
-
break if errored || stopped
|
16
|
+
errored = false
|
17
|
+
stopped = false
|
18
|
+
values = []
|
19
19
|
|
20
|
-
|
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
|
-
|
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
|
-
|
27
|
-
|
29
|
+
errored = false
|
30
|
+
stopped = false
|
31
|
+
futures = []
|
28
32
|
|
29
|
-
|
30
|
-
|
33
|
+
@object.each do |value|
|
34
|
+
break if errored || stopped
|
31
35
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
data/lib/core/async/future.rb
CHANGED
@@ -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
|
-
|
9
|
-
|
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]
|
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
|
-
|
17
|
-
resolve
|
18
|
-
end
|
47
|
+
resolve
|
19
48
|
|
20
49
|
self
|
21
50
|
end
|
22
51
|
|
23
|
-
# [public] Attempt to cancel the future,
|
52
|
+
# [public] Attempt to cancel the future, returning true if successful.
|
24
53
|
#
|
25
54
|
def cancel
|
26
55
|
if pending?
|
27
|
-
@
|
56
|
+
Fiber.scheduler.cancel(@fiber)
|
28
57
|
end
|
29
58
|
|
30
59
|
self
|
31
60
|
end
|
32
61
|
|
33
|
-
# [public]
|
62
|
+
# [public] Wait until the result is available, returning the result. Only awaits explicitly awaited children.
|
34
63
|
#
|
35
64
|
def result
|
36
|
-
|
37
|
-
end
|
38
|
-
|
39
|
-
private def resolve
|
40
|
-
|
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
|
-
|
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
|
-
|
46
|
-
|
123
|
+
end
|
124
|
+
|
125
|
+
private def resolve_all(filament)
|
126
|
+
filament.children&.each do |child|
|
127
|
+
resolve_all(child)
|
128
|
+
end
|
47
129
|
|
48
|
-
|
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
|
-
|
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
|
data/lib/core/async/reactor.rb
CHANGED
@@ -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]
|
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
|
data/lib/core/async/timeout.rb
CHANGED
data/lib/core/async/version.rb
CHANGED
data/lib/core/async/wrapper.rb
CHANGED
@@ -13,15 +13,13 @@ module Core
|
|
13
13
|
@target = target
|
14
14
|
end
|
15
15
|
|
16
|
-
def method_missing(
|
16
|
+
def method_missing(...)
|
17
17
|
@target.async do
|
18
|
-
@target.public_send(
|
18
|
+
@target.public_send(...)
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
|
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
|
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
|
16
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
80
|
-
|
81
|
-
|
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
|
-
|
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
|
-
|
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.
|
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-
|
11
|
+
date: 2021-11-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: event
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1.
|
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.
|
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:
|
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.
|
97
|
+
rubygems_version: 3.2.22
|
65
98
|
signing_key:
|
66
99
|
specification_version: 4
|
67
100
|
summary: Makes Ruby objects async-aware.
|