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