mongoid-bolt 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,45 @@
1
+ NAME
2
+ ----
3
+ mongoid-bolt
4
+
5
+ INSTALL
6
+ -------
7
+ gem install mongoid-bolt
8
+
9
+ SYNOPSIS
10
+ --------
11
+
12
+ ````ruby
13
+
14
+ require 'mongoid-bolt'
15
+
16
+ Bolt.for(:shared_resource) do
17
+ ioslated!
18
+ end
19
+
20
+
21
+ class A
22
+ include Mongoid::Document
23
+ include Mongoid::Bolt
24
+ end
25
+
26
+ a = A.new
27
+
28
+ a.lock!
29
+
30
+ a.unlock!
31
+
32
+ a.lock do
33
+ isolated!
34
+ end
35
+
36
+
37
+
38
+ ````
39
+
40
+ DESCRIPTION
41
+ -----------
42
+
43
+ mongoid-bolt is a concrete lock implementation and mixin.
44
+
45
+ it is process safe and atomic in a mongoid cluster.
data/Rakefile ADDED
@@ -0,0 +1,446 @@
1
+ This.name =
2
+ "Mongoid::Bolt"
3
+
4
+ This.synopsis =
5
+ "a mongoid 3/moped compatible lock implementation and mixin"
6
+
7
+ This.rubyforge_project = 'codeforpeople'
8
+ This.author = "Ara T. Howard"
9
+ This.email = "ara.t.howard@gmail.com"
10
+ This.homepage = "https://github.com/ahoward/#{ This.lib }"
11
+
12
+ This.setup!
13
+
14
+
15
+
16
+ task :default do
17
+ puts((Rake::Task.tasks.map{|task| task.name.gsub(/::/,':')} - ['default']).sort)
18
+ end
19
+
20
+ task :test do
21
+ This.run_tests!
22
+ end
23
+
24
+ namespace :test do
25
+ task(:unit){ This.run_tests!(:unit) }
26
+ task(:functional){ This.run_tests!(:functional) }
27
+ task(:integration){ This.run_tests!(:integration) }
28
+ end
29
+
30
+ def This.run_tests!(which = nil)
31
+ which ||= '**'
32
+ test_dir = File.join(This.dir, "test")
33
+ test_glob ||= File.join(test_dir, "#{ which }/**_test.rb")
34
+ test_rbs = Dir.glob(test_glob).sort
35
+
36
+ div = ('=' * 119)
37
+ line = ('-' * 119)
38
+
39
+ test_rbs.each_with_index do |test_rb, index|
40
+ testno = index + 1
41
+ command = "#{ File.basename(This.ruby) } -I ./lib -I ./test/lib #{ test_rb }"
42
+
43
+ puts
44
+ This.say(div, :color => :cyan, :bold => true)
45
+ This.say("@#{ testno } => ", :bold => true, :method => :print)
46
+ This.say(command, :color => :cyan, :bold => true)
47
+ This.say(line, :color => :cyan, :bold => true)
48
+
49
+ system(command)
50
+
51
+ This.say(line, :color => :cyan, :bold => true)
52
+
53
+ status = $?.exitstatus
54
+
55
+ if status.zero?
56
+ This.say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print)
57
+ This.say("SUCCESS", :color => :green, :bold => true)
58
+ else
59
+ This.say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print)
60
+ This.say("FAILURE", :color => :red, :bold => true)
61
+ end
62
+ This.say(line, :color => :cyan, :bold => true)
63
+
64
+ exit(status) unless status.zero?
65
+ end
66
+ end
67
+
68
+
69
+ task :gemspec do
70
+ ignore_extensions = ['git', 'svn', 'tmp', /sw./, 'bak', 'gem']
71
+ ignore_directories = ['pkg', 'db']
72
+ ignore_files = ['test/log', 'test/db.yml', 'a.rb', 'b.rb'] + Dir['db/*'] + %w'db'
73
+
74
+ shiteless =
75
+ lambda do |list|
76
+ list.delete_if do |entry|
77
+ next unless test(?e, entry)
78
+ extension = File.basename(entry).split(%r/[.]/).last
79
+ ignore_extensions.any?{|ext| ext === extension}
80
+ end
81
+ list.delete_if do |entry|
82
+ next unless test(?d, entry)
83
+ dirname = File.expand_path(entry)
84
+ ignore_directories.any?{|dir| File.expand_path(dir) == dirname}
85
+ end
86
+ list.delete_if do |entry|
87
+ next unless test(?f, entry)
88
+ filename = File.expand_path(entry)
89
+ ignore_files.any?{|file| File.expand_path(file) == filename}
90
+ end
91
+ end
92
+
93
+ lib = This.lib
94
+ object = This.object
95
+ version = This.version
96
+ files = shiteless[Dir::glob("**/**")]
97
+ executables = shiteless[Dir::glob("bin/*")].map{|exe| File.basename(exe)}
98
+ #has_rdoc = true #File.exist?('doc')
99
+ test_files = test(?e, "test/#{ lib }.rb") ? "test/#{ lib }.rb" : nil
100
+ summary = This.summary || This.synopsis || "#{ lib } kicks the ass"
101
+ description = This.description || summary
102
+
103
+ if This.extensions.nil?
104
+ This.extensions = []
105
+ extensions = This.extensions
106
+ %w( Makefile configure extconf.rb ).each do |ext|
107
+ extensions << ext if File.exists?(ext)
108
+ end
109
+ end
110
+ extensions = [extensions].flatten.compact
111
+
112
+ # TODO
113
+ if This.dependencies.nil?
114
+ dependencies = []
115
+ else
116
+ case This.dependencies
117
+ when Hash
118
+ dependencies = This.dependencies.values
119
+ when Array
120
+ dependencies = This.dependencies
121
+ end
122
+ end
123
+
124
+ template =
125
+ if test(?e, 'gemspec.erb')
126
+ This.template_for{ IO.read('gemspec.erb') }
127
+ else
128
+ This.template_for {
129
+ <<-__
130
+ ## <%= lib %>.gemspec
131
+ #
132
+
133
+ Gem::Specification::new do |spec|
134
+ spec.name = <%= lib.inspect %>
135
+ spec.version = <%= version.inspect %>
136
+ spec.platform = Gem::Platform::RUBY
137
+ spec.summary = <%= lib.inspect %>
138
+ spec.description = <%= description.inspect %>
139
+
140
+ spec.files =\n<%= files.sort.pretty_inspect %>
141
+ spec.executables = <%= executables.inspect %>
142
+
143
+ spec.require_path = "lib"
144
+
145
+ spec.test_files = <%= test_files.inspect %>
146
+
147
+ <% dependencies.each do |lib_version| %>
148
+ spec.add_dependency(*<%= Array(lib_version).flatten.inspect %>)
149
+ <% end %>
150
+
151
+ spec.extensions.push(*<%= extensions.inspect %>)
152
+
153
+ spec.rubyforge_project = <%= This.rubyforge_project.inspect %>
154
+ spec.author = <%= This.author.inspect %>
155
+ spec.email = <%= This.email.inspect %>
156
+ spec.homepage = <%= This.homepage.inspect %>
157
+ end
158
+ __
159
+ }
160
+ end
161
+
162
+ FileUtils.mkdir_p(This.pkgdir)
163
+ gemspec = "#{ lib }.gemspec"
164
+ open(gemspec, "w"){|fd| fd.puts(template)}
165
+ This.gemspec = gemspec
166
+ end
167
+
168
+ task :gem => [:clean, :gemspec] do
169
+ FileUtils.mkdir_p(This.pkgdir)
170
+ before = Dir['*.gem']
171
+ cmd = "gem build #{ This.gemspec }"
172
+ `#{ cmd }`
173
+ after = Dir['*.gem']
174
+ gem = ((after - before).first || after.first) or abort('no gem!')
175
+ FileUtils.mv(gem, This.pkgdir)
176
+ This.gem = File.join(This.pkgdir, File.basename(gem))
177
+ end
178
+
179
+ task :readme do
180
+ samples = ''
181
+ prompt = '~ > '
182
+ lib = This.lib
183
+ version = This.version
184
+
185
+ Dir['sample*/*'].sort.each do |sample|
186
+ samples << "\n" << " <========< #{ sample } >========>" << "\n\n"
187
+
188
+ cmd = "cat #{ sample }"
189
+ samples << This.util.indent(prompt + cmd, 2) << "\n\n"
190
+ samples << This.util.indent(`#{ cmd }`, 4) << "\n"
191
+
192
+ cmd = "ruby #{ sample }"
193
+ samples << This.util.indent(prompt + cmd, 2) << "\n\n"
194
+
195
+ cmd = "ruby -e'STDOUT.sync=true; exec %(ruby -I ./lib #{ sample })'"
196
+ samples << This.util.indent(`#{ cmd } 2>&1`, 4) << "\n"
197
+ end
198
+
199
+ template =
200
+ if test(?e, 'readme.erb')
201
+ This.template_for{ IO.read('readme.erb') }
202
+ else
203
+ This.template_for {
204
+ <<-__
205
+ NAME
206
+ #{ lib }
207
+
208
+ DESCRIPTION
209
+
210
+ INSTALL
211
+ gem install #{ lib }
212
+
213
+ SAMPLES
214
+ #{ samples }
215
+ __
216
+ }
217
+ end
218
+
219
+ open("README", "w"){|fd| fd.puts template}
220
+ end
221
+
222
+
223
+ task :clean do
224
+ Dir[File.join(This.pkgdir, '**/**')].each{|entry| FileUtils.rm_rf(entry)}
225
+ end
226
+
227
+
228
+ task :release => [:clean, :gemspec, :gem] do
229
+ gems = Dir[File.join(This.pkgdir, '*.gem')].flatten
230
+ raise "which one? : #{ gems.inspect }" if gems.size > 1
231
+ raise "no gems?" if gems.size < 1
232
+
233
+ cmd = "gem push #{ This.gem }"
234
+ puts cmd
235
+ puts
236
+ system(cmd)
237
+ abort("cmd(#{ cmd }) failed with (#{ $?.inspect })") unless $?.exitstatus.zero?
238
+
239
+ #cmd = "rubyforge login && rubyforge add_release #{ This.rubyforge_project } #{ This.lib } #{ This.version } #{ This.gem }"
240
+ #puts cmd
241
+ #puts
242
+ #system(cmd)
243
+ #abort("cmd(#{ cmd }) failed with (#{ $?.inspect })") unless $?.exitstatus.zero?
244
+ end
245
+
246
+
247
+
248
+
249
+
250
+ BEGIN {
251
+ # support for this rakefile
252
+ #
253
+ $VERBOSE = nil
254
+
255
+ require 'erb'
256
+ require 'fileutils'
257
+ require 'rbconfig'
258
+ require 'pp'
259
+
260
+ # cache a bunch of stuff about this rakefile/environment
261
+ #
262
+
263
+ This =
264
+ Class.new(Hash) do
265
+
266
+ def method_missing(method, *args, &block)
267
+ if method.to_s =~ /=/
268
+ key = method.to_s.chomp('=')
269
+ value = block ? block : args.shift
270
+ self[key] = value
271
+ else
272
+ key = method.to_s
273
+ if block
274
+ value = block
275
+ self[key] = value
276
+ else
277
+ value = self[key]
278
+
279
+ if value.respond_to?(:call)
280
+ self[key] = value.call()
281
+ else
282
+ value
283
+ end
284
+ end
285
+ end
286
+ end
287
+
288
+ def inspect
289
+ expand!
290
+ PP.pp(self, '')
291
+ end
292
+
293
+ def expand!
294
+ keys.each do |key|
295
+ value = self[key]
296
+ if value.respond_to?(:call)
297
+ self[key] = value.call()
298
+ end
299
+ end
300
+ end
301
+
302
+ end.new()
303
+
304
+ This.file = File.expand_path(__FILE__)
305
+ This.dir = File.dirname(This.file)
306
+ This.pkgdir = File.join(This.dir, 'pkg')
307
+
308
+ # defaults
309
+ #
310
+ This.lib do
311
+ File.basename(Dir.pwd)
312
+ end
313
+
314
+ def This.setup!
315
+ begin
316
+ require "./lib/#{ This.lib }"
317
+ rescue LoadError
318
+ abort("could not load #{ This.lib }")
319
+ end
320
+ end
321
+
322
+ This.name do
323
+ This.name = This.lib.capitalize
324
+ end
325
+
326
+ This.object do
327
+ begin
328
+ This.object = eval(This.name)
329
+ rescue Object
330
+ abort("could not determine object from #{ This.name }")
331
+ end
332
+ end
333
+
334
+ This.version do
335
+ This.object.send(:version)
336
+ end
337
+
338
+ This.dependencies do
339
+ if This.object.respond_to?(:dependencies)
340
+ This.object.dependencies
341
+ end
342
+ end
343
+
344
+ This.ruby do
345
+ c = Config::CONFIG
346
+ bindir = c["bindir"] || c['BINDIR']
347
+ ruby_install_name = c['ruby_install_name'] || c['RUBY_INSTALL_NAME'] || 'ruby'
348
+ ruby_ext = c['EXEEXT'] || ''
349
+ File.join(bindir, (ruby_install_name + ruby_ext))
350
+ end
351
+
352
+ # some utils
353
+ #
354
+ This.util = Module.new do
355
+ def indent(s, n = 2)
356
+ s = unindent(s)
357
+ ws = ' ' * n
358
+ s.gsub(%r/^/, ws)
359
+ end
360
+
361
+ def unindent(s)
362
+ indent = nil
363
+ s.each_line do |line|
364
+ next if line =~ %r/^\s*$/
365
+ indent = line[%r/^\s*/] and break
366
+ end
367
+ indent ? s.gsub(%r/^#{ indent }/, "") : s
368
+ end
369
+
370
+ extend self
371
+ end
372
+
373
+ # template support
374
+ #
375
+ This.template = Class.new do
376
+ def initialize(&block)
377
+ @block = block
378
+ @template = block.call.to_s
379
+ end
380
+
381
+ def expand(b=nil)
382
+ ERB.new(This.util.unindent(@template)).result((b||@block).binding)
383
+ end
384
+
385
+ alias_method 'to_s', 'expand'
386
+ end
387
+
388
+ def This.template_for(*args, &block)
389
+ This.template.new(*args, &block)
390
+ end
391
+
392
+ # colored console output support
393
+ #
394
+ This.ansi = {
395
+ :clear => "\e[0m",
396
+ :reset => "\e[0m",
397
+ :erase_line => "\e[K",
398
+ :erase_char => "\e[P",
399
+ :bold => "\e[1m",
400
+ :dark => "\e[2m",
401
+ :underline => "\e[4m",
402
+ :underscore => "\e[4m",
403
+ :blink => "\e[5m",
404
+ :reverse => "\e[7m",
405
+ :concealed => "\e[8m",
406
+ :black => "\e[30m",
407
+ :red => "\e[31m",
408
+ :green => "\e[32m",
409
+ :yellow => "\e[33m",
410
+ :blue => "\e[34m",
411
+ :magenta => "\e[35m",
412
+ :cyan => "\e[36m",
413
+ :white => "\e[37m",
414
+ :on_black => "\e[40m",
415
+ :on_red => "\e[41m",
416
+ :on_green => "\e[42m",
417
+ :on_yellow => "\e[43m",
418
+ :on_blue => "\e[44m",
419
+ :on_magenta => "\e[45m",
420
+ :on_cyan => "\e[46m",
421
+ :on_white => "\e[47m"
422
+ }
423
+
424
+ def This.say(something, *args)
425
+ options = args.last.is_a?(Hash) ? args.pop : {}
426
+ options[:color] = args.shift.to_s.to_sym unless args.empty?
427
+ keys = options.keys
428
+ keys.each{|key| options[key.to_s.to_sym] = options.delete(key)}
429
+
430
+ color = options[:color]
431
+ bold = options.has_key?(:bold)
432
+
433
+ parts = [something]
434
+ parts.unshift(This.ansi[color]) if color
435
+ parts.unshift(This.ansi[:bold]) if bold
436
+ parts.push(This.ansi[:clear]) if parts.size > 1
437
+
438
+ method = options[:method] || :puts
439
+
440
+ Kernel.send(method, parts.join)
441
+ end
442
+
443
+ # always run out of the project dir
444
+ #
445
+ Dir.chdir(This.dir)
446
+ }
@@ -0,0 +1,349 @@
1
+ # encoding: utf-8
2
+
3
+ ##
4
+ #
5
+ module Mongoid
6
+ class Bolt
7
+ const_set :Version, '1.0.0'
8
+
9
+ class << Bolt
10
+ def version
11
+ const_get :Version
12
+ end
13
+
14
+ def dependencies
15
+ {
16
+ 'mongoid' => [ 'mongoid' , ' >= 3.0.1' ] ,
17
+ }
18
+ end
19
+
20
+ def libdir(*args, &block)
21
+ @libdir ||= File.expand_path(__FILE__).sub(/\.rb$/,'')
22
+ args.empty? ? @libdir : File.join(@libdir, *args)
23
+ ensure
24
+ if block
25
+ begin
26
+ $LOAD_PATH.unshift(@libdir)
27
+ block.call()
28
+ ensure
29
+ $LOAD_PATH.shift()
30
+ end
31
+ end
32
+ end
33
+
34
+ def load(*libs)
35
+ libs = libs.join(' ').scan(/[^\s+]+/)
36
+ libdir{ libs.each{|lib| Kernel.load(lib) } }
37
+ end
38
+ end
39
+
40
+ begin
41
+ require 'rubygems'
42
+ rescue LoadError
43
+ nil
44
+ end
45
+
46
+ if defined?(gem)
47
+ dependencies.each do |lib, dependency|
48
+ gem(*dependency)
49
+ require(lib)
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ ##
56
+ #
57
+ module Mongoid
58
+ class Bolt
59
+ module Ability
60
+ Code = proc do
61
+ ## embedded lock class and associations
62
+ #
63
+ target_class = self
64
+
65
+ class << target_class
66
+ attr_accessor :lock_class
67
+ end
68
+
69
+ const_set(:Bolt, Class.new)
70
+ lock_class = const_get(:Bolt)
71
+
72
+ target_class.lock_class = lock_class
73
+
74
+ lock_class.class_eval do
75
+ define_method(:target_class){ target_class }
76
+ define_method(:lock_class){ lock_class }
77
+
78
+ include Mongoid::Document
79
+ include Mongoid::Timestamps
80
+
81
+ field(:hostname, :default => proc{ ::Mongoid::Bolt.hostname })
82
+ field(:ppid, :default => proc{ ::Mongoid::Bolt.ppid })
83
+ field(:pid, :default => proc{ ::Mongoid::Bolt.pid })
84
+
85
+ attr_accessor :stolen
86
+ alias_method :stolen?, :stolen
87
+
88
+ def initialize(*args, &block)
89
+ super
90
+ ensure
91
+ now = Time.now
92
+ self.created_at ||= now
93
+ self.updated_at ||= now
94
+ @locked = false
95
+ end
96
+
97
+ def localhost?
98
+ ::Mongoid::Bolt.hostname == hostname
99
+ end
100
+
101
+ def alive?
102
+ return true unless localhost?
103
+ ::Mongoid::Bolt.alive?(ppid, pid)
104
+ end
105
+
106
+ def relock!
107
+ reload
108
+
109
+ conditions = {
110
+ '_lock._id' => id,
111
+ '_lock.hostname' => hostname,
112
+ '_lock.ppid' => ppid,
113
+ '_lock.pid' => pid
114
+ }
115
+
116
+ update = {
117
+ '$set' => {
118
+ '_lock.hostname' => ::Mongoid::Bolt.hostname,
119
+ '_lock.ppid' => ::Mongoid::Bolt.ppid,
120
+ '_lock.pid' => ::Mongoid::Bolt.pid,
121
+ '_lock.updated_at' => Time.now.utc
122
+ }
123
+ }
124
+
125
+ result =
126
+ target_class.
127
+ with(safe: true).
128
+ where(conditions).
129
+ find_and_modify(update, new: false)
130
+
131
+ ensure
132
+ reload
133
+ end
134
+
135
+ def steal!
136
+ self.stolen = !!relock!
137
+ end
138
+
139
+ def stale?
140
+ localhost? and not alive?
141
+ end
142
+
143
+ def owner?
144
+ ::Mongoid::Bolt.identifier == identifier
145
+ end
146
+
147
+ def identifier
148
+ {:hostname => hostname, :ppid => ppid, :pid => pid}
149
+ end
150
+ end
151
+
152
+ target_association_name = "_" + target_class.name.underscore.split(%r{/}).last
153
+
154
+ lock_class.class_eval do
155
+ embedded_in(target_association_name, :class_name => "::#{ target_class.name }")
156
+ end
157
+
158
+ embeds_one(:_lock, :class_name => "::#{ lock_class.name }")
159
+
160
+ ## locking methods
161
+ #
162
+ def target_class.lock!(conditions = {}, update = {})
163
+ conditions.to_options!
164
+ update.to_options!
165
+
166
+ conditions[:_lock] = nil
167
+
168
+ update[:$set] = {:_lock => lock_class.new.attributes}
169
+
170
+ with(safe: true).
171
+ where(conditions).
172
+ find_and_modify(update, new: true)
173
+ end
174
+
175
+ def lock!(conditions = {})
176
+ conditions.to_options!
177
+
178
+ begin
179
+ if _lock and _lock.stale? and _lock.steal!
180
+ return _lock
181
+ end
182
+ rescue
183
+ nil
184
+ end
185
+
186
+ conditions[:_id] = id
187
+
188
+ if self.class.lock!(conditions)
189
+ reload
190
+
191
+ begin
192
+ @locked = _lock && _lock.owner?
193
+ rescue
194
+ nil
195
+ end
196
+ else
197
+ false
198
+ end
199
+ end
200
+
201
+ def unlock!
202
+ unlocked = false
203
+
204
+ if _lock
205
+ begin
206
+ _lock.destroy if _lock.owner?
207
+ @locked = false
208
+ unlocked = true
209
+ rescue
210
+ nil
211
+ end
212
+
213
+ reload
214
+ end
215
+
216
+ unlocked
217
+ end
218
+
219
+ def relock!
220
+ raise(::Mongoid::Bolt::Error, "#{ name } is not locked!") unless @locked
221
+
222
+ _lock.relock!
223
+ end
224
+
225
+ def locked?
226
+ begin
227
+ _lock and _lock.owner?
228
+ rescue
229
+ nil
230
+ end
231
+ end
232
+
233
+ def lock(options = {}, &block)
234
+ options.to_options!
235
+
236
+ return block.call(_lock) if locked?
237
+
238
+ loop do
239
+ if lock!
240
+ return _lock unless block
241
+
242
+ begin
243
+ return block.call(_lock)
244
+ ensure
245
+ unlock!
246
+ end
247
+ else
248
+ if options[:blocking] == false
249
+ if block
250
+ raise(::Mongoid::Bolt::Error, name)
251
+ else
252
+ return(false)
253
+ end
254
+ end
255
+
256
+ if options[:waiting]
257
+ options[:waiting].call(reload._lock)
258
+ end
259
+
260
+ sleep(rand)
261
+ end
262
+ end
263
+ end
264
+ end
265
+
266
+ def Ability.included(other)
267
+ super
268
+ ensure
269
+ other.module_eval(&Code)
270
+ end
271
+ end
272
+
273
+ def Bolt.ability
274
+ Ability
275
+ end
276
+
277
+ ##
278
+ #
279
+ include Mongoid::Document
280
+ include Mongoid::Timestamps
281
+
282
+ ##
283
+ #
284
+ class Error < ::StandardError; end
285
+
286
+ ##
287
+ #
288
+ def Bolt.for(name, options = {}, &block)
289
+ name = name.to_s
290
+ conditions = {:name => name}
291
+
292
+ attributes = conditions.dup
293
+ attributes[:created_at] || attributes[:updated_at] = Time.now.utc
294
+
295
+ lock =
296
+ begin
297
+ where(conditions).first or create!(attributes)
298
+ rescue Object => e
299
+ sleep(rand)
300
+ where(conditions).first or create!(attributes)
301
+ end
302
+
303
+ block ? lock.lock(options, &block) : lock
304
+ end
305
+
306
+ ##
307
+ #
308
+ field(:name)
309
+
310
+ validates_presence_of(:name)
311
+ validates_uniqueness_of(:name)
312
+
313
+ index({:name => 1}, {:unique => true})
314
+
315
+ ##
316
+ #
317
+ def Bolt.hostname
318
+ Socket.gethostname
319
+ end
320
+
321
+ def Bolt.ppid
322
+ Process.ppid
323
+ end
324
+
325
+ def Bolt.pid
326
+ Process.pid
327
+ end
328
+
329
+ def Bolt.identifier
330
+ {:hostname => hostname, :ppid => ppid, :pid => pid}
331
+ end
332
+
333
+ def Bolt.alive?(*pids)
334
+ pids.flatten.compact.all? do |pid|
335
+ begin
336
+ Process.kill(0, Integer(pid))
337
+ true
338
+ rescue Object
339
+ false
340
+ end
341
+ end
342
+ end
343
+
344
+ ##
345
+ #
346
+ include Bolt.ability
347
+ end
348
+ end
349
+
@@ -0,0 +1,38 @@
1
+ ## mongoid-bolt.gemspec
2
+ #
3
+
4
+ Gem::Specification::new do |spec|
5
+ spec.name = "mongoid-bolt"
6
+ spec.version = "1.0.0"
7
+ spec.platform = Gem::Platform::RUBY
8
+ spec.summary = "mongoid-bolt"
9
+ spec.description = "a mongoid 3/moped compatible lock implementation and mixin"
10
+
11
+ spec.files =
12
+ ["README.md",
13
+ "Rakefile",
14
+ "lib",
15
+ "lib/mongoid-bolt.rb",
16
+ "mongoid-bolt.gemspec",
17
+ "test",
18
+ "test/helper.rb",
19
+ "test/mongoid-bolt_test.rb",
20
+ "test/testing.rb"]
21
+
22
+ spec.executables = []
23
+
24
+ spec.require_path = "lib"
25
+
26
+ spec.test_files = nil
27
+
28
+
29
+ spec.add_dependency(*["mongoid", " >= 3.0.1"])
30
+
31
+
32
+ spec.extensions.push(*[])
33
+
34
+ spec.rubyforge_project = "codeforpeople"
35
+ spec.author = "Ara T. Howard"
36
+ spec.email = "ara.t.howard@gmail.com"
37
+ spec.homepage = "https://github.com/ahoward/mongoid-bolt"
38
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,7 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require_relative 'testing'
3
+ require_relative '../lib/mongoid-bolt.rb'
4
+
5
+ Mongoid.configure do |config|
6
+ config.connect_to('mongoid-bolt_test')
7
+ end
@@ -0,0 +1,70 @@
1
+ require_relative 'helper'
2
+
3
+ Testing Mongoid::Bolt do
4
+ ##
5
+ #
6
+ Bolt = Mongoid::Bolt
7
+
8
+ class A
9
+ include Mongoid::Document
10
+ include Bolt::Ability
11
+ end
12
+
13
+ setup do
14
+ Bolt.destroy_all
15
+ A.destroy_all
16
+ end
17
+
18
+ test 'that locks can be acquired by name' do
19
+ lock = assert{ Bolt.for(:shared_resource) }
20
+ end
21
+
22
+ test 'that locks can be locked' do
23
+ lock = assert{ Bolt.for(:shared_resource) }
24
+ assert{ lock.lock! }
25
+ end
26
+
27
+ test 'that locks cannot be locked twice' do
28
+ lock = assert{ Bolt.for(:shared_resource) }
29
+ assert{ lock.lock! }
30
+ assert{ !lock.lock! }
31
+ end
32
+
33
+ test 'that locks can be re-locked' do
34
+ lock = assert{ Bolt.for(:shared_resource) }
35
+ assert{ lock.lock! }
36
+ a = lock._lock.updated_at
37
+ sleep(0.042)
38
+ assert{ lock.relock! }
39
+ b = lock._lock.updated_at
40
+ assert{ b > a }
41
+ end
42
+
43
+ test 'that locks can be un-locked' do
44
+ lock = assert{ Bolt.for(:shared_resource) }
45
+ assert{ lock.lock! }
46
+ assert{ lock.unlock! }
47
+ end
48
+
49
+ test 'that locks know when they are locked' do
50
+ lock = assert{ Bolt.for(:shared_resource) }
51
+ assert{ !lock.locked? }
52
+ assert{ lock.lock! }
53
+ assert{ lock.locked? }
54
+ end
55
+
56
+ test 'that #lock takes a block' do
57
+ lock = assert{ Bolt.for(:shared_resource) }
58
+ assert do
59
+ assert{ !lock.locked? }
60
+ lock.lock{ assert{ lock.locked? } }
61
+ assert{ !lock.locked? }
62
+ end
63
+ end
64
+
65
+ test 'that other classes can mix-in lockability' do
66
+ locked = false
67
+ assert{ A.create.lock{ locked = true } }
68
+ assert{ locked }
69
+ end
70
+ end
data/test/testing.rb ADDED
@@ -0,0 +1,196 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'test/unit'
3
+
4
+ testdir = File.expand_path(File.dirname(__FILE__))
5
+ rootdir = File.dirname(testdir)
6
+ libdir = File.join(rootdir, 'lib')
7
+
8
+ STDOUT.sync = true
9
+
10
+ $:.unshift(testdir) unless $:.include?(testdir)
11
+ $:.unshift(libdir) unless $:.include?(libdir)
12
+ $:.unshift(rootdir) unless $:.include?(rootdir)
13
+
14
+ class Testing
15
+ class Slug < ::String
16
+ def Slug.for(*args)
17
+ string = args.flatten.compact.join('-')
18
+ words = string.to_s.scan(%r/\w+/)
19
+ words.map!{|word| word.gsub %r/[^0-9a-zA-Z_-]/, ''}
20
+ words.delete_if{|word| word.nil? or word.strip.empty?}
21
+ new(words.join('-').downcase)
22
+ end
23
+ end
24
+
25
+ class Context
26
+ attr_accessor :name
27
+
28
+ def initialize(name, *args)
29
+ @name = name
30
+ end
31
+
32
+ def to_s
33
+ Slug.for(name)
34
+ end
35
+ end
36
+ end
37
+
38
+ def Testing(*args, &block)
39
+ Class.new(::Test::Unit::TestCase) do
40
+
41
+ ## class methods
42
+ #
43
+ class << self
44
+ def contexts
45
+ @contexts ||= []
46
+ end
47
+
48
+ def context(*args, &block)
49
+ return contexts.last if(args.empty? and block.nil?)
50
+
51
+ context = Testing::Context.new(*args)
52
+ contexts.push(context)
53
+
54
+ begin
55
+ block.call(context)
56
+ ensure
57
+ contexts.pop
58
+ end
59
+ end
60
+
61
+ def slug_for(*args)
62
+ string = [context, args].flatten.compact.join('-')
63
+ words = string.to_s.scan(%r/\w+/)
64
+ words.map!{|word| word.gsub %r/[^0-9a-zA-Z_-]/, ''}
65
+ words.delete_if{|word| word.nil? or word.strip.empty?}
66
+ words.join('-').downcase.sub(/_$/, '')
67
+ end
68
+
69
+ def name() const_get(:Name) end
70
+
71
+ def testno()
72
+ '%05d' % (@testno ||= 0)
73
+ ensure
74
+ @testno += 1
75
+ end
76
+
77
+ def testing(*args, &block)
78
+ method = ["test", testno, slug_for(*args)].delete_if{|part| part.empty?}.join('_')
79
+ define_method(method, &block)
80
+ end
81
+
82
+ def test(*args, &block)
83
+ testing(*args, &block)
84
+ end
85
+
86
+ def setup(&block)
87
+ define_method(:setup, &block) if block
88
+ end
89
+
90
+ def teardown(&block)
91
+ define_method(:teardown, &block) if block
92
+ end
93
+
94
+ def prepare(&block)
95
+ @prepare ||= []
96
+ @prepare.push(block) if block
97
+ @prepare
98
+ end
99
+
100
+ def cleanup(&block)
101
+ @cleanup ||= []
102
+ @cleanup.push(block) if block
103
+ @cleanup
104
+ end
105
+ end
106
+
107
+ ## configure the subclass!
108
+ #
109
+ const_set(:Testno, '0')
110
+ slug = slug_for(*args).gsub(%r/-/,'_')
111
+ name = ['TESTING', '%03d' % const_get(:Testno), slug].delete_if{|part| part.empty?}.join('_')
112
+ name = name.upcase!
113
+ const_set(:Name, name)
114
+ const_set(:Missing, Object.new.freeze)
115
+
116
+ ## instance methods
117
+ #
118
+ alias_method('__assert__', 'assert')
119
+
120
+ def assert(*args, &block)
121
+ if args.size == 1 and args.first.is_a?(Hash)
122
+ options = args.first
123
+ expected = getopt(:expected, options){ missing }
124
+ actual = getopt(:actual, options){ missing }
125
+ if expected == missing and actual == missing
126
+ actual, expected, *ignored = options.to_a.flatten
127
+ end
128
+ expected = expected.call() if expected.respond_to?(:call)
129
+ actual = actual.call() if actual.respond_to?(:call)
130
+ assert_equal(expected, actual)
131
+ end
132
+
133
+ if block
134
+ label = "assert(#{ args.join(' ') })"
135
+ result = nil
136
+ assert_nothing_raised{ result = block.call }
137
+ __assert__(result, label)
138
+ result
139
+ else
140
+ result = args.shift
141
+ label = "assert(#{ args.join(' ') })"
142
+ __assert__(result, label)
143
+ result
144
+ end
145
+ end
146
+
147
+ def missing
148
+ self.class.const_get(:Missing)
149
+ end
150
+
151
+ def getopt(opt, hash, options = nil, &block)
152
+ [opt.to_s, opt.to_s.to_sym].each do |key|
153
+ return hash[key] if hash.has_key?(key)
154
+ end
155
+ default =
156
+ if block
157
+ block.call
158
+ else
159
+ options.is_a?(Hash) ? options[:default] : nil
160
+ end
161
+ return default
162
+ end
163
+
164
+ def subclass_of exception
165
+ class << exception
166
+ def ==(other) super or self > other end
167
+ end
168
+ exception
169
+ end
170
+
171
+ ##
172
+ #
173
+ module_eval(&block)
174
+
175
+ self.setup()
176
+ self.prepare.each{|b| b.call()}
177
+
178
+ at_exit{
179
+ self.teardown()
180
+ self.cleanup.each{|b| b.call()}
181
+ }
182
+
183
+ self
184
+ end
185
+ end
186
+
187
+
188
+ if $0 == __FILE__
189
+
190
+ Testing 'Testing' do
191
+ testing('foo'){ assert true }
192
+ test{ assert true }
193
+ p instance_methods.grep(/test/)
194
+ end
195
+
196
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mongoid-bolt
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ara T. Howard
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-24 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: mongoid
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 3.0.1
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 3.0.1
30
+ description: a mongoid 3/moped compatible lock implementation and mixin
31
+ email: ara.t.howard@gmail.com
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - README.md
37
+ - Rakefile
38
+ - lib/mongoid-bolt.rb
39
+ - mongoid-bolt.gemspec
40
+ - test/helper.rb
41
+ - test/mongoid-bolt_test.rb
42
+ - test/testing.rb
43
+ homepage: https://github.com/ahoward/mongoid-bolt
44
+ licenses: []
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubyforge_project: codeforpeople
63
+ rubygems_version: 1.8.24
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: mongoid-bolt
67
+ test_files: []