concurrent_rails 0.2.1 → 0.3.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: a0352c1516c930c65708383fa734cd7444b02b0a213c0ef1f794e5e31f8e1fdf
4
- data.tar.gz: 33ec089c95b33ff31a4652a073feac356e8b23a8d7e30bd214ea0a0dbc4aedcb
3
+ metadata.gz: fd75cf15aa7d7d0354bea478936bde3967d20a8e38daabff9e3fddf5c11bd5b6
4
+ data.tar.gz: 8c5faee065ed6de8bb7fedf2b29c3bc371792f579c7a00e6195d4b4e18ee0a29
5
5
  SHA512:
6
- metadata.gz: a486a3ab777574962127795db050fd2615b69594d0bc88adb5114368c7d90492f2b51b237817f97118a31969396d53171bf66371240577051977a052d935148a
7
- data.tar.gz: db095fe9e33e527a3ade62e8a40aec1866718bfce6b55ef519775cd1761d78b1eb1868d8c8b0ab799dcd05f78980ba16e31f5a7912b87ab3e2dd49d917dab004
6
+ metadata.gz: 649cf84ed43e2b6c7e70f89840a3091fefea3ade9df62af7d50ee7e7ef309af24729912bd9dbc5adc4112da953d3964e8b0ac4d41b36d3eb256edd73813c979f
7
+ data.tar.gz: 818ffd9287980e87473f9445b0424a5a006271b3ce4a070a4bd4b6fb08c4405576eff466e4def1c5ddce032614600f3d9a830951d20cfbb29823b7667545780a
data/README.md CHANGED
@@ -174,12 +174,12 @@ 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
- 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:
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 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
+ 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
179
 
180
- `immediate!` strategy:
180
+ `immediate` strategy:
181
181
  ```ruby
182
- irb(main):001:1* result = ConcurrentRails::Testing.immediate! do
182
+ irb(main):001:1* result = ConcurrentRails::Testing.immediate do
183
183
  irb(main):002:1* ConcurrentRails::Promises.future { 42 }
184
184
  irb(main):003:0> end
185
185
  =>
@@ -191,10 +191,10 @@ irb(main):005:0> result.executor
191
191
  => :immediate # <-- default executor (:io) gets replaced
192
192
  ```
193
193
 
194
- `fake!` strategy:
194
+ `fake` strategy:
195
195
 
196
196
  ```ruby
197
- irb(main):001:1* result = ConcurrentRails::Testing.fake! do
197
+ irb(main):001:1* result = ConcurrentRails::Testing.fake do
198
198
  irb(main):002:1* ConcurrentRails::Promises.future { 42 }
199
199
  irb(main):003:0> end
200
200
  => 42 # <-- yields the task but does not return a Promise
@@ -202,6 +202,8 @@ irb(main):004:0> result.class
202
202
  => Integer
203
203
  ```
204
204
 
205
+ You can also set the stragegy globally using `ConcurrentRails::Testing.fake!` or `ConcurrentRails::Testing.immediate!`
206
+
205
207
  ## Further reading
206
208
 
207
209
  For more information on how Futures work and how Rails handle multithread check these links:
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConcurrentRails::DelayAdapter
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+ def delay(*args, &task)
8
+ delay_on(:io, *args, &task)
9
+ end
10
+
11
+ def delay_on(executor, *args, &task)
12
+ new(executor).delay_on_rails(*args, &task)
13
+ end
14
+ end
15
+
16
+ def delay_on_rails(*args, &task)
17
+ @instance = rails_wrapped { delay_on(executor, *args, &task) }
18
+
19
+ self
20
+ end
21
+ end
@@ -1,49 +1,47 @@
1
1
  # frozen_string_literal: true
2
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
3
+ class ConcurrentRails::Future
4
+ def initialize(executor: :io, &block)
5
+ @executor = executor
6
+ @future = run_on_rails(block)
7
+ ActiveSupport::Deprecation.warn('ConcurrentRails::Future is deprecated. See README for details')
8
+ end
10
9
 
11
- def execute
12
- future.execute
10
+ def execute
11
+ future.execute
13
12
 
14
- self
15
- end
13
+ self
14
+ end
16
15
 
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
16
+ %i[value value!].each do |method_name|
17
+ define_method method_name do
18
+ permit_concurrent_loads do
19
+ future.__send__(method_name)
22
20
  end
23
21
  end
22
+ end
24
23
 
25
- delegate :state, :reason, :rejected?, :complete?, :add_observer, to: :future
24
+ delegate :state, :reason, :rejected?, :complete?, :add_observer, to: :future
26
25
 
27
- private
26
+ private
28
27
 
29
- def run_on_rails(block)
30
- @future = rails_wrapped do
31
- Concurrent::Future.new(executor: executor) do
32
- rails_wrapped(&block)
33
- end
28
+ def run_on_rails(block)
29
+ @future = rails_wrapped do
30
+ Concurrent::Future.new(executor: executor) do
31
+ rails_wrapped(&block)
34
32
  end
35
33
  end
34
+ end
36
35
 
37
- def rails_wrapped(&block)
38
- Rails.application.executor.wrap(&block)
39
- end
36
+ def rails_wrapped(&block)
37
+ Rails.application.executor.wrap(&block)
38
+ end
40
39
 
41
- def permit_concurrent_loads(&block)
42
- rails_wrapped do
43
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads(&block)
44
- end
40
+ def permit_concurrent_loads(&block)
41
+ rails_wrapped do
42
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads(&block)
45
43
  end
46
-
47
- attr_reader :executor, :future
48
44
  end
45
+
46
+ attr_reader :executor, :future
49
47
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConcurrentRails::FutureAdapter
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+ def future(*args, &task)
8
+ future_on(:io, *args, &task)
9
+ end
10
+
11
+ def future_on(executor, *args, &task)
12
+ new(executor).future_on_rails(*args, &task)
13
+ end
14
+ end
15
+
16
+ def future_on_rails(*args, &task)
17
+ @instance = rails_wrapped { future_on(executor, *args, &task) }
18
+
19
+ self
20
+ end
21
+ end
@@ -1,51 +1,49 @@
1
1
  # frozen_string_literal: true
2
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)
3
+ class ConcurrentRails::Multi
4
+ def self.enqueue(*actions, executor: :io)
5
+ raise ArgumentError, '#enqueue accepts `Proc`s only' unless actions.all?(Proc)
7
6
 
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
7
+ new(actions, executor).enqueue
8
+ end
16
9
 
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
10
+ def initialize(actions, executor)
11
+ @actions = actions
12
+ @executor = executor
13
+ @exceptions = Concurrent::Array.new
14
+ end
23
15
 
24
- self
16
+ def enqueue
17
+ @futures = actions.map do |action|
18
+ f = ConcurrentRails::Future.new(executor: executor, &action)
19
+ f.add_observer(self)
20
+ f.execute
25
21
  end
26
22
 
27
- def compute
28
- futures.map(&:value)
29
- end
23
+ self
24
+ end
30
25
 
31
- def compute!
32
- futures.map(&:value!)
33
- end
26
+ def compute
27
+ futures.map(&:value)
28
+ end
34
29
 
35
- def complete?
36
- futures.all?(&:complete?)
37
- end
30
+ def compute!
31
+ futures.map(&:value!)
32
+ end
38
33
 
39
- def errors
40
- @exceptions
41
- end
34
+ def complete?
35
+ futures.all?(&:complete?)
36
+ end
42
37
 
43
- private
38
+ def errors
39
+ @exceptions
40
+ end
44
41
 
45
- def update(_time, _value, reason)
46
- @exceptions << reason if reason
47
- end
42
+ private
48
43
 
49
- attr_reader :actions, :futures, :exceptions, :executor
44
+ def update(_time, _value, reason)
45
+ @exceptions << reason if reason
50
46
  end
47
+
48
+ attr_reader :actions, :futures, :exceptions, :executor
51
49
  end
@@ -1,83 +1,81 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'concurrent_rails/adapters/future'
4
- require 'concurrent_rails/adapters/delay'
3
+ require 'concurrent_rails/future_adapter'
4
+ require 'concurrent_rails/delay_adapter'
5
5
 
6
- module ConcurrentRails
7
- class Promises
8
- include Concurrent::Promises::FactoryMethods
9
- include ConcurrentRails::Adapters::Delay
10
- include ConcurrentRails::Adapters::Future
6
+ class ConcurrentRails::Promises
7
+ include Concurrent::Promises::FactoryMethods
8
+ include ConcurrentRails::DelayAdapter
9
+ include ConcurrentRails::FutureAdapter
11
10
 
12
- def initialize(executor)
13
- @executor = executor
14
- end
11
+ def initialize(executor)
12
+ @executor = executor
13
+ end
15
14
 
16
- %i[value value!].each do |method_name|
17
- define_method(method_name) do |timeout = nil, timeout_value = nil|
18
- permit_concurrent_loads do
19
- instance.__send__(method_name, timeout, timeout_value)
20
- end
15
+ %i[value value!].each do |method_name|
16
+ define_method(method_name) do |timeout = nil, timeout_value = nil|
17
+ permit_concurrent_loads do
18
+ instance.public_send(method_name, timeout, timeout_value)
21
19
  end
22
20
  end
21
+ end
23
22
 
24
- %i[then chain].each do |chainable|
25
- define_method(chainable) do |*args, &task|
26
- method = "#{chainable}_on"
27
- @instance = rails_wrapped do
28
- instance.__send__(method, executor, *args, &task)
29
- end
30
-
31
- self
23
+ %i[then chain].each do |chainable|
24
+ define_method(chainable) do |*args, &task|
25
+ method = "#{chainable}_on"
26
+ @instance = rails_wrapped do
27
+ instance.public_send(method, executor, *args, &task)
32
28
  end
33
- end
34
-
35
- def touch
36
- @instance = rails_wrapped { instance.touch }
37
29
 
38
30
  self
39
31
  end
32
+ end
40
33
 
41
- def wait(timeout = nil)
42
- result = permit_concurrent_loads { instance.__send__(:wait_until_resolved, timeout) }
34
+ def touch
35
+ @instance = rails_wrapped { instance.touch }
43
36
 
44
- timeout ? result : self
45
- end
37
+ self
38
+ end
46
39
 
47
- %i[on_fulfillment on_rejection on_resolution].each do |method|
48
- define_method(method) do |*args, &callback_task|
49
- rails_wrapped do
50
- @instance = instance.__send__("#{method}_using", executor, *args, &callback_task)
51
- end
40
+ def wait(timeout = nil)
41
+ result = permit_concurrent_loads { instance.__send__(:wait_until_resolved, timeout) }
52
42
 
53
- self
43
+ timeout ? result : self
44
+ end
45
+
46
+ %i[on_fulfillment on_rejection on_resolution].each do |method|
47
+ define_method(method) do |*args, &callback_task|
48
+ rails_wrapped do
49
+ @instance = instance.__send__("#{method}_using", executor, *args, &callback_task)
54
50
  end
55
51
 
56
- define_method("#{method}!") do |*args, &callback_task|
57
- rails_wrapped do
58
- @instance = instance.__send__(:add_callback, "callback_#{method}", args, callback_task)
59
- end
52
+ self
53
+ end
60
54
 
61
- self
55
+ define_method("#{method}!") do |*args, &callback_task|
56
+ rails_wrapped do
57
+ @instance = instance.__send__(:add_callback, "callback_#{method}", args, callback_task)
62
58
  end
59
+
60
+ self
63
61
  end
62
+ end
64
63
 
65
- delegate :state, :reason, :rejected?, :resolved?, :fulfilled?, to: :instance
64
+ delegate :state, :reason, :rejected?, :resolved?, :fulfilled?, to: :instance
66
65
 
67
- attr_reader :executor
66
+ attr_reader :executor
68
67
 
69
- private
68
+ private
70
69
 
71
- def rails_wrapped(&block)
72
- Rails.application.executor.wrap(&block)
73
- end
70
+ def rails_wrapped(&block)
71
+ Rails.application.executor.wrap(&block)
72
+ end
74
73
 
75
- def permit_concurrent_loads(&block)
76
- rails_wrapped do
77
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads(&block)
78
- end
74
+ def permit_concurrent_loads(&block)
75
+ rails_wrapped do
76
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads(&block)
79
77
  end
80
-
81
- attr_reader :instance
82
78
  end
79
+
80
+ attr_reader :instance
83
81
  end
@@ -1,6 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ConcurrentRails
4
- class Railtie < ::Rails::Railtie
5
- end
3
+ class ConcurrentRails::Railtie < ::Rails::Railtie
6
4
  end
@@ -1,37 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ConcurrentRails
4
- class Testing
5
- class << self
6
- attr_reader :execution_mode
3
+ class ConcurrentRails::Testing
4
+ class << self
5
+ attr_reader :execution_mode
7
6
 
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
7
+ %w[immediate fake real].each do |test_mode|
8
+ define_method(test_mode) do |&task|
9
+ @execution_mode = test_mode
10
+ result = task.call
11
+ @execution_mode = :real
13
12
 
14
- result
15
- end
13
+ result
14
+ end
16
15
 
17
- define_method("#{exec_method}?") do
18
- execution_mode == exec_method
19
- end
16
+ define_method("#{test_mode}!") do
17
+ @execution_mode = test_mode
20
18
  end
21
19
 
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
20
+ define_method("#{test_mode}?") do
21
+ execution_mode == test_mode
32
22
  end
23
+ end
24
+ end
33
25
 
34
- ConcurrentRails::Promises.extend(TestingFuture)
26
+ module TestingFuture
27
+ def future(*args, &task)
28
+ if ConcurrentRails::Testing.immediate?
29
+ future_on(:immediate, *args, &task)
30
+ elsif ConcurrentRails::Testing.fake?
31
+ yield
32
+ else
33
+ super
34
+ end
35
35
  end
36
36
  end
37
+
38
+ ConcurrentRails::Promises.extend(TestingFuture)
37
39
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ConcurrentRails
4
- VERSION = '0.2.1'
4
+ VERSION = '0.3.0'
5
5
  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.1
4
+ version: 0.3.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: 2021-06-15 00:00:00.000000000 Z
11
+ date: 2021-11-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '5.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest-reporters
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.4'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.4'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rubocop
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -66,20 +80,6 @@ dependencies:
66
80
  - - ">="
67
81
  - !ruby/object:Gem::Version
68
82
  version: '1.10'
69
- - !ruby/object:Gem::Dependency
70
- name: sqlite3
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: '1.4'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '1.4'
83
83
  description: Small library to make concurrent-ruby and Rails play nice together
84
84
  email:
85
85
  - luizeduardokowalski@gmail.com
@@ -91,9 +91,9 @@ files:
91
91
  - README.md
92
92
  - Rakefile
93
93
  - lib/concurrent_rails.rb
94
- - lib/concurrent_rails/adapters/delay.rb
95
- - lib/concurrent_rails/adapters/future.rb
94
+ - lib/concurrent_rails/delay_adapter.rb
96
95
  - lib/concurrent_rails/future.rb
96
+ - lib/concurrent_rails/future_adapter.rb
97
97
  - lib/concurrent_rails/multi.rb
98
98
  - lib/concurrent_rails/promises.rb
99
99
  - lib/concurrent_rails/railtie.rb
@@ -121,7 +121,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
121
121
  - !ruby/object:Gem::Version
122
122
  version: '0'
123
123
  requirements: []
124
- rubygems_version: 3.2.20
124
+ rubygems_version: 3.2.31
125
125
  signing_key:
126
126
  specification_version: 4
127
127
  summary: Multithread is hard
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ConcurrentRails::Adapters
4
- module Delay
5
- extend ActiveSupport::Concern
6
-
7
- class_methods do
8
- def delay(*args, &task)
9
- delay_on(:io, *args, &task)
10
- end
11
-
12
- def delay_on(executor, *args, &task)
13
- new(executor).delay_on_rails(*args, &task)
14
- end
15
- end
16
-
17
- def delay_on_rails(*args, &task)
18
- @instance = rails_wrapped { delay_on(executor, *args, &task) }
19
-
20
- self
21
- end
22
- end
23
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ConcurrentRails::Adapters
4
- module Future
5
- extend ActiveSupport::Concern
6
-
7
- class_methods do
8
- def future(*args, &task)
9
- future_on(:io, *args, &task)
10
- end
11
-
12
- def future_on(executor, *args, &task)
13
- new(executor).future_on_rails(*args, &task)
14
- end
15
- end
16
-
17
- def future_on_rails(*args, &task)
18
- @instance = rails_wrapped { future_on(executor, *args, &task) }
19
-
20
- self
21
- end
22
- end
23
- end