core-async 0.0.0 → 0.4.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 +40 -0
- data/lib/core/async.rb +6 -0
- data/lib/core/async/bootstrap.rb +4 -0
- data/lib/core/async/collection.rb +59 -0
- data/lib/core/async/enumerator.rb +46 -0
- data/lib/core/async/future.rb +141 -0
- data/lib/core/async/reactor.rb +50 -0
- data/lib/core/async/version.rb +1 -1
- data/lib/core/async/wrapper.rb +29 -0
- data/lib/is/async.rb +84 -13
- metadata +11 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c39a859a0ccc9ae393153098708feda94d76e34cf9c9ce07ce60833a1e94bc10
|
4
|
+
data.tar.gz: c9739940a35af9a076fbdd89742a698b7cf15d34670d44d2dcb0462820c85485
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dfc642aeff2aaa6c38c9ac96615ca1fa6e1dc064557e230802accb58c4703c3d3785e9a39718833fd50b9fca8b2025c1410252108be2a794834531bda2956031
|
7
|
+
data.tar.gz: fdf13769d60a5a60c04c854a7c571f115f3434ec48b4706a218bc0a4b1b6cbd86bd8ee2e70a2fedf9fe54d3f4a39e1c16926ec08163c4054cea28e94315f4b7e
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,43 @@
|
|
1
|
+
## [v0.4.0](https://github.com/metabahn/corerb/releases/tag/2021-03-17.1)
|
2
|
+
|
3
|
+
*released on 2021-03-17*
|
4
|
+
|
5
|
+
* `chg` [#21](https://github.com/metabahn/corerb/pull/21) Cancel async futures without waiting ([bryanp](https://github.com/bryanp))
|
6
|
+
|
7
|
+
## [v0.3.0](https://github.com/metabahn/corerb/releases/tag/2021-03-17)
|
8
|
+
|
9
|
+
*released on 2021-03-17*
|
10
|
+
|
11
|
+
* `add` [#20](https://github.com/metabahn/corerb/pull/20) Allow futures to be canceled ([bryanp](https://github.com/bryanp))
|
12
|
+
|
13
|
+
## [v0.2.1](https://github.com/metabahn/corerb/releases/tag/2021-03-05)
|
14
|
+
|
15
|
+
*released on 2021-03-05*
|
16
|
+
|
17
|
+
* `fix` [#18](https://github.com/metabahn/corerb/pull/18) Surface errors that occur in tasks running within an async reactor ([bryanp](https://github.com/bryanp))
|
18
|
+
|
19
|
+
## [v0.2.0](https://github.com/metabahn/corerb/releases/tag/2021-03-05)
|
20
|
+
|
21
|
+
*released on 2021-03-05*
|
22
|
+
|
23
|
+
* `add` [#17](https://github.com/metabahn/corerb/pull/17) Add Core::Async::Collection for building async collections ([bryanp](https://github.com/bryanp))
|
24
|
+
* `add` [#16](https://github.com/metabahn/corerb/pull/16) Add Core::Async::Enumerator for async enumeration ([bryanp](https://github.com/bryanp))
|
25
|
+
* `add` [#15](https://github.com/metabahn/corerb/pull/15) Add Is::Async#resolve for resolving potential futures to final values ([bryanp](https://github.com/bryanp))
|
26
|
+
* `add` [#14](https://github.com/metabahn/corerb/pull/14) Add Is::Async#aware for guaranteeing an async context without nesting ([bryanp](https://github.com/bryanp))
|
27
|
+
* `fix` [#13](https://github.com/metabahn/corerb/pull/13) Correctly return the result from top-level await ([bryanp](https://github.com/bryanp))
|
28
|
+
* `fix` [#12](https://github.com/metabahn/corerb/pull/12) Resolve some edge-cases and bugs around raising top-level errors ([bryanp](https://github.com/bryanp))
|
29
|
+
* `chg` [#11](https://github.com/metabahn/corerb/pull/11) Raise an argument error when attempting to wrap a non-async-aware object ([bryanp](https://github.com/bryanp))
|
30
|
+
* `add` [#10](https://github.com/metabahn/corerb/pull/10) Add a pattern for making a call explicitly async ([bryanp](https://github.com/bryanp))
|
31
|
+
* `add` [#9](https://github.com/metabahn/corerb/pull/9) Introduce async futures ([bryanp](https://github.com/bryanp))
|
32
|
+
|
33
|
+
## [v0.1.0](https://github.com/metabahn/corerb/releases/tag/2021-02-11)
|
34
|
+
|
35
|
+
*released on 2021-02-11*
|
36
|
+
|
37
|
+
* `chg` [#8](https://github.com/metabahn/corerb/pull/8) Wait for all async work to complete ([bryanp](https://github.com/bryanp))
|
38
|
+
* `add` [#7](https://github.com/metabahn/corerb/pull/7) Add Core::Async::Reactor ([bryanp](https://github.com/bryanp))
|
39
|
+
* `chg` [#6](https://github.com/metabahn/corerb/pull/6) Don't yield tasks from async and await ([bryanp](https://github.com/bryanp))
|
40
|
+
|
1
41
|
## [v0.0.0](https://github.com/metabahn/corerb/releases/tag/2020-12-17)
|
2
42
|
|
3
43
|
*released on 2020-12-17*
|
data/lib/core/async.rb
CHANGED
@@ -1,7 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "async/bootstrap"
|
4
|
+
|
3
5
|
module Core
|
4
6
|
module Async
|
7
|
+
require_relative "async/collection"
|
8
|
+
require_relative "async/enumerator"
|
9
|
+
require_relative "async/future"
|
10
|
+
require_relative "async/reactor"
|
5
11
|
require_relative "async/version"
|
6
12
|
end
|
7
13
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../is/async"
|
4
|
+
|
5
|
+
module Core
|
6
|
+
module Async
|
7
|
+
class Collection
|
8
|
+
class << self
|
9
|
+
include Is::Async
|
10
|
+
|
11
|
+
# [public] Builds a collection from an enumerable object.
|
12
|
+
#
|
13
|
+
def build(object)
|
14
|
+
aware do
|
15
|
+
values = object.map { |value|
|
16
|
+
async do
|
17
|
+
if block_given?
|
18
|
+
yield value
|
19
|
+
else
|
20
|
+
value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
}
|
24
|
+
|
25
|
+
new(values)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
include Enumerable
|
31
|
+
include Is::Async
|
32
|
+
|
33
|
+
def initialize(values = [])
|
34
|
+
unless values.respond_to?(:each)
|
35
|
+
raise ArgumentError, "values are not enumerable"
|
36
|
+
end
|
37
|
+
|
38
|
+
@values = values
|
39
|
+
end
|
40
|
+
|
41
|
+
# [public] Adds a value to the collection.
|
42
|
+
#
|
43
|
+
def <<(value)
|
44
|
+
@values << value
|
45
|
+
end
|
46
|
+
alias_method :add, :<<
|
47
|
+
|
48
|
+
# [public] Yields each resolved value.
|
49
|
+
#
|
50
|
+
def each
|
51
|
+
return to_enum(:each) unless block_given?
|
52
|
+
|
53
|
+
@values.each do |value|
|
54
|
+
yield resolve(value)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../is/async"
|
4
|
+
|
5
|
+
module Core
|
6
|
+
module Async
|
7
|
+
class Enumerator
|
8
|
+
include Enumerable
|
9
|
+
include Is::Async
|
10
|
+
|
11
|
+
def initialize(object)
|
12
|
+
unless object.respond_to?(:each)
|
13
|
+
raise ArgumentError, "object is not enumerable"
|
14
|
+
end
|
15
|
+
|
16
|
+
@object = object
|
17
|
+
end
|
18
|
+
|
19
|
+
# [public] Yields each value within its own async context, waiting on the enumeration to complete.
|
20
|
+
#
|
21
|
+
def each
|
22
|
+
unless block_given?
|
23
|
+
return to_enum(:each)
|
24
|
+
end
|
25
|
+
|
26
|
+
await do
|
27
|
+
errored = false
|
28
|
+
@object.each do |value|
|
29
|
+
break if errored
|
30
|
+
|
31
|
+
async {
|
32
|
+
begin
|
33
|
+
yield value
|
34
|
+
rescue => error
|
35
|
+
errored = true
|
36
|
+
raise error
|
37
|
+
ensure
|
38
|
+
defer
|
39
|
+
end
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Core
|
4
|
+
module Async
|
5
|
+
# [public] Represents a future result.
|
6
|
+
#
|
7
|
+
class Future
|
8
|
+
def initialize(task)
|
9
|
+
@task = task
|
10
|
+
@error = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
# [public] Wait on the future to resolve, returning self.
|
14
|
+
#
|
15
|
+
def wait
|
16
|
+
unless @error
|
17
|
+
resolve
|
18
|
+
end
|
19
|
+
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
# [public] Attempt to cancel the future, returns true if successful.
|
24
|
+
#
|
25
|
+
def cancel
|
26
|
+
if pending?
|
27
|
+
@task.stop
|
28
|
+
end
|
29
|
+
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
# [public] Return the result, blocking until available.
|
34
|
+
#
|
35
|
+
def result
|
36
|
+
@error || @result ||= resolve
|
37
|
+
end
|
38
|
+
|
39
|
+
private def resolve
|
40
|
+
wait_all(@task)
|
41
|
+
|
42
|
+
resolve_value(@task.result)
|
43
|
+
rescue => error
|
44
|
+
@error = error
|
45
|
+
|
46
|
+
raise error
|
47
|
+
end
|
48
|
+
|
49
|
+
# [public] Return any error that occurred without re-raising, blocking until available.
|
50
|
+
#
|
51
|
+
def error
|
52
|
+
@error ||= get_error
|
53
|
+
end
|
54
|
+
|
55
|
+
private def get_error
|
56
|
+
result
|
57
|
+
nil
|
58
|
+
rescue => error
|
59
|
+
error
|
60
|
+
end
|
61
|
+
|
62
|
+
# [public] Return the status; one of :pending, :failed, or :complete.
|
63
|
+
#
|
64
|
+
def status
|
65
|
+
if defined?(@status)
|
66
|
+
@status
|
67
|
+
else
|
68
|
+
status = find_status
|
69
|
+
if terminal_status?(status)
|
70
|
+
@status = status
|
71
|
+
end
|
72
|
+
|
73
|
+
status
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
private def find_status
|
78
|
+
case @task.status
|
79
|
+
when :running
|
80
|
+
:pending
|
81
|
+
when :stopped
|
82
|
+
:canceled
|
83
|
+
when :failed
|
84
|
+
:failed
|
85
|
+
when :complete
|
86
|
+
:complete
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
private def terminal_status?(status)
|
91
|
+
status == :failed || status == :complete
|
92
|
+
end
|
93
|
+
|
94
|
+
# [public] Return `true` if pending.
|
95
|
+
#
|
96
|
+
def pending?
|
97
|
+
status == :pending
|
98
|
+
end
|
99
|
+
|
100
|
+
# [public] Return `true` if failed.
|
101
|
+
#
|
102
|
+
def failed?
|
103
|
+
status == :failed
|
104
|
+
end
|
105
|
+
|
106
|
+
# [public] Return `true` if canceled.
|
107
|
+
#
|
108
|
+
def canceled?
|
109
|
+
status == :canceled
|
110
|
+
end
|
111
|
+
|
112
|
+
# [public] Return `true` if complete.
|
113
|
+
#
|
114
|
+
def complete?
|
115
|
+
status == :complete
|
116
|
+
end
|
117
|
+
|
118
|
+
# [public] Return `true` if failed or complete.
|
119
|
+
#
|
120
|
+
def resolved?
|
121
|
+
failed? || complete?
|
122
|
+
end
|
123
|
+
|
124
|
+
private def resolve_value(value)
|
125
|
+
if value.is_a?(self.class)
|
126
|
+
resolve_value(value.result)
|
127
|
+
else
|
128
|
+
value
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
private def wait_all(task)
|
133
|
+
task.children&.each do |child|
|
134
|
+
wait_all(child)
|
135
|
+
end
|
136
|
+
|
137
|
+
task.wait
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../is/async"
|
4
|
+
|
5
|
+
module Core
|
6
|
+
module Async
|
7
|
+
# [public] The top-level async context. Runs until all scheduled work is complete.
|
8
|
+
#
|
9
|
+
class Reactor
|
10
|
+
include Is::Async
|
11
|
+
|
12
|
+
class << self
|
13
|
+
# [public] Create a new reactor and immediately run it.
|
14
|
+
#
|
15
|
+
def run(&block)
|
16
|
+
instance = new
|
17
|
+
instance.run(&block)
|
18
|
+
end
|
19
|
+
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
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/core/async/version.rb
CHANGED
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Core
|
4
|
+
module Async
|
5
|
+
# [public] Wraps an object, calling all methods in an async context.
|
6
|
+
#
|
7
|
+
class Wrapper < BasicObject
|
8
|
+
def initialize(target)
|
9
|
+
unless target.respond_to?(:async)
|
10
|
+
::Kernel.raise ::ArgumentError, "object is not async-aware"
|
11
|
+
end
|
12
|
+
|
13
|
+
@target = target
|
14
|
+
end
|
15
|
+
|
16
|
+
def method_missing(name, *args, &block)
|
17
|
+
@target.async do
|
18
|
+
@target.public_send(name, *args, &block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords)
|
23
|
+
|
24
|
+
def respond_to_missing?(name, *)
|
25
|
+
true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/is/async.rb
CHANGED
@@ -1,38 +1,70 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
require_relative "../core/async/bootstrap"
|
4
|
+
require_relative "../core/async/future"
|
6
5
|
require_relative "../core/async/timeout"
|
6
|
+
require_relative "../core/async/wrapper"
|
7
7
|
|
8
8
|
module Is
|
9
9
|
# [public] Makes Ruby objects async-aware.
|
10
10
|
#
|
11
11
|
module Async
|
12
|
-
# [public] Call
|
12
|
+
# [public] Call behavior asychronously, returning a future.
|
13
13
|
#
|
14
14
|
def async
|
15
|
-
|
16
|
-
|
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)
|
25
|
+
else
|
26
|
+
Core::Async::Wrapper.new(self)
|
17
27
|
end
|
18
28
|
end
|
19
29
|
|
20
|
-
# [public] Call
|
30
|
+
# [public] Call behavior synchronously but within an async context, waiting on the result.
|
21
31
|
#
|
22
32
|
def await
|
23
33
|
if (task = ::Async::Task.current?)
|
24
|
-
|
34
|
+
reference = task.async { |current|
|
35
|
+
begin
|
36
|
+
yield
|
37
|
+
ensure
|
38
|
+
current.yield
|
39
|
+
end
|
40
|
+
}
|
41
|
+
|
42
|
+
wait_all(reference)
|
25
43
|
else
|
26
44
|
::Async::Reactor.run { |task|
|
27
|
-
|
45
|
+
async {
|
46
|
+
yield
|
47
|
+
}.result
|
28
48
|
}.wait
|
29
49
|
end
|
30
50
|
end
|
31
51
|
|
52
|
+
# [public] Call behavior within an async context without additional nesting.
|
53
|
+
#
|
54
|
+
def aware
|
55
|
+
if ::Async::Task.current?
|
56
|
+
yield
|
57
|
+
else
|
58
|
+
await do
|
59
|
+
yield
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
32
64
|
# [public] Sleeps for `seconds` in a proper async context.
|
33
65
|
#
|
34
66
|
def sleep(seconds)
|
35
|
-
|
67
|
+
internal_await do |task|
|
36
68
|
task.sleep(seconds)
|
37
69
|
end
|
38
70
|
end
|
@@ -42,13 +74,13 @@ module Is
|
|
42
74
|
# Raises `Core::Async::Timeout` if execution exceeds `seconds`.
|
43
75
|
#
|
44
76
|
def timeout(seconds, &block)
|
45
|
-
|
77
|
+
internal_await do |task|
|
46
78
|
if seconds && seconds > 0
|
47
79
|
task.with_timeout(seconds, Core::Async::Timeout) do
|
48
|
-
yield
|
80
|
+
yield
|
49
81
|
end
|
50
82
|
else
|
51
|
-
yield
|
83
|
+
yield
|
52
84
|
end
|
53
85
|
end
|
54
86
|
end
|
@@ -60,5 +92,44 @@ module Is
|
|
60
92
|
task.yield
|
61
93
|
end
|
62
94
|
end
|
95
|
+
|
96
|
+
# [public] Resolves a potential future to a final result.
|
97
|
+
#
|
98
|
+
def resolve(value)
|
99
|
+
case value
|
100
|
+
when Core::Async::Future
|
101
|
+
value.result
|
102
|
+
else
|
103
|
+
value
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
private def internal_async
|
108
|
+
::Async::Reactor.run do |task|
|
109
|
+
yield task
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
private def internal_await
|
114
|
+
if (task = ::Async::Task.current?)
|
115
|
+
reference = task.async {
|
116
|
+
yield task
|
117
|
+
}
|
118
|
+
|
119
|
+
wait_all(reference)
|
120
|
+
else
|
121
|
+
::Async::Reactor.run { |task|
|
122
|
+
yield task
|
123
|
+
}.wait
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
private def wait_all(task)
|
128
|
+
task.children&.each do |child|
|
129
|
+
wait_all(child)
|
130
|
+
end
|
131
|
+
|
132
|
+
task.wait
|
133
|
+
end
|
63
134
|
end
|
64
135
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: core-async
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.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:
|
11
|
+
date: 2021-03-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: async
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1.
|
19
|
+
version: '1.28'
|
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.28'
|
27
27
|
description: Makes Ruby objects async-aware.
|
28
28
|
email: bryan@metabahn.com
|
29
29
|
executables: []
|
@@ -33,8 +33,14 @@ files:
|
|
33
33
|
- CHANGELOG.md
|
34
34
|
- LICENSE
|
35
35
|
- lib/core/async.rb
|
36
|
+
- lib/core/async/bootstrap.rb
|
37
|
+
- lib/core/async/collection.rb
|
38
|
+
- lib/core/async/enumerator.rb
|
39
|
+
- lib/core/async/future.rb
|
40
|
+
- lib/core/async/reactor.rb
|
36
41
|
- lib/core/async/timeout.rb
|
37
42
|
- lib/core/async/version.rb
|
43
|
+
- lib/core/async/wrapper.rb
|
38
44
|
- lib/is/async.rb
|
39
45
|
homepage: https://github.com/metabahn/corerb/
|
40
46
|
licenses:
|
@@ -55,7 +61,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
55
61
|
- !ruby/object:Gem::Version
|
56
62
|
version: '0'
|
57
63
|
requirements: []
|
58
|
-
rubygems_version: 3.
|
64
|
+
rubygems_version: 3.2.4
|
59
65
|
signing_key:
|
60
66
|
specification_version: 4
|
61
67
|
summary: Makes Ruby objects async-aware.
|