distlock 0.0.6 → 0.0.7
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/.gitignore +1 -0
- data/distlock.gemspec +1 -2
- data/lib/distlock/lock_error.rb +4 -0
- data/lib/distlock/version.rb +1 -1
- data/lib/distlock/zk/common.rb +23 -1
- data/lib/distlock/zk/exclusive_lock.rb +154 -0
- data/lib/distlock/zk/zk.rb +1 -1
- data/lib/distlock.rb +2 -1
- data/spec/zk_common_spec.rb +11 -0
- metadata +80 -40
- data/lib/distlock/zk/affinity_pool.rb +0 -13
data/distlock.gemspec
CHANGED
@@ -19,10 +19,9 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
21
|
# specify any dependencies here; for example:
|
22
|
-
# s.add_development_dependency "rspec"
|
23
|
-
# s.add_runtime_dependency "rest-client"
|
24
22
|
|
25
23
|
s.add_development_dependency "rake", "~> 0.9"
|
24
|
+
s.add_development_dependency "rspec", "~> 2.0"
|
26
25
|
|
27
26
|
s.add_runtime_dependency "zookeeper", "~> 0.4"
|
28
27
|
end
|
data/lib/distlock/version.rb
CHANGED
data/lib/distlock/zk/common.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
1
3
|
module Distlock
|
2
4
|
module ZK
|
3
5
|
module Common
|
@@ -7,6 +9,14 @@ module Distlock
|
|
7
9
|
end
|
8
10
|
end
|
9
11
|
|
12
|
+
def logger
|
13
|
+
@logger ||= Logger.new(STDOUT)
|
14
|
+
end
|
15
|
+
|
16
|
+
def logger=(logger)
|
17
|
+
@logger = logger
|
18
|
+
end
|
19
|
+
|
10
20
|
# does a node exist for the given path?
|
11
21
|
def exists?(path)
|
12
22
|
puts "checking if #{path} exists"
|
@@ -42,7 +52,19 @@ module Distlock
|
|
42
52
|
puts lock_path
|
43
53
|
result = zk.create(:path => lock_path, :sequence => true, :ephemeral => true)
|
44
54
|
puts result
|
45
|
-
|
55
|
+
result[:path]
|
56
|
+
end
|
57
|
+
|
58
|
+
# access @zk directly here, we don't want to lazy instantiate again if closed already
|
59
|
+
def close
|
60
|
+
begin
|
61
|
+
@zk.close if @zk
|
62
|
+
rescue StandardError, ZookeeperExceptions::ZookeeperException => error
|
63
|
+
logger.error("Distlock::ZK::Common#close: caught and squashed error while closing connection - #{error}")
|
64
|
+
end
|
65
|
+
|
66
|
+
@zk = nil
|
67
|
+
end
|
46
68
|
end
|
47
69
|
end
|
48
70
|
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
module Distlock
|
2
|
+
module ZK
|
3
|
+
class ExclusiveLock
|
4
|
+
include Distlock::ZK::Common
|
5
|
+
|
6
|
+
WATCHER_TIMEOUT = 60 #seconds
|
7
|
+
|
8
|
+
def initialize(options={})
|
9
|
+
defaults = {:host => "localhost:2181", :timeout => 10, :root_path => "/lock/exclusive/default"}
|
10
|
+
@options = defaults.merge(options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def owner?
|
14
|
+
@owner
|
15
|
+
end
|
16
|
+
|
17
|
+
def my_lock
|
18
|
+
@my_lock
|
19
|
+
end
|
20
|
+
|
21
|
+
def do_watcher(watcher, lock)
|
22
|
+
if watcher.type == ZookeeperConstants::ZOO_DELETED_EVENT
|
23
|
+
logger.debug "Distlock::ZK::ExclusiveLock#do_watcher: watcher called for delete of node - #{lock}"
|
24
|
+
else
|
25
|
+
if watcher.type == ZookeeperConstants::ZOO_CREATED_EVENT
|
26
|
+
logger.error "Distlock::ZK::ExclusiveLock#do_watcher: watcher called for creation of node (should never happen for an ephemeral node?) - #{lock}"
|
27
|
+
elsif watcher.type == ZookeeperConstants::ZOO_SESSION_EVENT
|
28
|
+
logger.error "Distlock::ZK::ExclusiveLock#do_watcher: watcher called for zoo session event, closing the session for this client - #{lock}"
|
29
|
+
else
|
30
|
+
logger.error "Distlock::ZK::ExclusiveLock#do_watcher: watcher called for unexpected event, closing the session for this client - #{lock}, event - #{watcher.type}"
|
31
|
+
end
|
32
|
+
|
33
|
+
close
|
34
|
+
raise LockError.new("Distlock::ZK::ExclusiveLock#do_watcher: got an unexpected watcher type - #{watcher.type}")
|
35
|
+
end
|
36
|
+
|
37
|
+
@watcher_called=true
|
38
|
+
end
|
39
|
+
|
40
|
+
def check_for_existing_lock(path)
|
41
|
+
children = zk.get_children(:path => path)[:children].sort{|a,b|a.split('-').last <=> b.split('-').last}
|
42
|
+
children.detect do |child|
|
43
|
+
logger.debug "checking existing lock for our client_id - #{child} vs. #{zk.client_id}"
|
44
|
+
if child.split('-')[1] == "#{zk.client_id}"
|
45
|
+
logger.debug "found existing lock for client_id #{zk.client_id}, lock - #{child}, reusing"
|
46
|
+
return "#{path}/#{child}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def lock(path)
|
52
|
+
@owner = false
|
53
|
+
|
54
|
+
safe_create(path)
|
55
|
+
|
56
|
+
# TODO - combine these into a single method like find_or_create
|
57
|
+
lock = check_for_existing_lock(path)
|
58
|
+
lock = create_sequenced_ephemeral(path) unless lock
|
59
|
+
|
60
|
+
logger.debug "my lock path - #{lock}"
|
61
|
+
@my_lock = lock
|
62
|
+
result = _get_lock(lock)
|
63
|
+
logger.info("Distlock::ZK::ExclusiveLock#lock: lock acquired - #{lock}")
|
64
|
+
|
65
|
+
result
|
66
|
+
end
|
67
|
+
|
68
|
+
def unlock(lock = @my_lock)
|
69
|
+
return unless lock
|
70
|
+
|
71
|
+
logger.debug "unlocking - #{lock}"
|
72
|
+
|
73
|
+
zk.delete(:path => lock)
|
74
|
+
|
75
|
+
if lock == @my_lock
|
76
|
+
@my_lock = nil
|
77
|
+
@owner = false
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def _get_lock(lock)
|
82
|
+
logger.debug "_get_lock: entered for #{lock}"
|
83
|
+
|
84
|
+
while !@owner
|
85
|
+
|
86
|
+
path = lock.split('/')[0...-1].join('/')
|
87
|
+
|
88
|
+
# TODO - pass this in as parameter?
|
89
|
+
children = zk.get_children(:path => path)[:children].sort{|a,b|a.split('-').last <=> b.split('-').last}
|
90
|
+
lock_last = lock.split('/').last
|
91
|
+
lock_idx = children.index(lock_last)
|
92
|
+
|
93
|
+
if lock_idx.nil?
|
94
|
+
logger.error("Distlock::ZK::ExclusiveLock#_get_lock: failed to find our lock in the node children (connection reset?)")
|
95
|
+
raise LockError.new("failed to find our lock in the node children (connection reset?)")
|
96
|
+
elsif lock_idx == 0
|
97
|
+
logger.debug "lock acquired (client id - #{zk.client_id}), lock - #{lock}"
|
98
|
+
@owner = true
|
99
|
+
return true
|
100
|
+
else
|
101
|
+
logger.debug "Distlock::ZK::ExclusiveLock#_get_lock: lock contention for #{lock} - #{children.inspect} (my client id - #{zk.client_id})"
|
102
|
+
logger.info "Distlock::ZK::ExclusiveLock#_get_lock: lock contention - #{lock}"
|
103
|
+
|
104
|
+
to_watch = "#{path}/#{children[lock_idx-1]}"
|
105
|
+
logger.debug "about to set watch on - #{to_watch}"
|
106
|
+
|
107
|
+
# 2-step process so we minimise the chance of setting watches on the node if it does not exist for any reason
|
108
|
+
@watcher_called=false
|
109
|
+
@watcher = Zookeeper::WatcherCallback.new { do_watcher(@watcher, lock) }
|
110
|
+
resp = zk.stat(:path => to_watch)
|
111
|
+
resp = zk.stat(:path => to_watch, :watcher => @watcher) if resp[:stat].exists
|
112
|
+
|
113
|
+
if resp[:stat].exists
|
114
|
+
logger.info "Distlock::ZK::ExclusiveLock#_get_lock: watcher set, node exists, watching - #{to_watch}, our lock - #{lock}"
|
115
|
+
start_time = Time.now
|
116
|
+
while !@watcher_called
|
117
|
+
sleep 0.1
|
118
|
+
|
119
|
+
if (start_time + WATCHER_TIMEOUT) < Time.now
|
120
|
+
logger.error("Distlock::ZK::ExclusiveLock#_get_lock: timed out while watching - #{to_watch}, our lock - #{lock}, closing session and bombing out")
|
121
|
+
close
|
122
|
+
raise LockError.new("Distlock::ZK::ExclusiveLock#_get_lock timed out while waiting for watcher")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
else
|
126
|
+
logger.error("Distlock::ZK::ExclusiveLock#_get_lock: node we are watching does not exist, closing session, lock - #{lock}")
|
127
|
+
close
|
128
|
+
raise LockError.new("node we tried to watch does not exist")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def with_lock(path="/distlock/zk/exclusive_lock/default")
|
135
|
+
begin
|
136
|
+
lock(path)
|
137
|
+
yield if block_given?
|
138
|
+
rescue ZookeeperExceptions::ZookeeperException::SessionExpired => e
|
139
|
+
close
|
140
|
+
raise LockError.new("error encountered while attempting to obtain lock - #{e}, zookeeper session has been closed")
|
141
|
+
rescue ZookeeperExceptions::ZookeeperException => e
|
142
|
+
raise LockError.new("error encountered while attempting to obtain lock - #{e}")
|
143
|
+
ensure
|
144
|
+
begin
|
145
|
+
unlock
|
146
|
+
rescue ZookeeperExceptions::ZookeeperException => e
|
147
|
+
logger.error("Distlock::ZK::ExclusiveLock#with_lock: error while unlocking - #{e}, closing session to clean up our lock")
|
148
|
+
close
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
data/lib/distlock/zk/zk.rb
CHANGED
data/lib/distlock.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
require 'distlock/version'
|
2
|
+
require 'distlock/lock_error'
|
2
3
|
require 'distlock/zk/zk'
|
3
4
|
|
4
5
|
module Distlock
|
5
6
|
end
|
6
7
|
|
7
|
-
# convenience so we can use either
|
8
|
+
# convenience so we can use either Distlock or DistLock
|
8
9
|
module DistLock
|
9
10
|
include Distlock
|
10
11
|
end
|
metadata
CHANGED
@@ -1,45 +1,78 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: distlock
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 17
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 7
|
10
|
+
version: 0.0.7
|
6
11
|
platform: ruby
|
7
|
-
authors:
|
12
|
+
authors:
|
8
13
|
- Simon Horne
|
9
14
|
autorequire:
|
10
15
|
bindir: bin
|
11
16
|
cert_chain: []
|
12
|
-
|
13
|
-
|
14
|
-
|
17
|
+
|
18
|
+
date: 2012-04-11 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
15
22
|
name: rake
|
16
|
-
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
17
25
|
none: false
|
18
|
-
requirements:
|
26
|
+
requirements:
|
19
27
|
- - ~>
|
20
|
-
- !ruby/object:Gem::Version
|
21
|
-
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 25
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 9
|
33
|
+
version: "0.9"
|
22
34
|
type: :development
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rspec
|
23
38
|
prerelease: false
|
24
|
-
|
25
|
-
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ~>
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 3
|
45
|
+
segments:
|
46
|
+
- 2
|
47
|
+
- 0
|
48
|
+
version: "2.0"
|
49
|
+
type: :development
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
26
52
|
name: zookeeper
|
27
|
-
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
28
55
|
none: false
|
29
|
-
requirements:
|
56
|
+
requirements:
|
30
57
|
- - ~>
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
- 4
|
63
|
+
version: "0.4"
|
33
64
|
type: :runtime
|
34
|
-
|
35
|
-
version_requirements: *70294611916060
|
65
|
+
version_requirements: *id003
|
36
66
|
description: Distributed Locking
|
37
|
-
email:
|
67
|
+
email:
|
38
68
|
- simon@soulware.co.uk
|
39
69
|
executables: []
|
70
|
+
|
40
71
|
extensions: []
|
72
|
+
|
41
73
|
extra_rdoc_files: []
|
42
|
-
|
74
|
+
|
75
|
+
files:
|
43
76
|
- .gitignore
|
44
77
|
- Gemfile
|
45
78
|
- README
|
@@ -47,38 +80,45 @@ files:
|
|
47
80
|
- distlock.gemspec
|
48
81
|
- lib/dist_lock.rb
|
49
82
|
- lib/distlock.rb
|
83
|
+
- lib/distlock/lock_error.rb
|
50
84
|
- lib/distlock/version.rb
|
51
|
-
- lib/distlock/zk/affinity_pool.rb
|
52
85
|
- lib/distlock/zk/common.rb
|
86
|
+
- lib/distlock/zk/exclusive_lock.rb
|
53
87
|
- lib/distlock/zk/zk.rb
|
88
|
+
- spec/zk_common_spec.rb
|
89
|
+
has_rdoc: true
|
54
90
|
homepage: http://soulware.github.com/distlock
|
55
91
|
licenses: []
|
92
|
+
|
56
93
|
post_install_message:
|
57
94
|
rdoc_options: []
|
58
|
-
|
95
|
+
|
96
|
+
require_paths:
|
59
97
|
- lib
|
60
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
99
|
none: false
|
62
|
-
requirements:
|
63
|
-
- -
|
64
|
-
- !ruby/object:Gem::Version
|
65
|
-
|
66
|
-
segments:
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
hash: 3
|
104
|
+
segments:
|
67
105
|
- 0
|
68
|
-
|
69
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
|
+
version: "0"
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
108
|
none: false
|
71
|
-
requirements:
|
72
|
-
- -
|
73
|
-
- !ruby/object:Gem::Version
|
74
|
-
|
75
|
-
segments:
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
hash: 3
|
113
|
+
segments:
|
76
114
|
- 0
|
77
|
-
|
115
|
+
version: "0"
|
78
116
|
requirements: []
|
117
|
+
|
79
118
|
rubyforge_project: distlock
|
80
|
-
rubygems_version: 1.
|
119
|
+
rubygems_version: 1.3.7
|
81
120
|
signing_key:
|
82
121
|
specification_version: 3
|
83
122
|
summary: Distributed Locking
|
84
|
-
test_files:
|
123
|
+
test_files:
|
124
|
+
- spec/zk_common_spec.rb
|
@@ -1,13 +0,0 @@
|
|
1
|
-
module Distlock
|
2
|
-
module ZK
|
3
|
-
class AffinityPool
|
4
|
-
include Distlock::ZK::Common
|
5
|
-
|
6
|
-
def initialize(options={})
|
7
|
-
defaults = {:host => "localhost:2181", :timeout => 10, :root_path => "/affinity/pool/test"}
|
8
|
-
@options = defaults.merge(options)
|
9
|
-
end
|
10
|
-
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|