abstractivator 0.5.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7a894768eba016b4434a8b631edd7206ddf500ab
4
- data.tar.gz: b046f6c118d727a4e1d016147a77cb7e5e53fb81
3
+ metadata.gz: 58e09417a37a0401715ae76dd45139fe0b5cba42
4
+ data.tar.gz: f7d97a9ff2a8b887022ab5bf80f05f35f7c2f97a
5
5
  SHA512:
6
- metadata.gz: 2fe54299e0af9aa942fb870e40dffcf6543b4910a6d060048334f21aa69ed555d727390ae24cfa2097bbe3e8dc5d001ce2988db9379af64927e68b7be5e11723
7
- data.tar.gz: e9b6affbe39f825295dc91a3f3e3f1cc6440f92ac69b34d3803c358dfa8050dd8c7a3a1343f0b53a8a73242844e563ab2ad9b393406b6fc1631ae75adc740e2d
6
+ metadata.gz: 0982af424cdd32e91469cadbbb54de940b9872c09de0a2170af1896fe856d5474d4e59ee402a47005e1e11223d5b70764292712c301fe2672f8b4a7693208ddb
7
+ data.tar.gz: 8baad053d8a4dcec57c37ebb2ecc50bb8d1179402ac105989160fda70cf6fcfc8ac18012a4ddfed68397c87bb1a7e07272e9e960ba89f17da2db3a81bebd9c32
@@ -7,17 +7,92 @@ rescue => e
7
7
  end
8
8
 
9
9
  module Abstractivator
10
+
11
+ # Provides a pair of functions for handling long-running requests in a thread pool.
12
+ # Uses fibers to maintain a somewhat normal coding style (hides explicit continuations).
13
+ # with_fiber_defer defines the lexical scope of all work being done.
14
+ # fiber_defer defines the portion of the work to be done in the worker thread.
15
+ # Control passes from the calling thread, to the worker thread, and back to the calling thread.
16
+ # The code is capable of propagating thread variables (e.g., Mongoid::Threaded.database_override)
17
+ # across these thread/fiber transitions.
18
+ # See EventMachine::defer for more information.
10
19
  module FiberDefer
11
20
  ROOT_FIBER = Fiber.current
12
21
 
13
- def with_fiber_defer(&block)
22
+ def with_fiber_defer(thread_var_guard=nil, &block)
14
23
  raise 'this method requires an eventmachine reactor to be running' unless EM.reactor_running?
15
- Fiber.new{block.call}.resume if block
24
+ raise 'with_fiber_defer cannot be nested within with_fiber_defer' if Thread.current[:fiber_defer_guard_proc]
25
+ raise 'with_fiber_defer cannot be nested within fiber_defer' if Thread.current[:inside_fiber_defer]
26
+ return unless block
27
+ guard_proc = make_guard_proc(thread_var_guard)
28
+ f = Fiber.new do
29
+ guard_proc.call
30
+ begin
31
+ Thread.current[:fiber_defer_guard_proc] = guard_proc # make available to fiber_defer calls
32
+ block.call
33
+ ensure
34
+ Thread.current[:fiber_defer_guard_proc] = nil
35
+ end
36
+ end
37
+ f.resume
16
38
  end
17
39
 
18
- def fiber_defer(&action)
19
- f = Fiber.current
40
+ def fiber_defer(thread_var_guard=nil, &action)
41
+ # we start out in the caller thread
42
+ inherited_guard_proc = Thread.current[:fiber_defer_guard_proc]
43
+ raise 'fiber_defer cannot be nested within fiber_defer' if Thread.current[:inside_fiber_defer]
44
+ raise 'fiber_defer must be called within a with_fiber_defer block' unless inherited_guard_proc
20
45
  raise 'fiber_defer must be passed an action to defer (the block)' unless action
46
+ local_guard_proc = make_guard_proc(thread_var_guard)
47
+ guard_proc = proc do
48
+ inherited_guard_proc.call
49
+ local_guard_proc.call
50
+ end
51
+ begin
52
+ basic_fiber_defer do
53
+ # in the background thread now
54
+ begin
55
+ Thread.current[:inside_fiber_defer] = true
56
+ guard_proc.call
57
+ action.call
58
+ ensure
59
+ Thread.current[:inside_fiber_defer] = false
60
+ end
61
+ end
62
+ ensure
63
+ # back in the caller thread
64
+ guard_proc.call
65
+ end
66
+ end
67
+
68
+ def mongoid_fiber_defer(&action)
69
+ thread_vars = {
70
+ Mongoid::Threaded.database_override => proc { |db| Mongoid.override_database(db) }
71
+ }
72
+ fiber_defer(thread_vars, &action)
73
+ end
74
+
75
+ private
76
+
77
+ def make_guard_proc(x)
78
+ case x
79
+ when Proc
80
+ x
81
+ when Hash
82
+ proc do
83
+ x.each do |value, setter|
84
+ setter.call(value)
85
+ end
86
+ end
87
+ when nil
88
+ proc { }
89
+ else
90
+ raise "Cannot turn #{x.inspect} into a guard proc"
91
+ end
92
+ end
93
+
94
+ def basic_fiber_defer(&action)
95
+ f = Fiber.current
21
96
  raise 'fiber_defer must be called within a with_fiber_defer block' if f == ROOT_FIBER
