-
-
Save bmizerany/1177261 to your computer and use it in GitHub Desktop.
Locking with Doozer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'rubygems' | |
require 'fraggle/block' | |
# # Locks with doozerd | |
# | |
# Doozerd is a little coordination service. I think of it like Zookeeper | |
# Lite (TM). In today's episode, we'll naively manage exclusive advisory locks. | |
# | |
# Disclaimer: If part of this implementation is total lolscale, | |
# please to let me know what papers/sources I should read to avoid lolscaling. | |
# | |
# To run this example (assuming you have doozerd installed and the fraggle-block | |
# gem): | |
# | |
# $ doozerd # In shell 0 | |
# $ ruby locksmith2.rb # In shell 1 | |
# $ ruby locksmith2.rb # In shell 2 | |
# $ doozer watch '/example' # In shell 3 | |
# | |
# You should observe the two clients trading the lock. In the shell 3, you can | |
# watch the clients acquire and release the lock as doozerd sees it. | |
# Gracefully exit if we're in the middle of doing work. | |
$run = true | |
Signal.trap('TERM') { puts "Quittin' time, namean!"; $run = false } | |
Signal.trap('INT') { puts "Quittin' time, namean!"; $run = false } | |
# A locker is where you put your valuable state while you're doing | |
# something else. | |
module Locker | |
# We'll share a connection between all locks via a module instance variable. | |
@connection = nil | |
# Grab that connection. | |
def self.connection | |
@connection | |
end | |
# Set that connection. | |
def self.connection=(c) | |
@connection = c | |
end | |
end | |
# A shared, advisory lock. Kids, only use locks if all else fails. | |
class Locker::Lock | |
# This is the whole API. Specify a lock (`filename`) to wrap around a `block` | |
# of work. Doesn't handle timing out the lock, just retries until the heat | |
# death of the universe. | |
def self.with_lock(filename, &block) | |
lock = new | |
rev = lock.acquire(filename) | |
block.call | |
lock.release(filename, rev) | |
end | |
# Grabs the lock. It first grabs the lock file in doozer to see if anyone | |
# already has the lock. If it's already acquired, it sleeps for a second and | |
# tries again. Otherwise, it tries to set the lock. If the revision on the | |
# lock file is greater after setting the lock than when it was first | |
# observed, the lock was successfully acquired. Otherwise it tries again. | |
# | |
# This is using the client process's PID as the lock value. In a | |
# distributed system, you'd want to use something more unique, probably MAC | |
# address and PID/thread ID. | |
def acquire(filename) | |
rev = nil | |
holder = connection.get(filename) | |
while rev.nil? | |
if holder.value == '' | |
resp = connection.set(filename, Process.pid.to_s, holder.rev) | |
if resp.err_code.nil? && resp.rev > holder.rev | |
rev = resp.rev | |
else | |
end | |
else | |
sleep 1 | |
holder = connection.get(filename) | |
end | |
end | |
rev | |
end | |
# Surrender the lock. Blindly assumes we really have the lock and deletes | |
# the file, passing along the revision so doozerd can verify we really own | |
# the lock. | |
def release(filename, rev) | |
resp = connection.del(filename, rev) | |
end | |
# Just a helper, nothing interesting here. | |
def connection | |
Locker.connection | |
end | |
end | |
if __FILE__ == $PROGRAM_NAME | |
# Connect to Doozerd with the non-EM, blocking driver. | |
Locker.connection = Fraggle::Block.connect | |
# Run until a signal is received. | |
while $run do | |
# Use the doozerd file '/example' as our coordination point. | |
Locker::Lock.with_lock('/example') do | |
# This block is executed whenever we hold the lock. Presumably we'd do | |
# something fancier than writing to STDOUT in a real system. | |
5.times { puts "Doing work"; sleep 1 } | |
end | |
# Sleeping here gives other clients a chance to acquire the lock. Not sure | |
# if doozerd has primitives that make it possible for other clients to | |
# watch the lock file while avoiding the herd effect. | |
sleep 1 | |
end | |
# Once we're done running, disconnect from doozerd. | |
Locker.connection.disconnect | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment