core-async 0.1.0 → 0.5.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: 2f2c40406be47b9385d6252ddfc534383b817249c44aeda7e3410ea12d15afde
4
- data.tar.gz: 48ee77b1bec473726d7deb3f35481c487e946c8fd00668c039d7556164bd70fc
3
+ metadata.gz: 5449a66653642c9fa6f96c2a968bda754e31aefdee4c207d1e3a02e18e7e7dcb
4
+ data.tar.gz: bc9e2ae806089f3a64cb3a3fa15d97505c5be54c0ca455577ddf3f6b1806f73b
5
5
  SHA512:
6
- metadata.gz: 21f8e4bf33f14497c2080450f6ba4db0abde814ffbb86a3b0ed64f187edfd4c042830fcccd4c9044b5ff67d31bdf25904b2430bf715c02ea0334c7152cbe597d
7
- data.tar.gz: b41c4e064774fc736814255aa5f92dfd160967c0370c7a5b76784418793a2550e0403beb6524e37b488ea76e9db25c7f1810b8919f699b9182a3268feaa44431
6
+ metadata.gz: 7fe6d3f541fd066a958453573f027a70321f0176e3db0b1e3afdb414841bcb8d7367414ccc1aa6ae06b97437ef2fc299fee761ab88661470d69bd7cffd2d9c88
7
+ data.tar.gz: a4786ac589bbb949c78ca6832e27b49abe79ed36b2b401529b0a56443404165e665d6f28aecdeeb9d1543e2f7e7c8ffba4ad05219fe5775e74d89fde750d2f9e
data/CHANGELOG.md CHANGED
@@ -1,3 +1,44 @@
1
+ ## [v0.5.0](https://github.com/metabahn/corerb/releases/tag/2021-03-26)
2
+
3
+ *released on 2021-03-26*
4
+
5
+ * `chg` [#25](https://github.com/metabahn/corerb/pull/25) Privatize async behavior ([bryanp](https://github.com/bryanp))
6
+ * `add` [#24](https://github.com/metabahn/corerb/pull/24) Add Is::Async#cancel ([bryanp](https://github.com/bryanp))
7
+ * `fix` [#23](https://github.com/metabahn/corerb/pull/23) Improve control flow during async enumeration / collection building ([bryanp](https://github.com/bryanp))
8
+ * `fix` [#22](https://github.com/metabahn/corerb/pull/22) Expose errors that occur when building a collection ([bryanp](https://github.com/bryanp))
9
+
10
+ ## [v0.4.0](https://github.com/metabahn/corerb/releases/tag/2021-03-17.1)
11
+
12
+ *released on 2021-03-17*
13
+
14
+ * `chg` [#21](https://github.com/metabahn/corerb/pull/21) Cancel async futures without waiting ([bryanp](https://github.com/bryanp))
15
+
16
+ ## [v0.3.0](https://github.com/metabahn/corerb/releases/tag/2021-03-17)
17
+
18
+ *released on 2021-03-17*
19
+
20
+ * `add` [#20](https://github.com/metabahn/corerb/pull/20) Allow futures to be canceled ([bryanp](https://github.com/bryanp))
21
+
22
+ ## [v0.2.1](https://github.com/metabahn/corerb/releases/tag/2021-03-05)
23
+
24
+ *released on 2021-03-05*
25
+
26
+ * `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))
27
+
28
+ ## [v0.2.0](https://github.com/metabahn/corerb/releases/tag/2021-03-05)
29
+
30
+ *released on 2021-03-05*
31
+
32
+ * `add` [#17](https://github.com/metabahn/corerb/pull/17) Add Core::Async::Collection for building async collections ([bryanp](https://github.com/bryanp))
33
+ * `add` [#16](https://github.com/metabahn/corerb/pull/16) Add Core::Async::Enumerator for async enumeration ([bryanp](https://github.com/bryanp))
34
+ * `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))
35
+ * `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))
36
+ * `fix` [#13](https://github.com/metabahn/corerb/pull/13) Correctly return the result from top-level await ([bryanp](https://github.com/bryanp))
37
+ * `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))
38
+ * `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))
39
+ * `add` [#10](https://github.com/metabahn/corerb/pull/10) Add a pattern for making a call explicitly async ([bryanp](https://github.com/bryanp))
40
+ * `add` [#9](https://github.com/metabahn/corerb/pull/9) Introduce async futures ([bryanp](https://github.com/bryanp))
41
+
1
42
  ## [v0.1.0](https://github.com/metabahn/corerb/releases/tag/2021-02-11)
2
43
 
3
44
  *released on 2021-02-11*
data/lib/core/async.rb CHANGED
@@ -1,7 +1,12 @@
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"
5
10
  require_relative "async/reactor"
6
11
  require_relative "async/version"
7
12
  end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "async"
4
+ Console.logger.off!
@@ -0,0 +1,68 @@
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
+ errored, stopped = false
16
+
17
+ values = object.map { |value|
18
+ break if errored || stopped
19
+
20
+ async do
21
+ if block_given?
22
+ yield value
23
+ else
24
+ value
25
+ end
26
+ rescue LocalJumpError
27
+ stopped = true
28
+ rescue => error
29
+ errored = true
30
+ raise error
31
+ end
32
+ }
33
+
34
+ new(values) if values
35
+ end
36
+ end
37
+ end
38
+
39
+ include Enumerable
40
+ include Is::Async
41
+
42
+ def initialize(values = [])
43
+ unless values.respond_to?(:each)
44
+ raise ArgumentError, "values are not enumerable"
45
+ end
46
+
47
+ @values = values
48
+ end
49
+
50
+ # [public] Adds a value to the collection.
51
+ #
52
+ def <<(value)
53
+ @values << value
54
+ end
55
+ alias_method :add, :<<
56
+
57
+ # [public] Yields each resolved value.
58
+ #
59
+ def each
60
+ return to_enum(:each) unless block_given?
61
+
62
+ @values.each do |value|
63
+ yield resolve(value)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,45 @@
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, stopped = false
28
+
29
+ @object.each do |value|
30
+ break if errored || stopped
31
+
32
+ async do
33
+ yield value
34
+ rescue LocalJumpError
35
+ stopped = true
36
+ rescue => error
37
+ errored = true
38
+ raise error
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ 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
@@ -33,7 +33,9 @@ module Core
33
33
  @runnable = ::Async::Reactor.new
34
34
 
35
35
  @runnable.run {
36
- yield self
36
+ async {
37
+ yield self
38
+ }.result
37
39
  }.wait
38
40
  end
39
41
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Core
4
4
  module Async
5
- VERSION = "0.1.0"
5
+ VERSION = "0.5.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,41 +1,69 @@
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
16
- yield
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
- def await
32
+ private def await
23
33
  if (task = ::Async::Task.current?)
24
- reference = task.async {
25
- yield
34
+ reference = task.async { |current|
35
+ begin
36
+ yield
37
+ ensure
38
+ current.yield
39
+ end
26
40
  }
27
41
 
28
42
  wait_all(reference)
29
43
  else
30
- ::Async::Reactor.run {
31
- yield
44
+ ::Async::Reactor.run { |task|
45
+ async {
46
+ yield
47
+ }.result
32
48
  }.wait
33
49
  end
34
50
  end
35
51
 
52
+ # [public] Call behavior within an async context without additional nesting.
53
+ #
54
+ private def aware
55
+ if ::Async::Task.current?
56
+ yield
57
+ else
58
+ await do
59
+ yield
60
+ end
61
+ end
62
+ end
63
+
36
64
  # [public] Sleeps for `seconds` in a proper async context.
37
65
  #
38
- def sleep(seconds)
66
+ private def sleep(seconds)
39
67
  internal_await do |task|
40
68
  task.sleep(seconds)
41
69
  end
@@ -45,7 +73,7 @@ module Is
45
73
  #
46
74
  # Raises `Core::Async::Timeout` if execution exceeds `seconds`.
47
75
  #
48
- def timeout(seconds, &block)
76
+ private def timeout(seconds, &block)
49
77
  internal_await do |task|
50
78
  if seconds && seconds > 0
51
79
  task.with_timeout(seconds, Core::Async::Timeout) do
@@ -59,12 +87,31 @@ module Is
59
87
 
60
88
  # [public] Yields control to allow other fibers to execute.
61
89
  #
62
- def defer
90
+ private def defer
63
91
  if (task = ::Async::Task.current?)
64
92
  task.yield
65
93
  end
66
94
  end
67
95
 
96
+ # [public] Cancels the current async behavior if in progress.
97
+ #
98
+ private def cancel
99
+ if (task = ::Async::Task.current?)
100
+ task.stop
101
+ end
102
+ end
103
+
104
+ # [public] Resolves a potential future to a final result.
105
+ #
106
+ private def resolve(value)
107
+ case value
108
+ when Core::Async::Future
109
+ value.result
110
+ else
111
+ value
112
+ end
113
+ end
114
+
68
115
  private def internal_async
69
116
  ::Async::Reactor.run do |task|
70
117
  yield task
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.1.0
4
+ version: 0.5.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-02-12 00:00:00.000000000 Z
11
+ date: 2021-03-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async
@@ -33,9 +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
36
40
  - lib/core/async/reactor.rb
37
41
  - lib/core/async/timeout.rb
38
42
  - lib/core/async/version.rb
43
+ - lib/core/async/wrapper.rb
39
44
  - lib/is/async.rb
40
45
  homepage: https://github.com/metabahn/corerb/
41
46
  licenses: