concurrent_rails 0.2.1 → 0.3.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: 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