22
97
 
23
98
  safe_action = proc do
@@ -33,18 +108,5 @@ module Abstractivator
33
108
  raise error if error
34
109
  result
35
110
  end
36
-
37
- def mongoid_fiber_defer(&action)
38
- db = Mongoid::Threaded.database_override
39
- begin
40
- fiber_defer do
41
- # in the background thread
42
- Mongoid.override_database(db) # set the db to what it was in the main thread
43
- action.call
44
- end
45
- ensure
46
- Mongoid.override_database(db) # main thread has moved on before we resume here. restore the db override.
47
- end
48
- end
49
111
  end
50
112
  end
@@ -1,3 +1,3 @@
1
1
  module Abstractivator
2
- VERSION = '0.5.0'
2
+ VERSION = '0.6.0'
3
3
  end
@@ -7,38 +7,90 @@ describe Abstractivator::FiberDefer do
7
7
  include Abstractivator::FiberDefer
8
8
 
9
9
  describe '#with_fiber_defer' do
10
- context 'when an eventmachine reactor is not running' do
11
- it 'raises an error' do
12
- expect{with_fiber_defer}.to raise_error /reactor/
10
+ it 'raises an error when an eventmachine reactor is not running' do
11
+ expect{with_fiber_defer}.to raise_error /reactor/
12
+ end
13
+ it 'does nothing when no block is provided' do
14
+ EM.run do
15
+ with_fiber_defer
16
+ EM.stop
17
+ end
18
+ end
19
+ it 'calls the block' do
20
+ EM.run do
21
+ expect{|b| with_fiber_defer(&b)}.to yield_control
22
+ EM.stop
13
23
  end
14
24
  end
15
- context 'when an eventmachine reactor is running' do
16
- it 'calls the block' do
25
+ context 'when a proc guard is provided' do
26
+ it 'invokes the guard upon entering the block' do
27
+ called_it = false
28
+ guard = proc { called_it = true }
17
29
  EM.run do
18
- expect{|b| with_fiber_defer(&b)}.to yield_control
19
- EM.stop
30
+ with_fiber_defer(guard) do
31
+ expect(called_it).to be true
32
+ EM.stop
33
+ end
20
34
  end
21
35
  end
22
- context 'when no block is provided' do
23
- it 'does nothing' do
24
- EM.run do
25
- with_fiber_defer
36
+ end
37
+ context 'when a hash guard is provided' do
38
+ before(:each) { Thread.current[:meaning] = 42 }
39
+ after(:each) { Thread.current[:meaning] = nil }
40
+ it 'propagates the thread/fiber-local variables into the block' do
41
+ EM.run do
42
+ guard = {
43
+ Thread.current[:meaning] => proc { |x| Thread.current[:meaning] = x }
44
+ }
45
+ with_fiber_defer(guard) do
46
+ expect(Thread.current[:meaning]).to eql 42
26
47
  EM.stop
27
48
  end
28
49
  end
29
50
  end
30
51
  end
52
+ context 'when an invalid guard is provided' do
53
+ it 'raises an error' do
54
+ EM.run do
55
+ expect{with_fiber_defer(3){ }}.to raise_error /guard/
56
+ EM.stop
57
+ end
58
+ end
59
+ end
60
+ it 'cannot be nested in with_fiber_defer' do
61
+ EM.run do
62
+ with_fiber_defer do
63
+ expect{with_fiber_defer{}}.to raise_error /nested/
64
+ EM.stop
65
+ end
66
+ end
67
+ end
68
+ it 'cannot be nested in fiber_defer' do
69
+ EM.run do
70
+ with_fiber_defer do
71
+ fiber_defer do
72
+ expect{with_fiber_defer{}}.to raise_error /nested/
73
+ end
74
+ EM.stop
75
+ end
76
+ end
77
+ end
31
78
  end
32
79
 
33
80
  describe '#fiber_defer' do
34
- context 'when it is called outside a with_fiber_defer block' do
81
+ context 'when called outside a with_fiber_defer block' do
35
82
  it 'raises an error' do
36
83
  expect{fiber_defer{}}.to raise_error /with_fiber_defer/
37
84
  end
