abstractivator 0.5.0 → 0.6.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
  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