core-async 0.0.0 → 0.4.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: 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.