standard-procedure-async 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.nova/Configuration.json +3 -0
- data/CHANGELOG.md +7 -1
- data/Gemfile.lock +2 -1
- data/LICENSE.txt +165 -21
- data/README.md +109 -23
- data/checksums/standard-procedure-async-0.2.0.gem.sha512 +1 -0
- data/lib/{standard/procedure → standard_procedure}/async/actor.rb +11 -4
- data/lib/{standard/procedure → standard_procedure}/async/error.rb +1 -1
- data/lib/{standard/procedure → standard_procedure}/async/promises.rb +1 -1
- data/lib/{standard/procedure → standard_procedure}/async/rails_not_loaded_error.rb +1 -1
- data/lib/standard_procedure/async/version.rb +7 -0
- data/lib/{standard/procedure → standard_procedure}/async.rb +4 -6
- data/standard-procedure-async.gemspec +2 -2
- metadata +13 -11
- data/lib/standard/procedure/async/version.rb +0 -9
- /data/lib/{standard/procedure → standard_procedure}/async/await.rb +0 -0
- /data/sig/{standard/procedure → standard_procedure}/async.rbs +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4936777d02c93a9195506f2807a9ecfbc80ccbfd4e50e9af6477456742efaf9a
|
4
|
+
data.tar.gz: 1a094f5fbe3e205204a1425eeb798cdfd075b497d2e9afb4e64b132a040964f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9bacc126f5ecde4c54d9caf9d25cc73ccce1aa683d39ed4936ed4504791ab526af630511b4522c9fad7d699e55f5b8c9139a6d5e5b763fd0d550f386a4fafdf9
|
7
|
+
data.tar.gz: 45aceadf0e436aee7da11a21f2958953caa5e6de88938faac63340384554da0afae44230629a184560ecf4c700d93dab6d7c9d88c4b63d71d97726be43398423
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,10 @@
|
|
1
|
-
## [
|
1
|
+
## [0.1.2] - 2023-05-31
|
2
|
+
|
3
|
+
- Added a timeout to `StandardProcedure::Async::Actor::Message` to prevent indefinite deadlocks.
|
4
|
+
|
5
|
+
## [0.1.1] - 2023-05-31
|
6
|
+
|
7
|
+
- Changed the namespace from Standard::Procedure to StandardProcedure
|
2
8
|
|
3
9
|
## [0.1.0] - 2023-05-30
|
4
10
|
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
standard-procedure-async (0.
|
4
|
+
standard-procedure-async (0.2.0)
|
5
5
|
concurrent-ruby (>= 1.0)
|
6
6
|
|
7
7
|
GEM
|
@@ -119,6 +119,7 @@ GEM
|
|
119
119
|
|
120
120
|
PLATFORMS
|
121
121
|
arm64-darwin-22
|
122
|
+
arm64-darwin-23
|
122
123
|
x86_64-linux
|
123
124
|
|
124
125
|
DEPENDENCIES
|
data/LICENSE.txt
CHANGED
@@ -1,21 +1,165 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
of this
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
1
|
+
GNU LESSER GENERAL PUBLIC LICENSE
|
2
|
+
Version 3, 29 June 2007
|
3
|
+
|
4
|
+
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
5
|
+
Everyone is permitted to copy and distribute verbatim copies
|
6
|
+
of this license document, but changing it is not allowed.
|
7
|
+
|
8
|
+
|
9
|
+
This version of the GNU Lesser General Public License incorporates
|
10
|
+
the terms and conditions of version 3 of the GNU General Public
|
11
|
+
License, supplemented by the additional permissions listed below.
|
12
|
+
|
13
|
+
0. Additional Definitions.
|
14
|
+
|
15
|
+
As used herein, "this License" refers to version 3 of the GNU Lesser
|
16
|
+
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
17
|
+
General Public License.
|
18
|
+
|
19
|
+
"The Library" refers to a covered work governed by this License,
|
20
|
+
other than an Application or a Combined Work as defined below.
|
21
|
+
|
22
|
+
An "Application" is any work that makes use of an interface provided
|
23
|
+
by the Library, but which is not otherwise based on the Library.
|
24
|
+
Defining a subclass of a class defined by the Library is deemed a mode
|
25
|
+
of using an interface provided by the Library.
|
26
|
+
|
27
|
+
A "Combined Work" is a work produced by combining or linking an
|
28
|
+
Application with the Library. The particular version of the Library
|
29
|
+
with which the Combined Work was made is also called the "Linked
|
30
|
+
Version".
|
31
|
+
|
32
|
+
The "Minimal Corresponding Source" for a Combined Work means the
|
33
|
+
Corresponding Source for the Combined Work, excluding any source code
|
34
|
+
for portions of the Combined Work that, considered in isolation, are
|
35
|
+
based on the Application, and not on the Linked Version.
|
36
|
+
|
37
|
+
The "Corresponding Application Code" for a Combined Work means the
|
38
|
+
object code and/or source code for the Application, including any data
|
39
|
+
and utility programs needed for reproducing the Combined Work from the
|
40
|
+
Application, but excluding the System Libraries of the Combined Work.
|
41
|
+
|
42
|
+
1. Exception to Section 3 of the GNU GPL.
|
43
|
+
|
44
|
+
You may convey a covered work under sections 3 and 4 of this License
|
45
|
+
without being bound by section 3 of the GNU GPL.
|
46
|
+
|
47
|
+
2. Conveying Modified Versions.
|
48
|
+
|
49
|
+
If you modify a copy of the Library, and, in your modifications, a
|
50
|
+
facility refers to a function or data to be supplied by an Application
|
51
|
+
that uses the facility (other than as an argument passed when the
|
52
|
+
facility is invoked), then you may convey a copy of the modified
|
53
|
+
version:
|
54
|
+
|
55
|
+
a) under this License, provided that you make a good faith effort to
|
56
|
+
ensure that, in the event an Application does not supply the
|
57
|
+
function or data, the facility still operates, and performs
|
58
|
+
whatever part of its purpose remains meaningful, or
|
59
|
+
|
60
|
+
b) under the GNU GPL, with none of the additional permissions of
|
61
|
+
this License applicable to that copy.
|
62
|
+
|
63
|
+
3. Object Code Incorporating Material from Library Header Files.
|
64
|
+
|
65
|
+
The object code form of an Application may incorporate material from
|
66
|
+
a header file that is part of the Library. You may convey such object
|
67
|
+
code under terms of your choice, provided that, if the incorporated
|
68
|
+
material is not limited to numerical parameters, data structure
|
69
|
+
layouts and accessors, or small macros, inline functions and templates
|
70
|
+
(ten or fewer lines in length), you do both of the following:
|
71
|
+
|
72
|
+
a) Give prominent notice with each copy of the object code that the
|
73
|
+
Library is used in it and that the Library and its use are
|
74
|
+
covered by this License.
|
75
|
+
|
76
|
+
b) Accompany the object code with a copy of the GNU GPL and this license
|
77
|
+
document.
|
78
|
+
|
79
|
+
4. Combined Works.
|
80
|
+
|
81
|
+
You may convey a Combined Work under terms of your choice that,
|
82
|
+
taken together, effectively do not restrict modification of the
|
83
|
+
portions of the Library contained in the Combined Work and reverse
|
84
|
+
engineering for debugging such modifications, if you also do each of
|
85
|
+
the following:
|
86
|
+
|
87
|
+
a) Give prominent notice with each copy of the Combined Work that
|
88
|
+
the Library is used in it and that the Library and its use are
|
89
|
+
covered by this License.
|
90
|
+
|
91
|
+
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
92
|
+
document.
|
93
|
+
|
94
|
+
c) For a Combined Work that displays copyright notices during
|
95
|
+
execution, include the copyright notice for the Library among
|
96
|
+
these notices, as well as a reference directing the user to the
|
97
|
+
copies of the GNU GPL and this license document.
|
98
|
+
|
99
|
+
d) Do one of the following:
|
100
|
+
|
101
|
+
0) Convey the Minimal Corresponding Source under the terms of this
|
102
|
+
License, and the Corresponding Application Code in a form
|
103
|
+
suitable for, and under terms that permit, the user to
|
104
|
+
recombine or relink the Application with a modified version of
|
105
|
+
the Linked Version to produce a modified Combined Work, in the
|
106
|
+
manner specified by section 6 of the GNU GPL for conveying
|
107
|
+
Corresponding Source.
|
108
|
+
|
109
|
+
1) Use a suitable shared library mechanism for linking with the
|
110
|
+
Library. A suitable mechanism is one that (a) uses at run time
|
111
|
+
a copy of the Library already present on the user's computer
|
112
|
+
system, and (b) will operate properly with a modified version
|
113
|
+
of the Library that is interface-compatible with the Linked
|
114
|
+
Version.
|
115
|
+
|
116
|
+
e) Provide Installation Information, but only if you would otherwise
|
117
|
+
be required to provide such information under section 6 of the
|
118
|
+
GNU GPL, and only to the extent that such information is
|
119
|
+
necessary to install and execute a modified version of the
|
120
|
+
Combined Work produced by recombining or relinking the
|
121
|
+
Application with a modified version of the Linked Version. (If
|
122
|
+
you use option 4d0, the Installation Information must accompany
|
123
|
+
the Minimal Corresponding Source and Corresponding Application
|
124
|
+
Code. If you use option 4d1, you must provide the Installation
|
125
|
+
Information in the manner specified by section 6 of the GNU GPL
|
126
|
+
for conveying Corresponding Source.)
|
127
|
+
|
128
|
+
5. Combined Libraries.
|
129
|
+
|
130
|
+
You may place library facilities that are a work based on the
|
131
|
+
Library side by side in a single library together with other library
|
132
|
+
facilities that are not Applications and are not covered by this
|
133
|
+
License, and convey such a combined library under terms of your
|
134
|
+
choice, if you do both of the following:
|
135
|
+
|
136
|
+
a) Accompany the combined library with a copy of the same work based
|
137
|
+
on the Library, uncombined with any other library facilities,
|
138
|
+
conveyed under the terms of this License.
|
139
|
+
|
140
|
+
b) Give prominent notice with the combined library that part of it
|
141
|
+
is a work based on the Library, and explaining where to find the
|
142
|
+
accompanying uncombined form of the same work.
|
143
|
+
|
144
|
+
6. Revised Versions of the GNU Lesser General Public License.
|
145
|
+
|
146
|
+
The Free Software Foundation may publish revised and/or new versions
|
147
|
+
of the GNU Lesser General Public License from time to time. Such new
|
148
|
+
versions will be similar in spirit to the present version, but may
|
149
|
+
differ in detail to address new problems or concerns.
|
150
|
+
|
151
|
+
Each version is given a distinguishing version number. If the
|
152
|
+
Library as you received it specifies that a certain numbered version
|
153
|
+
of the GNU Lesser General Public License "or any later version"
|
154
|
+
applies to it, you have the option of following the terms and
|
155
|
+
conditions either of that published version or of any later version
|
156
|
+
published by the Free Software Foundation. If the Library as you
|
157
|
+
received it does not specify a version number of the GNU Lesser
|
158
|
+
General Public License, you may choose any version of the GNU Lesser
|
159
|
+
General Public License ever published by the Free Software Foundation.
|
160
|
+
|
161
|
+
If the Library as you received it specifies that a proxy can decide
|
162
|
+
whether future versions of the GNU Lesser General Public License shall
|
163
|
+
apply, that proxy's public statement of acceptance of any version is
|
164
|
+
permanent authorization for you to choose that version for the
|
165
|
+
Library.
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# StandardProcedure::Async
|
2
2
|
|
3
3
|
## The Actor model
|
4
4
|
|
@@ -8,7 +8,11 @@ The actual implementation does not use a thread-per-object as that could get ver
|
|
8
8
|
|
9
9
|
## Why does this gem exist?
|
10
10
|
|
11
|
-
|
11
|
+
The short answer:
|
12
|
+
|
13
|
+
Because you can't use concurrent-ruby's Async mixin inside a Rails app. On top of that, I wanted to change its syntax a little bit to match how it's done in Javascript.
|
14
|
+
|
15
|
+
The long answer:
|
12
16
|
|
13
17
|
Concurrent-ruby has a simple implementation of this model, using the [Async](https://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Async.html) mixin. However, Concurrent::Async uses [IVar](https://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/IVar.html)s which are now deprecated.
|
14
18
|
|
@@ -16,24 +20,24 @@ In addition, while concurrent-ruby is an excellent library, it does not work wel
|
|
16
20
|
|
17
21
|
Finally, using the actor model is infectious. If you mark a public method as asynchronous, in order to be safe, you have to mark them all as asynchronous. Concurrent::Async's syntax relies on the caller using `@my_object.async.my_method` which means that it is easy to forget and miss an asynchronous call. Which in turn will result in inconsistent behaviour and hard to trace bugs.
|
18
22
|
|
19
|
-
So [
|
23
|
+
So [StandardProcedure::Async::Actor](https://github.com/standard-procedure/async/blob/main/lib/standard_procedure/async/actor.rb) replaces the IVars with a mix of Futures and [MVar](https://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/MVar.html)s. The MVar is used to transfer the return values of any methods back to the calling thread and the Future is used to do the work in a separate thread.
|
20
24
|
|
21
|
-
In addition, if you are in a Ruby on Rails project,
|
25
|
+
In addition, if you are in a Ruby on Rails project, StandardProcedure::Async uses [Luiz Kowalski](https://github.com/luizkowalski)'s [concurrent_rails](https://github.com/luizkowalski/concurrent_rails) gem. This is a layer above concurrent-ruby's Futures that ensure any future code is run within the Rails Executor.
|
22
26
|
|
23
27
|
Finally, instead of relying on the caller to call `async` on asynchronous methods, we define the asynchronous methods on the class itself with the `async` definition. The caller simply uses `@my_object.my_method` and it will always be run safely in an alternate thread. There is also an implementation of `await`, making it easy to resolve the results of your method calls.
|
24
28
|
|
25
29
|
## Adding `async` and `await` capabilities to ruby objects
|
26
30
|
|
27
|
-
Instead of defining methods on your class with ruby's `def` keyword, include the [
|
31
|
+
Instead of defining methods on your class with ruby's `def` keyword, include the [StandardProcedure::Async::Actor](https://github.com/standard-procedure/async/blob/main/lib/standard_procedure/async/actor.rb) module and use the `async` class method.
|
28
32
|
|
29
|
-
When you call an asynchronous method, it immediately returns an internal message object. If you don't care about the return value from the method, you can discard this object immediately. But if you do need the return value, you can call `value` on this message object - and your thread will then
|
33
|
+
When you call an asynchronous method, it immediately returns an internal message object. If you don't care about the return value from the method, you can discard this object immediately. But if you do need the return value, you can call `value` on this message object - and your thread will then block until the return value is ready. Alternatively, you can use `await`, effectively turning your asynchronous method into a synchronous one. See the note below about using `await` or `value` if you are calling a method from inside an actor - in short, be careful.
|
30
34
|
|
31
|
-
An added bonus (or negative, depending upon your point of view) is that this syntax is very similar to Javascript's async/await pairing, which
|
35
|
+
An added bonus (or negative, depending upon your point of view) is that this syntax is very similar to Javascript's async/await pairing, which marks out asychronous function calls and waits until any [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) are ready to return their values.
|
32
36
|
|
33
37
|
Example usage:
|
34
38
|
```ruby
|
35
39
|
class MyObject
|
36
|
-
include
|
40
|
+
include StandardProcedure::Async::Actor
|
37
41
|
|
38
42
|
def initialize name
|
39
43
|
@name = name
|
@@ -69,15 +73,24 @@ end
|
|
69
73
|
|
70
74
|
@my_object = MyObject.new "George"
|
71
75
|
|
76
|
+
# Getting started
|
77
|
+
puts @my_object.report_status # => internal message object
|
78
|
+
@my_object.report_status.then do |value|
|
79
|
+
puts value # => :idle
|
80
|
+
end
|
81
|
+
puts await { @my_object.report_status } # => :idle
|
72
82
|
puts await { @my_object.greet } # => "Hello George"
|
83
|
+
|
84
|
+
# Do some actual work
|
73
85
|
await { @my_object.rename "Ringo" }
|
74
86
|
puts await { @my_object.greet } # => "Hello Ringo"
|
75
87
|
@my_object.rename "Paul" # Note: we didn't use await here - we'll talk about that later
|
76
88
|
puts await { @my_object.greet } # => "Hello Paul"
|
77
89
|
|
90
|
+
# Do something a bit more complex
|
78
91
|
@initial_status = @my_object.do_some_long_running_task
|
79
|
-
puts @initial_status # =>
|
80
|
-
|
92
|
+
puts await { @initial_status } # => :in_progress
|
93
|
+
# *drums fingers* *sips from cup of tea*
|
81
94
|
sleep 11
|
82
95
|
@final_status = await { @my_object.report_status }
|
83
96
|
puts @final_status # => :done
|
@@ -90,13 +103,34 @@ When defining `MyObject`, we use `async` instead of `def` for each method. For
|
|
90
103
|
|
91
104
|
### Awaiting the results from those methods
|
92
105
|
|
93
|
-
The asynchronous wrapper always returns
|
106
|
+
The asynchronous wrapper always returns a internal message object that contains a [Concurrent::MVar](https://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/MVar.html). The MVar is empty until the actor has completed its work but if you need the return value from the method, there are a few ways to access it.
|
107
|
+
|
108
|
+
You can call `value` on the returned message. For convenience, `value` is also aliased as `get` and `await`.
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
# The following calls are all equivalent
|
112
|
+
@my_object.report_status.value
|
113
|
+
@my_object.report_status.get
|
114
|
+
@my_object.report_status.await
|
115
|
+
```
|
116
|
+
|
117
|
+
You can use the global `await` method.
|
94
118
|
|
95
|
-
|
119
|
+
```ruby
|
120
|
+
puts await { @my_object.report_status }
|
121
|
+
```
|
122
|
+
|
123
|
+
Or you can use the [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)-style resolution.
|
96
124
|
|
97
|
-
|
125
|
+
```ruby
|
126
|
+
@my_object.report_status.then do |value|
|
127
|
+
puts value
|
128
|
+
end
|
129
|
+
```
|
98
130
|
|
99
|
-
|
131
|
+
In all cases the calling thread will block until the value is returned (or the `then` clause is triggered).
|
132
|
+
|
133
|
+
### The strict sequencing of asynchronous calls
|
100
134
|
|
101
135
|
In the example above there is the following code:
|
102
136
|
|
@@ -104,11 +138,13 @@ In the example above there is the following code:
|
|
104
138
|
@my_object.rename "Paul"
|
105
139
|
puts await { @my_object.greet } # => "Hello Paul"
|
106
140
|
```
|
107
|
-
The call to `rename` is asynchronous
|
141
|
+
The call to `rename` is asynchronous and we call `greet` immediately afterwards. You may expect that the result from `greet` would not be deterministic. If `rename` takes a long time to complete it may or may not have finished before we get to the next line of code. Does that mean that `greet` could return "Hello Ringo" or "Hello Paul" depending on timing?
|
142
|
+
|
143
|
+
No - the call to `greet` will _always_ return "Hello Paul".
|
108
144
|
|
109
|
-
|
145
|
+
This is because internally, the actor queues all asynchronous method calls.
|
110
146
|
|
111
|
-
|
147
|
+
The call to `rename` is added to the queue, then a future starts to process it. The subsequent call to `greet` is added to the queue and will not begin until the call to `rename` has completed.
|
112
148
|
|
113
149
|
Another example of the same behaviour is in `do_some_long_running_task`:
|
114
150
|
|
@@ -122,13 +158,62 @@ Another example of the same behaviour is in `do_some_long_running_task`:
|
|
122
158
|
|
123
159
|
When `@my_object.do_some_long_running_task` is called, the message `do_some_long_running_task` is added to the queue.
|
124
160
|
|
125
|
-
When the queue starts processing that message, `_do_some_long_running_task` (the implementation of the method) changes the status to :in_progress, then adds `do_part_two_of_the_long_running_task` to the message queue. This second message will _not_ start processing immediately as it is behind the unfinished `do_some_long_running_task`. `_do_some_long_running_task` returns the value of status (which is still :in_progress) and completes, which allows the next message on the queue to start. And that next message is probably `do_part_two_of_the_long_running_task` - but it might not be
|
161
|
+
When the queue starts processing that message, `_do_some_long_running_task` (the implementation of the method) changes the status to :in_progress, then adds `do_part_two_of_the_long_running_task` to the message queue. This second message will _not_ start processing immediately as it is behind the unfinished `do_some_long_running_task`. Eventually `_do_some_long_running_task` returns the value of status (which is still :in_progress) and completes, which allows the next message on the queue to start. And that next message is probably `do_part_two_of_the_long_running_task` - but, this being concurrent programming, it might not be (as another thread may have put a different message onto the queue first).
|
162
|
+
|
163
|
+
This queueing has an important implication.
|
164
|
+
|
165
|
+
If you are within an actor class, you can happily call async methods on other objects and `await` the results - because each actor has its own independent queue. But if you are calling your own methods, you must **never** call `await` (or call `value` on the returned message object).
|
166
|
+
|
167
|
+
Picture this:
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
class ClassThatWillDeadlock
|
171
|
+
include StandardProcedure::Async::Actor
|
172
|
+
|
173
|
+
async :start_processing do
|
174
|
+
do_some_complicated_calculation.value
|
175
|
+
end
|
176
|
+
|
177
|
+
async :do_some_complicated_calculation do
|
178
|
+
sleep 1
|
179
|
+
2 + 2
|
180
|
+
end
|
181
|
+
end
|
182
|
+
```
|
183
|
+
|
184
|
+
- Your actor has two `async` methods - `start_processing` and `do_complicated_calculation`
|
185
|
+
- The main thread calls `start_processing` - so a message gets added to the head of the queue.
|
186
|
+
- The actor's internal thread starts working on the message at the head of the queue, and `_start_processing` (the actual implementation) is called.
|
187
|
+
- Within `_start_processing` your actor calls `do_complicated_calculation`. This adds a message to the second position in the queue, ready to start working after `_start_processing` has completed. The message object itself is returned to `_start_processing`.
|
188
|
+
- Within `_start_processing` you call `value` to extract the actual return value from `do_complicated_calculation`. This then blocks the actor's thread, while it waits for the value to be calculated.
|
189
|
+
- But the message for `do_complicated_calculation` is second in the queue and won't be called until `_start_processing` has completed. And `_start_processing` won't complete, as it is now blocked until `_do_complicated_calculation` has finished.
|
190
|
+
- Your actor is now deadlocked and will not be able to do any more work.
|
191
|
+
|
192
|
+
To avoid this, if you ever need the return value from an internal `async` method, always call the implementation method: `_do_complicated_calculation` instead of `do_complicated_calculation`. This does not put anything on the queue and proceeds as a normal method call.
|
193
|
+
|
194
|
+
```ruby
|
195
|
+
class ClassThatWillNotDeadlock
|
196
|
+
include StandardProcedure::Async::Actor
|
197
|
+
|
198
|
+
async :start_processing do
|
199
|
+
_do_some_complicated_calculation
|
200
|
+
end
|
201
|
+
|
202
|
+
async :do_some_complicated_calculation do
|
203
|
+
sleep 1
|
204
|
+
2 + 2
|
205
|
+
end
|
206
|
+
end
|
207
|
+
```
|
208
|
+
|
209
|
+
|
210
|
+
As a fail-safe, any calls to `value` will also time-out after 30 seconds, returning Concurrent::MVar::TIMEOUT. If you need to override the timeout value you can use `message.value(timeout: value_in_seconds)`.
|
126
211
|
|
127
212
|
### The rules of using actors
|
128
213
|
|
129
|
-
- If you make a public method asynchronous, you need to make _all_ public methods asynchronous. You cannot mix and match asynchronous and synchronous usage. The simplest way to comply is to make all your public methods as `async` and your protected and private methods as synchronous.
|
130
|
-
- Never make internal instance variables directly accessible without an asynchronous method call. Do not use `attr_reader` or `attr_accessor` - these will bypass the internal queue and you may get inconsistent results from the actor. For the same reason, never update internal variables outside of an asynchronous call.
|
131
|
-
- When an object is calling its own internal methods, never use `await` or `value` as this will cause your actor to block indefinitely. If you don't care about the return value or if it does not matter when the method starts and finishes, call the asynchronous method. If you need the return value or need to be sure that the method runs immediately then use the internal implementation - `
|
214
|
+
- If you make a public method asynchronous, you need to make _all_ public methods asynchronous. You cannot mix and match asynchronous and synchronous usage. The simplest way to comply is to make all your public methods as `async` and your protected and private methods as synchronous.
|
215
|
+
- Never make internal instance variables directly accessible without an asynchronous method call. Do not use `attr_reader` or `attr_accessor` - these will bypass the internal queue and you may get inconsistent results from the actor. For the same reason, never update internal variables outside of an asynchronous call. In Object-Oriented terms, the doctrine is "Tell, don't Ask": tell your objects what you want them to do, instead of asking them for information about themselves.
|
216
|
+
- When an object is calling its own internal methods, never use `await` or `value` as this will cause your actor to block indefinitely (actually, they will time out after 30s). If you don't care about the return value or if it does not matter when the method starts and finishes, call the asynchronous method. If you need the return value or need to be sure that the method runs immediately then use the internal implementation - `_my_method` instead of `my_method`.
|
132
217
|
- Avoid class variables. These are effectively global variables that are accessible without any locking around them, so you could get inconsistent results. If you must have class variables, intialize them on startup and if they are mutable, use concurrent-ruby's thread-safe objects.
|
133
218
|
|
134
219
|
## Making Rails work with Concurrent-Ruby
|
@@ -164,10 +249,11 @@ Or install it yourself as:
|
|
164
249
|
## Development
|
165
250
|
|
166
251
|
Coming soon
|
252
|
+
|
167
253
|
## Contributing
|
168
254
|
|
169
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
255
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/standard-procedure/async.
|
170
256
|
|
171
257
|
## License
|
172
258
|
|
173
|
-
The gem is available as open source under the terms of the [
|
259
|
+
The gem is available as open source under the terms of the [GNU Lesser General Public Licence](https://www.gnu.org/licenses/lgpl-3.0.txt). Paraphrased, this means you can use this library in your applications, but you must make give prominent notice that you are using this code, under this licence, and you must make the source to this library available if the software is distributed. Any changes you make _to this library_ must also be released under the LGPL and made available if the software is distributed. The licensing for your own code (that does not amend this library) is unaffected.
|
@@ -0,0 +1 @@
|
|
1
|
+
e9ecad84981421f13a69b6fa77964a1331488ddeb9581dcb2381d7daa4f6ae712f4a33edd1291b50fb70a49896a14b9b84dc6df4001a5c834ed561173685ba30
|
@@ -4,7 +4,7 @@ require "concurrent/array"
|
|
4
4
|
require "concurrent/mvar"
|
5
5
|
require "concurrent/immutable_struct"
|
6
6
|
|
7
|
-
module
|
7
|
+
module StandardProcedure::Async
|
8
8
|
module Actor
|
9
9
|
def self.included base
|
10
10
|
base.class_eval do
|
@@ -44,16 +44,23 @@ module Standard::Procedure::Async
|
|
44
44
|
end
|
45
45
|
|
46
46
|
def _perform_messages
|
47
|
-
|
47
|
+
StandardProcedure::Async.promises.future do
|
48
48
|
while (message = _messages.shift)
|
49
49
|
message.call
|
50
50
|
end
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
+
# nodoc:
|
54
55
|
class Message < Concurrent::ImmutableStruct.new(:target, :name, :args, :block, :result)
|
55
|
-
def value
|
56
|
-
result.take
|
56
|
+
def value(timeout: 30)
|
57
|
+
result.take(timeout)
|
58
|
+
end
|
59
|
+
alias_method :get, :value
|
60
|
+
alias_method :await, :value
|
61
|
+
|
62
|
+
def then &block
|
63
|
+
block&.call value
|
57
64
|
end
|
58
65
|
|
59
66
|
def call
|
@@ -5,12 +5,10 @@ require_relative "async/error"
|
|
5
5
|
require_relative "async/promises"
|
6
6
|
require_relative "async/actor"
|
7
7
|
require_relative "async/await"
|
8
|
-
module
|
9
|
-
module
|
10
|
-
|
11
|
-
|
12
|
-
@promises ||= Promises.new
|
13
|
-
end
|
8
|
+
module StandardProcedure
|
9
|
+
module Async
|
10
|
+
def self.promises
|
11
|
+
@promises ||= Promises.new
|
14
12
|
end
|
15
13
|
end
|
16
14
|
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "lib/
|
3
|
+
require_relative "lib/standard_procedure/async/version"
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = "standard-procedure-async"
|
7
|
-
spec.version =
|
7
|
+
spec.version = StandardProcedure::Async::VERSION
|
8
8
|
spec.authors = ["Rahoul Baruah"]
|
9
9
|
spec.email = ["rahoulb@standardprocedure.app"]
|
10
10
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: standard-procedure-async
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rahoul Baruah
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-10-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -74,6 +74,7 @@ executables: []
|
|
74
74
|
extensions: []
|
75
75
|
extra_rdoc_files: []
|
76
76
|
files:
|
77
|
+
- ".nova/Configuration.json"
|
77
78
|
- ".rspec"
|
78
79
|
- ".standard.yml"
|
79
80
|
- CHANGELOG.md
|
@@ -82,14 +83,15 @@ files:
|
|
82
83
|
- LICENSE.txt
|
83
84
|
- README.md
|
84
85
|
- Rakefile
|
85
|
-
-
|
86
|
-
- lib/
|
87
|
-
- lib/
|
88
|
-
- lib/
|
89
|
-
- lib/
|
90
|
-
- lib/
|
91
|
-
- lib/
|
92
|
-
-
|
86
|
+
- checksums/standard-procedure-async-0.2.0.gem.sha512
|
87
|
+
- lib/standard_procedure/async.rb
|
88
|
+
- lib/standard_procedure/async/actor.rb
|
89
|
+
- lib/standard_procedure/async/await.rb
|
90
|
+
- lib/standard_procedure/async/error.rb
|
91
|
+
- lib/standard_procedure/async/promises.rb
|
92
|
+
- lib/standard_procedure/async/rails_not_loaded_error.rb
|
93
|
+
- lib/standard_procedure/async/version.rb
|
94
|
+
- sig/standard_procedure/async.rbs
|
93
95
|
- standard-procedure-async.gemspec
|
94
96
|
homepage: https://github.com/standard-procedure/async
|
95
97
|
licenses:
|
@@ -114,7 +116,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
114
116
|
- !ruby/object:Gem::Version
|
115
117
|
version: '0'
|
116
118
|
requirements: []
|
117
|
-
rubygems_version: 3.4.
|
119
|
+
rubygems_version: 3.4.20
|
118
120
|
signing_key:
|
119
121
|
specification_version: 4
|
120
122
|
summary: A simple wrapper around Concurrent::Future to make concurrent-ruby Rails-friendly.
|
File without changes
|
File without changes
|