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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b92c0e8440b6f2ad4b0c95cc83ac562ece4124490f15982f1d6531efec3ca2b1
4
- data.tar.gz: ba0100a22ccbf8c572489111b841bdee403347e2f3a65172b8584594ebd026e5
3
+ metadata.gz: 4936777d02c93a9195506f2807a9ecfbc80ccbfd4e50e9af6477456742efaf9a
4
+ data.tar.gz: 1a094f5fbe3e205204a1425eeb798cdfd075b497d2e9afb4e64b132a040964f6
5
5
  SHA512:
6
- metadata.gz: ec6d3846f51149189ae18dffbf38af10cbb9c317ac9b546b1e336b726219d040b7b0d5fab65dd4a33c1be079ad093703947236c46ce1345af0f33cc5474bb50d
7
- data.tar.gz: fb99264092314bad0f9c4c1e7d1061e2530456bf59b409eadbbe6f5f4a216ad69d71a5abd2fd3a74c4d0c1de0d9fa4f50b7f2053259de9435bfd3e9f7eb34fc8
6
+ metadata.gz: 9bacc126f5ecde4c54d9caf9d25cc73ccce1aa683d39ed4936ed4504791ab526af630511b4522c9fad7d699e55f5b8c9139a6d5e5b763fd0d550f386a4fafdf9
7
+ data.tar.gz: 45aceadf0e436aee7da11a21f2958953caa5e6de88938faac63340384554da0afae44230629a184560ecf4c700d93dab6d7c9d88c4b63d71d97726be43398423
@@ -0,0 +1,3 @@
1
+ {
2
+ "tommasonegri.solargraph.internals.server.error" : true
3
+ }
data/CHANGELOG.md CHANGED
@@ -1,4 +1,10 @@
1
- ## [Unreleased]
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.1.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
- The MIT License (MIT)
2
-
3
- Copyright (c) 2023 Rahoul Baruah
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- THE SOFTWARE.
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
- # Standard::Procedure::Async
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
- Two reasons.
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 [Standard::Procedure::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 MVars. 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.
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, Standard::Procedure::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.
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 [Standard::Procedure::Async::Actor](https://github.com/standard-procedure/async/blob/main/lib/standard/procedure/async/actor.rb) module and use the `async` class method.
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 wait until the return value is ready. Alternatively, you can use `await`, effectively turning your asynchronous method into a synchronous one.
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 similarly marks out asychronous function calls and waits until any Promises are ready to return their values.
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 Standard::Procedure::Async::Actor
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 # => a internal message object
80
- puts @initial_status.value # => :in_progress
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 an object containing a [Concurrent::MVar](https://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/MVar.html) which is empty until the actor has completed its work.
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
- If you need the return value from the method, there are two ways to access it.
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
- You can call `value` on the returned object, or you can use the `await` method (which is just a fancy wrapper around `value`). In both cases the calling thread will block until the value is returned. In the example above, you can see what happens if you use neither of these methods - `puts @initial_status` returns the internal message object itself, not any meaningful information. The next line then calls `@initial_status.value` to wait until the return value is generated.
125
+ ```ruby
126
+ @my_object.report_status.then do |value|
127
+ puts value
128
+ end
129
+ ```
98
130
 
99
- ### The sequence of asynchronous calls
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, so you may expect that sometimes the following call to `greet` would return "Hello Paul" and other times it would return "Hello Ringo" (the previous value) - depending on the timing of the two calls.
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
- However, it will _always_ return "Hello Paul".
145
+ This is because internally, the actor queues all asynchronous method calls.
110
146
 
111
- This is because internally, the actor queues all method calls. So even if the call to `rename` takes a long time to complete, the subsequent call to `greet` will not start until `rename` has finished.
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, as other threads may have got there first.
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. If you have to call an async method on `self` use the internal implementation (which starts with `_`).
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 - `_rename` instead of `rename`.
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/[USERNAME]/standard-procedure-async.
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 [MIT License](https://opensource.org/licenses/MIT).
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 Standard::Procedure::Async
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
- Standard::Procedure::Async.promises.future do
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
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Standard::Procedure::Async
3
+ module StandardProcedure::Async
4
4
  class Error < StandardError
5
5
  end
6
6
  end
@@ -3,7 +3,7 @@
3
3
  require_relative "rails_not_loaded_error"
4
4
  require "concurrent/promises"
5
5
 
6
- module Standard::Procedure::Async
6
+ module StandardProcedure::Async
7
7
  class Promises
8
8
  def initialize
9
9
  @promises = rails_is_loaded? ? ConcurrentRails::Promises : Concurrent::Promises
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative "error"
4
4
 
5
- module Standard::Procedure::Async
5
+ module StandardProcedure::Async
6
6
  class RailsNotLoadedError < Error
7
7
  end
8
8
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StandardProcedure
4
+ module Async
5
+ VERSION = "0.2.0"
6
+ end
7
+ end
@@ -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 Standard
9
- module Procedure
10
- module Async
11
- def self.promises
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/standard/procedure/async/version"
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 = Standard::Procedure::Async::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.1.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-05-31 00:00:00.000000000 Z
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
- - lib/standard/procedure/async.rb
86
- - lib/standard/procedure/async/actor.rb
87
- - lib/standard/procedure/async/await.rb
88
- - lib/standard/procedure/async/error.rb
89
- - lib/standard/procedure/async/promises.rb
90
- - lib/standard/procedure/async/rails_not_loaded_error.rb
91
- - lib/standard/procedure/async/version.rb
92
- - sig/standard/procedure/async.rbs
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.12
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.
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Standard
4
- module Procedure
5
- module Async
6
- VERSION = "0.1.0"
7
- end
8
- end
9
- end