active_record_mutex 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
+