concurrent_rails 0.5.1 → 0.6.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 +4 -4
- data/README.md +6 -4
- data/lib/concurrent_rails/promises.rb +4 -7
- data/lib/concurrent_rails/testing.rb +4 -4
- data/lib/concurrent_rails/version.rb +1 -1
- data/lib/concurrent_rails.rb +3 -6
- metadata +20 -8
- data/lib/concurrent_rails/future.rb +0 -49
- data/lib/concurrent_rails/multi.rb +0 -51
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5667dde5992af86d3d798a7a2f87b9ec6f84ebaa3eedcd4f37fa76ba5caa460e
|
4
|
+
data.tar.gz: 2f8d9f11f5fb55acbb4f9a6277312f754cc9798d3cb3c48c52873d15d6c21416
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: efe856cba6102e157f0286451ddfa618e9a56195456f16d4dc090a16aefb0846a5c227793fdc92686021b243a2603ccbb27833edbeb15d94ea974cbcf54ac37e
|
7
|
+
data.tar.gz: 595c6b3011bd8094407efcc1746d8f0c949a2fb4b37d4678923a84b8ef7d0a2f1ca1eb7a4bc61165cbdd7f9256802932a4711635552cfd60978a353b80fa8cfd
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# ConcurrentRails
|
2
2
|
|
3
|
-

|
4
4
|
|
5
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 has 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.
|
6
6
|
|
@@ -8,7 +8,7 @@ The goal of this gem is to provide a simple library that allows the developer to
|
|
8
8
|
|
9
9
|
## Usage
|
10
10
|
|
11
|
-
This library provides three classes that will help you run tasks in parallel: `ConcurrentRails::Promises`, `ConcurrentRails::Future` ([in process of being deprecated by concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby#deprecated)) and `ConcurrentRails::Multi`
|
11
|
+
This library provides three classes that will help you run tasks in parallel: `ConcurrentRails::Promises`, `ConcurrentRails::Future` ([in the process of being deprecated by concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby#deprecated)) and `ConcurrentRails::Multi`
|
12
12
|
|
13
13
|
### Promises
|
14
14
|
|
@@ -174,16 +174,18 @@ irb(main):007:0> multi.errors
|
|
174
174
|
It is worth mention that a failed proc will return `nil`.
|
175
175
|
|
176
176
|
## Testing
|
177
|
+
|
177
178
|
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 your 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
179
|
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
|
|
180
181
|
`immediate` strategy:
|
182
|
+
|
181
183
|
```ruby
|
182
184
|
irb(main):001:1* result = ConcurrentRails::Testing.immediate do
|
183
185
|
irb(main):002:1* ConcurrentRails::Promises.future { 42 }
|
184
186
|
irb(main):003:0> end
|
185
187
|
=>
|
186
|
-
#<ConcurrentRails::Promises:0x000000013e5fc870
|
188
|
+
#<ConcurrentRails::Promises:0x000000013e5fc870
|
187
189
|
...
|
188
190
|
irb(main):004:0> result.class
|
189
191
|
=> ConcurrentRails::Promises # <-- Still a `ConcurrentRails::Promises` class
|
@@ -217,7 +219,7 @@ For more information on how Futures work and how Rails handle multithread check
|
|
217
219
|
Add this line to your application's Gemfile:
|
218
220
|
|
219
221
|
```ruby
|
220
|
-
gem 'concurrent_rails', '~> 0.
|
222
|
+
gem 'concurrent_rails', '~> 0.5.1'
|
221
223
|
```
|
222
224
|
|
223
225
|
And then execute:
|
@@ -1,8 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "concurrent_rails/future_adapter"
|
4
|
-
require "concurrent_rails/delay_adapter"
|
5
|
-
|
6
3
|
module ConcurrentRails
|
7
4
|
class Promises
|
8
5
|
include Concurrent::Promises::FactoryMethods
|
@@ -47,13 +44,13 @@ module ConcurrentRails
|
|
47
44
|
%i[on_fulfillment on_rejection on_resolution].each do |method|
|
48
45
|
define_method(method) do |*args, &callback_task|
|
49
46
|
rails_wrapped do
|
50
|
-
@instance = instance.__send__("#{method}_using", executor, *args, &callback_task)
|
47
|
+
@instance = instance.__send__(:"#{method}_using", executor, *args, &callback_task)
|
51
48
|
end
|
52
49
|
|
53
50
|
self
|
54
51
|
end
|
55
52
|
|
56
|
-
define_method("#{method}!") do |*args, &callback_task|
|
53
|
+
define_method(:"#{method}!") do |*args, &callback_task|
|
57
54
|
rails_wrapped do
|
58
55
|
@instance = instance.__send__(:add_callback, "callback_#{method}", args, callback_task)
|
59
56
|
end
|
@@ -68,8 +65,8 @@ module ConcurrentRails
|
|
68
65
|
|
69
66
|
private
|
70
67
|
|
71
|
-
def rails_wrapped(&
|
72
|
-
Rails.application.executor.wrap(&
|
68
|
+
def rails_wrapped(&)
|
69
|
+
Rails.application.executor.wrap(&)
|
73
70
|
end
|
74
71
|
|
75
72
|
def permit_concurrent_loads(&block)
|
@@ -14,20 +14,20 @@ module ConcurrentRails
|
|
14
14
|
result
|
15
15
|
end
|
16
16
|
|
17
|
-
define_method("#{test_mode}!") do
|
17
|
+
define_method(:"#{test_mode}!") do
|
18
18
|
@execution_mode = test_mode
|
19
19
|
end
|
20
20
|
|
21
|
-
define_method("#{test_mode}?") do
|
21
|
+
define_method(:"#{test_mode}?") do
|
22
22
|
execution_mode == test_mode
|
23
23
|
end
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
27
|
module TestingFuture
|
28
|
-
def future(*args, &
|
28
|
+
def future(*args, &)
|
29
29
|
if ConcurrentRails::Testing.immediate?
|
30
|
-
future_on(:immediate, *args, &
|
30
|
+
future_on(:immediate, *args, &)
|
31
31
|
elsif ConcurrentRails::Testing.fake?
|
32
32
|
yield
|
33
33
|
else
|
data/lib/concurrent_rails.rb
CHANGED
@@ -1,8 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
4
|
-
|
5
|
-
|
6
|
-
require "concurrent_rails/railtie"
|
7
|
-
require "concurrent_rails/testing"
|
8
|
-
require "concurrent_rails/version"
|
3
|
+
require "zeitwerk"
|
4
|
+
loader = Zeitwerk::Loader.for_gem
|
5
|
+
loader.setup
|
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.
|
4
|
+
version: 0.6.0
|
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:
|
11
|
+
date: 2024-08-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: railties
|
@@ -16,14 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '6.
|
19
|
+
version: '6.1'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '6.
|
26
|
+
version: '6.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: zeitwerk
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
description: Small library to make concurrent-ruby and Rails play nice together
|
28
42
|
email:
|
29
43
|
- luizeduardokowalski@gmail.com
|
@@ -36,9 +50,7 @@ files:
|
|
36
50
|
- Rakefile
|
37
51
|
- lib/concurrent_rails.rb
|
38
52
|
- lib/concurrent_rails/delay_adapter.rb
|
39
|
-
- lib/concurrent_rails/future.rb
|
40
53
|
- lib/concurrent_rails/future_adapter.rb
|
41
|
-
- lib/concurrent_rails/multi.rb
|
42
54
|
- lib/concurrent_rails/promises.rb
|
43
55
|
- lib/concurrent_rails/railtie.rb
|
44
56
|
- lib/concurrent_rails/testing.rb
|
@@ -56,14 +68,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
56
68
|
requirements:
|
57
69
|
- - ">="
|
58
70
|
- !ruby/object:Gem::Version
|
59
|
-
version: '3.
|
71
|
+
version: '3.1'
|
60
72
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
73
|
requirements:
|
62
74
|
- - ">="
|
63
75
|
- !ruby/object:Gem::Version
|
64
76
|
version: '0'
|
65
77
|
requirements: []
|
66
|
-
rubygems_version: 3.
|
78
|
+
rubygems_version: 3.5.17
|
67
79
|
signing_key:
|
68
80
|
specification_version: 4
|
69
81
|
summary: Multithread is hard
|
@@ -1,49 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ConcurrentRails
|
4
|
-
class Future
|
5
|
-
def initialize(executor: :io, &block)
|
6
|
-
@executor = executor
|
7
|
-
@future = run_on_rails(block)
|
8
|
-
ActiveSupport::Deprecation.warn("ConcurrentRails::Future is deprecated. See README for details")
|
9
|
-
end
|
10
|
-
|
11
|
-
def execute
|
12
|
-
future.execute
|
13
|
-
|
14
|
-
self
|
15
|
-
end
|
16
|
-
|
17
|
-
%i[value value!].each do |method_name|
|
18
|
-
define_method method_name do
|
19
|
-
permit_concurrent_loads do
|
20
|
-
future.__send__(method_name)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
delegate :state, :reason, :rejected?, :complete?, :add_observer, to: :future
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
def run_on_rails(block)
|
30
|
-
@future = rails_wrapped do
|
31
|
-
Concurrent::Future.new(executor: executor) do
|
32
|
-
rails_wrapped(&block)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def rails_wrapped(&block)
|
38
|
-
Rails.application.executor.wrap(&block)
|
39
|
-
end
|
40
|
-
|
41
|
-
def permit_concurrent_loads(&block)
|
42
|
-
rails_wrapped do
|
43
|
-
ActiveSupport::Dependencies.interlock.permit_concurrent_loads(&block)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
attr_reader :executor, :future
|
48
|
-
end
|
49
|
-
end
|
@@ -1,51 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ConcurrentRails
|
4
|
-
class Multi
|
5
|
-
def self.enqueue(*actions, executor: :io)
|
6
|
-
raise ArgumentError, "#enqueue accepts `Proc`s only" unless actions.all?(Proc)
|
7
|
-
|
8
|
-
new(actions, executor).enqueue
|
9
|
-
end
|
10
|
-
|
11
|
-
def initialize(actions, executor)
|
12
|
-
@actions = actions
|
13
|
-
@executor = executor
|
14
|
-
@exceptions = Concurrent::Array.new
|
15
|
-
end
|
16
|
-
|
17
|
-
def enqueue
|
18
|
-
@futures = actions.map do |action|
|
19
|
-
f = ConcurrentRails::Future.new(executor: executor, &action)
|
20
|
-
f.add_observer(self)
|
21
|
-
f.execute
|
22
|
-
end
|
23
|
-
|
24
|
-
self
|
25
|
-
end
|
26
|
-
|
27
|
-
def compute
|
28
|
-
futures.map(&:value)
|
29
|
-
end
|
30
|
-
|
31
|
-
def compute!
|
32
|
-
futures.map(&:value!)
|
33
|
-
end
|
34
|
-
|
35
|
-
def complete?
|
36
|
-
futures.all?(&:complete?)
|
37
|
-
end
|
38
|
-
|
39
|
-
def errors
|
40
|
-
@exceptions
|
41
|
-
end
|
42
|
-
|
43
|
-
private
|
44
|
-
|
45
|
-
def update(_time, _value, reason)
|
46
|
-
@exceptions << reason if reason
|
47
|
-
end
|
48
|
-
|
49
|
-
attr_reader :actions, :futures, :exceptions, :executor
|
50
|
-
end
|
51
|
-
end
|