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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 14fd5aa53c8cb75c74999acd7e1c43410a9564ba4122d83142f0e3ed8565d429
4
- data.tar.gz: 71e60b4e355052fcc328ada6b881b9e41330360aaaa582eff9afb90cab19866f
3
+ metadata.gz: a0352c1516c930c65708383fa734cd7444b02b0a213c0ef1f794e5e31f8e1fdf
4
+ data.tar.gz: 33ec089c95b33ff31a4652a073feac356e8b23a8d7e30bd214ea0a0dbc4aedcb
5
5
  SHA512:
6
- metadata.gz: 020b3dee7781090195212145f6faf7f19c311cdee6b619e0601dde476fd3dd2dbe811269da5362794d5903f56ca95eaf56e615dcadb55db454d2a705b6599837
7
- data.tar.gz: 9abb0931cb4283b4c9947d7498784c3d0ca0c32e70fb0fbaff9aa29ad28cde0c97ba48bde5f2d07837902cfcb912f987f449b9825658f47f93a7ef1f48bacf32
6
+ metadata.gz: a486a3ab777574962127795db050fd2615b69594d0bc88adb5114368c7d90492f2b51b237817f97118a31969396d53171bf66371240577051977a052d935148a
7
+ data.tar.gz: db095fe9e33e527a3ade62e8a40aec1866718bfce6b55ef519775cd1761d78b1eb1868d8c8b0ab799dcd05f78980ba16e31f5a7912b87ab3e2dd49d917dab004
data/README.md CHANGED
@@ -2,8 +2,7 @@
2
2
 
3
3
  ![status](https://github.com/luizkowalski/concurrent_rails/actions/workflows/ruby.yml/badge.svg?branch=master)
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 separated 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:
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.0'
218
+ gem 'concurrent_rails', '~> 0.2.1'
189
219
  ```
190
220
 
191
221
  And then execute:
192
222
 
193
223
  ```bash
194
- $ bundle
224
+ bundle
195
225
  ```
196
226
 
197
227
  Or install it yourself as:
198
228
 
199
229
  ```bash
200
- $ gem install concurrent_rails
230
+ gem install concurrent_rails
201
231
  ```
202
232
 
203
233
  ## Contributing
@@ -4,4 +4,5 @@ require 'concurrent_rails/future'
4
4
  require 'concurrent_rails/multi'
5
5
  require 'concurrent_rails/promises'
6
6
  require 'concurrent_rails/railtie'
7
+ require 'concurrent_rails/testing'
7
8
  require 'concurrent_rails/version'
@@ -19,7 +19,5 @@ module ConcurrentRails::Adapters
19
19
 
20
20
  self
21
21
  end
22
-
23
- delegate :touch, to: :instance
24
22
  end
25
23
  end
@@ -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
- # ActiveSupport::Deprecation.warn('Concurrent::Future is deprecated')
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
- rails_wrapped do
20
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
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? { |action| action.is_a?(Proc) }
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?, :wait,
54
- to: :instance
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 :executor, :instance
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ConcurrentRails
4
- VERSION = '0.2.0'
4
+ VERSION = '0.2.1'
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.0
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-05-13 00:00:00.000000000 Z
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.17
124
+ rubygems_version: 3.2.20
124
125
  signing_key:
125
126
  specification_version: 4
126
127
  summary: Multithread is hard