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 CHANGED
@@ -1,4 +1,5 @@
1
1
  *.gem
2
2
  .bundle
3
+ .rvmrc
3
4
  Gemfile.lock
4
5
  pkg/*
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
@@ -0,0 +1,4 @@
1
+ module Distlock
2
+ class LockError < StandardError
3
+ end
4
+ end
@@ -1,3 +1,3 @@
1
1
  module Distlock
2
- VERSION = "0.0.6"
2
+ VERSION = "0.0.7"
3
3
  end
@@ -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
- end
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
@@ -1,6 +1,6 @@
1
1
  require 'zookeeper'
2
2
  require 'distlock/zk/common'
3
- require 'distlock/zk/affinity_pool'
3
+ require 'distlock/zk/exclusive_lock'
4
4
 
5
5
  module Distlock
6
6
  module ZK
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 name
8
+ # convenience so we can use either Distlock or DistLock
8
9
  module DistLock
9
10
  include Distlock
10
11
  end
@@ -0,0 +1,11 @@
1
+ require 'distlock/zk/common'
2
+
3
+ class ZKFoo
4
+ include Distlock::ZK::Common
5
+ end
6
+
7
+ describe Distlock::ZK::Common do
8
+ it "should do something" do
9
+ true
10
+ end
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
- version: 0.0.6
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
- date: 2012-01-31 00:00:00.000000000 Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
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
- requirement: &70294611917200 !ruby/object:Gem::Requirement
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
- version: '0.9'
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
- version_requirements: *70294611917200
25
- - !ruby/object:Gem::Dependency
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
- requirement: &70294611916060 !ruby/object:Gem::Requirement
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
- version: '0.4'
58
+ - !ruby/object:Gem::Version
59
+ hash: 3
60
+ segments:
61
+ - 0
62
+ - 4
63
+ version: "0.4"
33
64
  type: :runtime
34
- prerelease: false
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
- files:
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
- require_paths:
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
- version: '0'
66
- segments:
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ hash: 3
104
+ segments:
67
105
  - 0
68
- hash: -2701141366311173259
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
- version: '0'
75
- segments:
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ hash: 3
113
+ segments:
76
114
  - 0
77
- hash: -2701141366311173259
115
+ version: "0"
78
116
  requirements: []
117
+
79
118
  rubyforge_project: distlock
80
- rubygems_version: 1.8.10
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