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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d07ba2a96ea37afd1f5abe0bb7a24182ef1b94e055b39cd2b4ad6cf08c420a5a
4
- data.tar.gz: 893d9b5b72309cea17f9ca5cba0c840f9bfbacfb8d56e1db2b8df34c9cfd58ff
3
+ metadata.gz: c39a859a0ccc9ae393153098708feda94d76e34cf9c9ce07ce60833a1e94bc10
4
+ data.tar.gz: c9739940a35af9a076fbdd89742a698b7cf15d34670d44d2dcb0462820c85485
5
5
  SHA512:
6
- metadata.gz: 04e6ebb9c4684fa2501d9b069fdcc4368d8c9cbdfa9294e5a379b423a9a1894ab8d05854a7bd8137cf2da061fdecaac859d7cc0466e631a704244c02a514763b
7
- data.tar.gz: ed03928b876cd32a5df6f37f787400c2e4ef2db581b5e5de3c8c111734e5f53518675c35303ceb2a45e26af8f7b65cba4554ebfdd5d724e90d65ea15fdb0ef1f
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,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "async"
4
+ Console.logger.off!
@@ -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
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Core
4
4
  module Async
5
- VERSION = "0.0.0"
5
+ VERSION = "0.4.0"
6
6
 
7
7
  def self.version
8
8
  VERSION
@@ -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
- require "async"
4
- Console.logger.off!
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 asynchronous behavior in a proper async context.
12
+ # [public] Call behavior asychronously, returning a future.
13
13
  #
14
14
  def async
15
- ::Async::Reactor.run do |task|
16
- yield task
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 asynchronous behavior synchronously in a proper async context.
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
- yield task
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
- yield task
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
- await do |task|
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
- await do |task|
77
+ internal_await do |task|
46
78
  if seconds && seconds > 0
47
79
  task.with_timeout(seconds, Core::Async::Timeout) do
48
- yield task
80
+ yield
49
81
  end
50
82
  else
51
- yield task
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.0.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: 2020-12-17 00:00:00.000000000 Z
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.27'
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.27'
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.1.4
64
+ rubygems_version: 3.2.4
59
65
  signing_key:
60
66
  specification_version: 4
61
67
  summary: Makes Ruby objects async-aware.