active_record_mutex 0.0.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.
data/CHANGES ADDED
File without changes
data/README ADDED
@@ -0,0 +1,46 @@
1
+ == Description
2
+
3
+ This gem provides a Mutex that is based on ctiveRecord's database connection.
4
+ (At the moment this only works for Mysql.) It can be used to synchronise
5
+ different ruby processes via the connected database.
6
+
7
+ == Download
8
+
9
+ The latest version of the <b>active_record_mutex</b> source archive can be
10
+ found at
11
+
12
+ * http://www.ping.de/~flori
13
+
14
+ == Installation
15
+
16
+ You can use rubygems to fetch the gem and install it for you:
17
+
18
+ # gem install active_record_mutex
19
+
20
+ You can also put this line into your Rails environment.rb file
21
+
22
+ config.gem 'active_record_mutex'
23
+
24
+ and install the gem via
25
+
26
+ $ rake gems:install
27
+
28
+ == Usage
29
+
30
+ If you want to synchronize method calls to your model's methods you can easily
31
+ do this by passing a mutex instance to ActiveRecord's synchronize class method.
32
+ This mutex instance will be named Foo like the ActiveRecord was named:
33
+
34
+ class Foo < ActiveRecord::Base
35
+ def foo
36
+ end
37
+
38
+ synchronize :foo, :with => :mutex
39
+ end
40
+
41
+ If you want more control over the mutex and/or give it a special name you can
42
+ create Mutex instance like this:
43
+
44
+ my_mutex = ActiveRecord::Mutex::Mutex.new(:name => 'my_mutex')
45
+
46
+ Now you can send all messages directly to the Mutex instance.
data/Rakefile ADDED
@@ -0,0 +1,86 @@
1
+ begin
2
+ require 'rake/gempackagetask'
3
+ rescue LoadError
4
+ end
5
+ require 'rake/clean'
6
+ require 'rbconfig'
7
+ include Config
8
+
9
+ PKG_NAME = 'active_record_mutex'
10
+ PKG_VERSION = File.read('VERSION').chomp
11
+ PKG_FILES = FileList['**/*'].exclude(/^(doc|CVS|pkg|coverage)/)
12
+ PKG_SUMMARY = 'Implementation of a Mutex for Active Record'
13
+ PKG_RDOC_OPTIONS = [ '--main', 'README', '--title', PKG_SUMMARY ]
14
+ CLEAN.include 'coverage', 'doc'
15
+
16
+ desc "Testing library"
17
+ task :test do
18
+ ruby %{-Ilib test/mutex_test.rb}
19
+ end
20
+
21
+ desc "Testing library (with coverage)"
22
+ task :coverage do
23
+ sh %{rcov -Ilib test/mutex_test.rb}
24
+ end
25
+
26
+ desc "Installing library"
27
+ task :install do
28
+ ruby 'install.rb'
29
+ end
30
+
31
+ desc "Creating documentation"
32
+ task :doc do
33
+ sh 'rdoc', *(PKG_RDOC_OPTIONS + Dir['lib/**/*.rb'] + [ 'README' ])
34
+ end
35
+
36
+ if defined? Gem
37
+ spec = Gem::Specification.new do |s|
38
+ s.name = PKG_NAME
39
+ s.version = PKG_VERSION
40
+ s.summary = PKG_SUMMARY
41
+ s.description = "Mutex that can be used to synchronise ruby processes via an ActiveRecord"\
42
+ " datababase connection. (Only Mysql is supported at the moment.)"
43
+
44
+ s.files = PKG_FILES.to_a.sort
45
+
46
+ s.require_path = 'lib'
47
+
48
+ s.has_rdoc = true
49
+ s.rdoc_options = PKG_RDOC_OPTIONS
50
+ s.extra_rdoc_files << 'README'
51
+ #s.test_files << 'test/mutex_test.rb'
52
+
53
+ s.author = "Florian Frank"
54
+ s.email = "flori@ping.de"
55
+ s.homepage = "http://flori.github.com/#{PKG_NAME}"
56
+ s.rubyforge_project = "#{PKG_NAME}"
57
+ end
58
+
59
+ Rake::GemPackageTask.new(spec) do |pkg|
60
+ pkg.need_tar = true
61
+ pkg.package_files += PKG_FILES
62
+ end
63
+ end
64
+
65
+ desc m = "Writing version information for #{PKG_VERSION}"
66
+ task :version do
67
+ puts m
68
+ File.open(File.join('lib', 'active_record', 'mutex', 'version.rb'), 'w') do |v|
69
+ v.puts <<EOT
70
+ module ActiveRecord
71
+ module Mutex
72
+ # ActiveRecord::Mutex version
73
+ VERSION = '#{PKG_VERSION}'
74
+ VERSION_ARRAY = VERSION.split(/\\./).map { |x| x.to_i } # :nodoc:
75
+ VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
76
+ VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
77
+ VERSION_BUILD = VERSION_ARRAY[2] # :nodoc:
78
+ end
79
+ end
80
+ EOT
81
+ end
82
+ end
83
+
84
+ task :default => [ :version, :test ]
85
+
86
+ task :release => [ :clean, :version, :package ]
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
@@ -0,0 +1,148 @@
1
+ require 'active_record'
2
+ require 'active_record/mutex/version'
3
+
4
+ module ActiveRecord
5
+ # This module is mixed into ActiveRecord::Base to provide the mutex methods
6
+ # that return a mutex for a particular ActiveRecord::Base subclass/instance.
7
+ module Mutex
8
+ # This is the base exception of all mutex exceptions.
9
+ class MutexError < ActiveRecordError; end
10
+
11
+ # This exception is raised if a mutex of the given name isn't locked at the
12
+ # moment and unlock was called.
13
+ class MutexUnlockFailed < MutexError; end
14
+
15
+ # This exception is raised if a mutex of the given name is locked at the
16
+ # moment and lock was called again.
17
+ class MutexLocked < MutexError; end
18
+
19
+ def self.included(modul)
20
+ modul.instance_eval do
21
+ extend ClassMethods
22
+ end
23
+ end
24
+
25
+ module ClassMethods
26
+ # Returns a mutex instance for this ActiveRecord subclass.
27
+ def mutex
28
+ @mutex ||= Mutex.new(:name => name)
29
+ end
30
+ end
31
+
32
+ # Returns a mutex instance for this ActiveRecord instance.
33
+ def mutex
34
+ @mutex ||= Mutex.new(:name => self.class.name)
35
+ end
36
+
37
+ class Mutex
38
+ # Creates a mutex with the name given with the option :name.
39
+ def initialize(opts = {})
40
+ @name = opts[:name] or raise ArgumentError, "Mutex requires a :name argument"
41
+ end
42
+
43
+ # Returns the name of this mutex as given as a constructor argument.
44
+ attr_reader :name
45
+
46
+ # Locks the mutex if it isn't already locked via another database
47
+ # connection and yields to the given block. After executing the block's
48
+ # content the mutex is unlocked (only if it was locked by this
49
+ # synchronize method before).
50
+ #
51
+ # If the mutex was already locked by another database connection the
52
+ # method blocks until it could aquire the lock and only then the block's
53
+ # content is executed. If the mutex was already locked by the current database
54
+ # connection then the block's content is run and the the mutex isn't
55
+ # unlocked afterwards.
56
+ #
57
+ # If a value in seconds is passed to the :timeout option the blocking
58
+ # ends after that many seconds and the method returns immediately if the
59
+ # lock couldn't be aquired during that time.
60
+ def synchronize(opts = {})
61
+ locked_before = aquired_lock?
62
+ lock opts
63
+ yield
64
+ rescue ActiveRecord::Mutex::MutexLocked
65
+ return nil
66
+ ensure
67
+ locked_before or unlock
68
+ end
69
+
70
+ # Locks the mutex and returns true if successful. If the mutex is
71
+ # already locked and the timeout in seconds is given as the :timeout
72
+ # option, this method raises a MutexLocked exception after that many
73
+ # seconds. If the :timeout option wasn't given, this method blocks until
74
+ # the lock could be aquired.
75
+ def lock(opts = {})
76
+ if opts[:timeout]
77
+ lock_with_timeout opts
78
+ else
79
+ begin
80
+ lock_with_timeout :timeout => 1
81
+ rescue MutexLocked
82
+ retry
83
+ end
84
+ end
85
+ end
86
+
87
+ # Unlocks the mutex and returns true if successful. Otherwise this method
88
+ # raises a MutexLocked exception.
89
+ def unlock(*)
90
+ case query("SELECT RELEASE_LOCK(#{ActiveRecord::Base.quote_value(name)})")
91
+ when 1 then true
92
+ when 0, nil then raise MutexUnlockFailed, "unlocking of mutex '#{name}' failed"
93
+ end
94
+ end
95
+
96
+ # Returns true if this mutex is unlocked at the moment.
97
+ def unlocked?
98
+ query("SELECT IS_FREE_LOCK(#{ActiveRecord::Base.quote_value(name)})") == 1
99
+ end
100
+
101
+ # Returns true if this mutex is locked at the moment.
102
+ def locked?
103
+ not unlocked?
104
+ end
105
+
106
+ # Returns true if this mutex is locked by this database connection.
107
+ def aquired_lock?
108
+ query("SELECT CONNECTION_ID() = IS_USED_LOCK(#{ActiveRecord::Base.quote_value(name)})") == 1
109
+ end
110
+
111
+ # Returns true if this mutex is not locked by this database connection.
112
+ def not_aquired_lock?
113
+ not aquired_lock?
114
+ end
115
+
116
+ # Returns a string representation of this Mutex instance.
117
+ def to_s
118
+ "#<#{self.class} #{name}>"
119
+ end
120
+
121
+ alias inspect to_s
122
+
123
+ private
124
+
125
+ def lock_with_timeout(opts = {})
126
+ timeout = opts[:timeout] || 1
127
+ case query("SELECT GET_LOCK(#{ActiveRecord::Base.quote_value(name)}, #{timeout})")
128
+ when 1 then true
129
+ when 0 then raise MutexLocked, "mutex '#{name}' is already locked"
130
+ end
131
+ end
132
+
133
+ def query(sql)
134
+ if result = ActiveRecord::Base.connection.execute(sql)
135
+ result = result.fetch_row.first.to_i
136
+ $DEBUG and warn %{query("#{sql}") = #{result}}
137
+ end
138
+ result
139
+ rescue ActiveRecord::StatementInvalid
140
+ nil
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ ActiveRecord::Base.class_eval do
147
+ include ActiveRecord::Mutex
148
+ end
@@ -0,0 +1,10 @@
1
+ module ActiveRecord
2
+ module Mutex
3
+ # ActiveRecord::Mutex version
4
+ VERSION = '0.0.0'
5
+ VERSION_ARRAY = VERSION.split(/\./).map { |x| x.to_i } # :nodoc:
6
+ VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
7
+ VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
8
+ VERSION_BUILD = VERSION_ARRAY[2] # :nodoc:
9
+ end
10
+ end
@@ -0,0 +1 @@
1
+ require 'active_record/mutex'
@@ -0,0 +1,101 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+
4
+ require 'active_record'
5
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
6
+ require 'active_record/mutex'
7
+
8
+ ActiveRecord::Base.establish_connection(
9
+ :adapter => "mysql",
10
+ :database => ENV['DATABASE'] || "test",
11
+ :username => ENV['USER'],
12
+ :password => ENV['PASSWORD']
13
+ )
14
+
15
+ class MutexTest < Test::Unit::TestCase
16
+ class Foo < ActiveRecord::Base; end
17
+
18
+ def setup
19
+ ActiveRecord::Schema.define(:version => 1) do
20
+ create_table(:foos, :force => true) { |t| t.string :bar }
21
+ end
22
+ end
23
+
24
+ def teardown
25
+ ActiveRecord::Base.connection.tables.each do |table|
26
+ ActiveRecord::Base.connection.drop_table(table)
27
+ end
28
+ end
29
+
30
+ def test_exported_methods
31
+ mutex = Foo.mutex
32
+ assert_kind_of ActiveRecord::Mutex::Mutex, mutex
33
+ assert_equal mutex.name, Foo.name
34
+ mutex = Foo.new.mutex
35
+ assert_kind_of ActiveRecord::Mutex::Mutex, mutex
36
+ assert_equal mutex.name, Foo.name
37
+ end
38
+
39
+ def test_create
40
+ mutex = ActiveRecord::Mutex::Mutex.new(:name => 'Create')
41
+ assert_equal 'Create', mutex.name
42
+ end
43
+
44
+ def test_lock
45
+ mutex = ActiveRecord::Mutex::Mutex.new(:name => 'Lock')
46
+ assert mutex.unlocked?
47
+ assert mutex.lock
48
+ assert mutex.locked?
49
+ assert mutex.aquired_lock?
50
+ assert mutex.lock
51
+ end
52
+
53
+ def test_unlock
54
+ mutex = ActiveRecord::Mutex::Mutex.new(:name => 'Unlock')
55
+ assert_raises(ActiveRecord::Mutex::MutexUnlockFailed) { mutex.unlock }
56
+ assert mutex.lock
57
+ assert mutex.locked?
58
+ assert mutex.aquired_lock?
59
+ assert mutex.unlock
60
+ assert mutex.unlocked?
61
+ assert_raises(ActiveRecord::Mutex::MutexUnlockFailed) { mutex.unlock }
62
+ end
63
+
64
+ def test_synchronize
65
+ mutex = ActiveRecord::Mutex::Mutex.new(:name => 'Sync1')
66
+ assert mutex.unlocked?
67
+ mutex.synchronize do
68
+ assert mutex.locked?
69
+ end
70
+ assert mutex.unlocked?
71
+ end
72
+
73
+ def test_synchronize_exception
74
+ mutex = ActiveRecord::Mutex::Mutex.new(:name => 'Sync2')
75
+ exception = Class.new StandardError
76
+ begin
77
+ assert mutex.unlocked?
78
+ mutex.synchronize do
79
+ assert mutex.locked?
80
+ raise exception
81
+ end
82
+ rescue exception
83
+ assert mutex.unlocked?
84
+ assert mutex.lock
85
+ assert mutex.locked?
86
+ end
87
+ end
88
+
89
+ def test_synchronize_nested
90
+ mutex = ActiveRecord::Mutex::Mutex.new(:name => 'Sync3')
91
+ assert mutex.unlocked?
92
+ mutex.synchronize do
93
+ assert mutex.locked?
94
+ mutex.synchronize do
95
+ assert mutex.locked?
96
+ end
97
+ assert mutex.locked?
98
+ end
99
+ assert mutex.unlocked?
100
+ end
101
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_record_mutex
3
+ version: !ruby/object:Gem::Version
4
+ hash: 31
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 0
10
+ version: 0.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Florian Frank
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-04-01 00:00:00 +02:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: Mutex that can be used to synchronise ruby processes via an ActiveRecord datababase connection. (Only Mysql is supported at the moment.)
23
+ email: flori@ping.de
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files:
29
+ - README
30
+ files:
31
+ - CHANGES
32
+ - README
33
+ - Rakefile
34
+ - VERSION
35
+ - lib/active_record/mutex.rb
36
+ - lib/active_record/mutex/version.rb
37
+ - lib/active_record_mutex.rb
38
+ - test/mutex_test.rb
39
+ has_rdoc: true
40
+ homepage: http://flori.github.com/active_record_mutex
41
+ licenses: []
42
+
43
+ post_install_message:
44
+ rdoc_options:
45
+ - --main
46
+ - README
47
+ - --title
48
+ - Implementation of a Mutex for Active Record
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ hash: 3
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ hash: 3
66
+ segments:
67
+ - 0
68
+ version: "0"
69
+ requirements: []
70
+
71
+ rubyforge_project: active_record_mutex
72
+ rubygems_version: 1.6.2
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: Implementation of a Mutex for Active Record
76
+ test_files: []
77
+