concurrent_rails 0.2.0 → 0.2.1
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 +4 -4
- data/README.md +36 -6
- data/lib/concurrent_rails.rb +1 -0
- data/lib/concurrent_rails/adapters/delay.rb +0 -2
- data/lib/concurrent_rails/future.rb +9 -5
- data/lib/concurrent_rails/multi.rb +1 -3
- data/lib/concurrent_rails/promises.rb +16 -3
- data/lib/concurrent_rails/testing.rb +37 -0
- data/lib/concurrent_rails/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0352c1516c930c65708383fa734cd7444b02b0a213c0ef1f794e5e31f8e1fdf
|
4
|
+
data.tar.gz: 33ec089c95b33ff31a4652a073feac356e8b23a8d7e30bd214ea0a0dbc4aedcb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a486a3ab777574962127795db050fd2615b69594d0bc88adb5114368c7d90492f2b51b237817f97118a31969396d53171bf66371240577051977a052d935148a
|
7
|
+
data.tar.gz: db095fe9e33e527a3ade62e8a40aec1866718bfce6b55ef519775cd1761d78b1eb1868d8c8b0ab799dcd05f78980ba16e31f5a7912b87ab3e2dd49d917dab004
|
data/README.md
CHANGED
@@ -2,8 +2,7 @@
|
|
2
2
|
|
3
3
|

|
4
4
|
|
5
|
-
Multithread is hard. [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby) did an amazing job
|
6
|
-
implementing the concepts of multithread in the Ruby world. The problem is that Rails doesn't play nice with it. Rails have a complex way of managing threads called Executor and concurrent-ruby (most specifically, [Future](https://github.com/ruby-concurrency/concurrent-ruby/blob/master/docs-source/future.md)) does not work seamlessly with it.
|
5
|
+
Multithread is hard. [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby) did an amazing job implementing the concepts of multithread in the Ruby world. The problem is that Rails doesn't play nice with it. Rails have a complex way of managing threads called Executor and concurrent-ruby (most specifically, [Future](https://github.com/ruby-concurrency/concurrent-ruby/blob/master/docs-source/future.md)) does not work seamlessly with it.
|
7
6
|
|
8
7
|
The goal of this gem is to provide a simple library that allows the developer to work with Futures without having to care about Rails's Executor and the whole pack of problems that come with it: autoload, thread pools, active record connections, etc.
|
9
8
|
|
@@ -84,7 +83,7 @@ All of these callbacks have a bang version (e.g. `on_fulfillment!`). The bang ve
|
|
84
83
|
|
85
84
|
### (Deprecated) Future
|
86
85
|
|
87
|
-
`ConcurrentRails::Future` will execute your code in a
|
86
|
+
`ConcurrentRails::Future` will execute your code in a separate thread and you can check the progress of it whenever you need it. When the task is ready, you can access the result with `#result` function:
|
88
87
|
|
89
88
|
```ruby
|
90
89
|
irb(main):001:0> future = ConcurrentRails::Future.new do
|
@@ -174,6 +173,37 @@ irb(main):007:0> multi.errors
|
|
174
173
|
|
175
174
|
It is worth mention that a failed proc will return `nil`.
|
176
175
|
|
176
|
+
## Testing
|
177
|
+
If you are using RSpec, you will notice that it might not play well with threads. ActiveRecord opens a database connection for every thread and since RSpec tests are wrapped in a transaction, by the time you promise tries to access something on the database, for example, a user, gems like Database Cleaner probably already triggered and deleted the user, resulting in `ActiveRecord::RecordNotFound` errors. You have a couple of solutions like disable transactional fixtures if you are using it or update the Database Cleaner strategy (that will result in much slower tests).
|
178
|
+
Since none of these solutions were satisfactory to me, I created `ConcurrentRails::Testing` with two strategies: `immediate!` and `fake!`. When you wrap a Promise's `future` with `immediate!`, the executor gets replaced from `:io` to `:immediate`. It still returns a promise anyway. This is not the case with `fake!` strategy: it executes the task outside the `ConcurrentRails` engine and returns whatever `.value` would return:
|
179
|
+
|
180
|
+
`immediate!` strategy:
|
181
|
+
```ruby
|
182
|
+
irb(main):001:1* result = ConcurrentRails::Testing.immediate! do
|
183
|
+
irb(main):002:1* ConcurrentRails::Promises.future { 42 }
|
184
|
+
irb(main):003:0> end
|
185
|
+
=>
|
186
|
+
#<ConcurrentRails::Promises:0x000000013e5fc870
|
187
|
+
...
|
188
|
+
irb(main):004:0> result.class
|
189
|
+
=> ConcurrentRails::Promises # <-- Still a `ConcurrentRails::Promises` class
|
190
|
+
irb(main):005:0> result.executor
|
191
|
+
=> :immediate # <-- default executor (:io) gets replaced
|
192
|
+
```
|
193
|
+
|
194
|
+
`fake!` strategy:
|
195
|
+
|
196
|
+
```ruby
|
197
|
+
irb(main):001:1* result = ConcurrentRails::Testing.fake! do
|
198
|
+
irb(main):002:1* ConcurrentRails::Promises.future { 42 }
|
199
|
+
irb(main):003:0> end
|
200
|
+
=> 42 # <-- yields the task but does not return a Promise
|
201
|
+
irb(main):004:0> result.class
|
202
|
+
=> Integer
|
203
|
+
```
|
204
|
+
|
205
|
+
## Further reading
|
206
|
+
|
177
207
|
For more information on how Futures work and how Rails handle multithread check these links:
|
178
208
|
|
179
209
|
[Future documentation](https://github.com/ruby-concurrency/concurrent-ruby/blob/master/docs-source/future.md)
|
@@ -185,19 +215,19 @@ For more information on how Futures work and how Rails handle multithread check
|
|
185
215
|
Add this line to your application's Gemfile:
|
186
216
|
|
187
217
|
```ruby
|
188
|
-
gem 'concurrent_rails', '~> 0.2.
|
218
|
+
gem 'concurrent_rails', '~> 0.2.1'
|
189
219
|
```
|
190
220
|
|
191
221
|
And then execute:
|
192
222
|
|
193
223
|
```bash
|
194
|
-
|
224
|
+
bundle
|
195
225
|
```
|
196
226
|
|
197
227
|
Or install it yourself as:
|
198
228
|
|
199
229
|
```bash
|
200
|
-
|
230
|
+
gem install concurrent_rails
|
201
231
|
```
|
202
232
|
|
203
233
|
## Contributing
|
data/lib/concurrent_rails.rb
CHANGED
@@ -5,7 +5,7 @@ module ConcurrentRails
|
|
5
5
|
def initialize(executor: :io, &block)
|
6
6
|
@executor = executor
|
7
7
|
@future = run_on_rails(block)
|
8
|
-
|
8
|
+
ActiveSupport::Deprecation.warn('ConcurrentRails::Future is deprecated. See README for details')
|
9
9
|
end
|
10
10
|
|
11
11
|
def execute
|
@@ -16,10 +16,8 @@ module ConcurrentRails
|
|
16
16
|
|
17
17
|
%i[value value!].each do |method_name|
|
18
18
|
define_method method_name do
|
19
|
-
|
20
|
-
|
21
|
-
future.__send__(method_name)
|
22
|
-
end
|
19
|
+
permit_concurrent_loads do
|
20
|
+
future.__send__(method_name)
|
23
21
|
end
|
24
22
|
end
|
25
23
|
end
|
@@ -40,6 +38,12 @@ module ConcurrentRails
|
|
40
38
|
Rails.application.executor.wrap(&block)
|
41
39
|
end
|
42
40
|
|
41
|
+
def permit_concurrent_loads(&block)
|
42
|
+
rails_wrapped do
|
43
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads(&block)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
43
47
|
attr_reader :executor, :future
|
44
48
|
end
|
45
49
|
end
|
@@ -3,9 +3,7 @@
|
|
3
3
|
module ConcurrentRails
|
4
4
|
class Multi
|
5
5
|
def self.enqueue(*actions, executor: :io)
|
6
|
-
unless actions.all?
|
7
|
-
raise ArgumentError, '#enqueue accepts `Proc`s only'
|
8
|
-
end
|
6
|
+
raise ArgumentError, '#enqueue accepts `Proc`s only' unless actions.all?(Proc)
|
9
7
|
|
10
8
|
new(actions, executor).enqueue
|
11
9
|
end
|
@@ -32,6 +32,18 @@ module ConcurrentRails
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
+
def touch
|
36
|
+
@instance = rails_wrapped { instance.touch }
|
37
|
+
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def wait(timeout = nil)
|
42
|
+
result = permit_concurrent_loads { instance.__send__(:wait_until_resolved, timeout) }
|
43
|
+
|
44
|
+
timeout ? result : self
|
45
|
+
end
|
46
|
+
|
35
47
|
%i[on_fulfillment on_rejection on_resolution].each do |method|
|
36
48
|
define_method(method) do |*args, &callback_task|
|
37
49
|
rails_wrapped do
|
@@ -50,8 +62,9 @@ module ConcurrentRails
|
|
50
62
|
end
|
51
63
|
end
|
52
64
|
|
53
|
-
delegate :state, :reason, :rejected?, :resolved?, :fulfilled?, :
|
54
|
-
|
65
|
+
delegate :state, :reason, :rejected?, :resolved?, :fulfilled?, to: :instance
|
66
|
+
|
67
|
+
attr_reader :executor
|
55
68
|
|
56
69
|
private
|
57
70
|
|
@@ -65,6 +78,6 @@ module ConcurrentRails
|
|
65
78
|
end
|
66
79
|
end
|
67
80
|
|
68
|
-
attr_reader :
|
81
|
+
attr_reader :instance
|
69
82
|
end
|
70
83
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ConcurrentRails
|
4
|
+
class Testing
|
5
|
+
class << self
|
6
|
+
attr_reader :execution_mode
|
7
|
+
|
8
|
+
%i[immediate fake].each do |exec_method|
|
9
|
+
define_method("#{exec_method}!") do |&task|
|
10
|
+
@execution_mode = exec_method
|
11
|
+
result = task.call
|
12
|
+
@execution_mode = :real
|
13
|
+
|
14
|
+
result
|
15
|
+
end
|
16
|
+
|
17
|
+
define_method("#{exec_method}?") do
|
18
|
+
execution_mode == exec_method
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module TestingFuture
|
23
|
+
def future(*args, &task)
|
24
|
+
if ConcurrentRails::Testing.immediate?
|
25
|
+
future_on(:immediate, *args, &task)
|
26
|
+
elsif ConcurrentRails::Testing.fake?
|
27
|
+
yield
|
28
|
+
else
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
ConcurrentRails::Promises.extend(TestingFuture)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: concurrent_rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Luiz Eduardo Kowalski
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-06-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -97,6 +97,7 @@ files:
|
|
97
97
|
- lib/concurrent_rails/multi.rb
|
98
98
|
- lib/concurrent_rails/promises.rb
|
99
99
|
- lib/concurrent_rails/railtie.rb
|
100
|
+
- lib/concurrent_rails/testing.rb
|
100
101
|
- lib/concurrent_rails/version.rb
|
101
102
|
homepage: https://github.com/luizkowalski/concurrent_rails
|
102
103
|
licenses:
|
@@ -120,7 +121,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
120
121
|
- !ruby/object:Gem::Version
|
121
122
|
version: '0'
|
122
123
|
requirements: []
|
123
|
-
rubygems_version: 3.2.
|
124
|
+
rubygems_version: 3.2.20
|
124
125
|
signing_key:
|
125
126
|
specification_version: 4
|
126
127
|
summary: Multithread is hard
|