38
85
  end
39
86
  context 'when it is not passed a block' do
40
87
  it 'raises an error' do
41
- expect{fiber_defer}.to raise_error /must be passed an action/
88
+ EM.run do
89
+ with_fiber_defer do
90
+ expect{fiber_defer}.to raise_error /must be passed an action/
91
+ EM.stop
92
+ end
93
+ end
42
94
  end
43
95
  end
44
96
  it 'executes the block on a background thread' do
@@ -95,6 +147,108 @@ describe Abstractivator::FiberDefer do
95
147
  end
96
148
  end
97
149
  end
150
+ context 'when a proc guard is provided' do
151
+ it 'invokes the guard upon entering and exiting the block' do
152
+ called_count = 0
153
+ guard = proc { called_count += 1 }
154
+ EM.run do
155
+ with_fiber_defer do
156
+ fiber_defer(guard) do
157
+ expect(called_count).to eql 1
158
+ end
159
+ expect(called_count).to eql 2
160
+ EM.stop
161
+ end
162
+ end
163
+ end
164
+ context 'and an inherited guard is provided' do
165
+ it 'invokes the inherited guard and then the local guard upon entering and exiting the block' do
166
+ the_log = []
167
+ guard1 = proc { the_log << 'guard1' }
168
+ guard2 = proc { the_log << 'guard2' }
169
+ EM.run do
170
+ with_fiber_defer(guard1) do
171
+ expect(the_log).to eql %w(guard1)
172
+ fiber_defer(guard2) do
173
+ expect(the_log).to eql %w(guard1 guard1 guard2)
174
+ end
175
+ expect(the_log).to eql %w(guard1 guard1 guard2 guard1 guard2)
176
+ EM.stop
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
182
+ context 'when a hash guard is provided' do
183
+ before(:each) do
184
+ Thread.current[:a] = 1
185
+ Thread.current[:b] = 2
186
+ end
187
+ after(:each) do
188
+ Thread.current[:a] = nil
189
+ Thread.current[:b] = nil
190
+ end
191
+ it 'propagates the thread/fiber-local variables into the block' do
192
+ EM.run do
193
+ with_fiber_defer do
194
+ Thread.current[:a] = 42
195
+ guard = { Thread.current[:a] => proc {|x| Thread.current[:a] = x} }
196
+ fiber_defer(guard) do
197
+ expect(Thread.current[:a]).to eql 42
198
+ end
199
+ expect(Thread.current[:a]).to eql 42
200
+ EM.stop
201
+ end
202
+ end
203
+ end
204
+ context 'and an inherited guard is provided' do
205
+ it 'applies the inherited guard and then the local guard upon entering and exiting the block' do
206
+ EM.run do
207
+ guard1 = {
208
+ Thread.current[:a] => proc {|x| Thread.current[:a] = x},
209
+ Thread.current[:b] => proc {|x| Thread.current[:b] = x}
210
+ }
211
+ with_fiber_defer(guard1) do
212
+ Thread.current[:b] = 22
213
+ Thread.current[:c] = 33
214
+ guard2 = {
215
+ Thread.current[:b] => proc {|x| Thread.current[:b] = x},
216
+ Thread.current[:c] => proc {|x| Thread.current[:c] = x}
217
+ }
218
+ fiber_defer(guard2) do
219
+ expect(Thread.current[:a]).to eql 1
220
+ expect(Thread.current[:b]).to eql 22
221
+ expect(Thread.current[:c]).to eql 33
222
+ end
223
+ expect(Thread.current[:a]).to eql 1
224
+ expect(Thread.current[:b]).to eql 22
225
+ expect(Thread.current[:c]).to eql 33
226
+ EM.stop
227
+ end
228
+ end
229
+ end
230
+ end
231
+ end
232
+ context 'when an invalid guard is provided' do
233
+ it 'raises an error' do
234
+ EM.run do
235
+ with_fiber_defer do
236
+ expect{fiber_defer(3){ }}.to raise_error /guard/
237
+ end
238
+ EM.stop
239
+ end
240
+ end
241
+ end
242
+ it 'cannot be nested in fiber_defer' do
243
+ EM.run do
244
+ with_fiber_defer do
245
+ fiber_defer do
246
+ expect{fiber_defer{}}.to raise_error /nested/
247
+ end
248
+ EM.stop
249
+ end
250
+ end
251
+ end
98
252
  end
99
253
 
100
254
  describe '#mongoid_fiber_defer' do
@@ -130,4 +284,5 @@ describe Abstractivator::FiberDefer do
130
284
  end
131
285
  end
132
286
  end
287
+
133
288
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abstractivator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Winton
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-03-30 00:00:00.000000000 Z
11
+ date: 2016-04-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler