Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Added locking class

  • Loading branch information...
commit 9bbe06c664d733ed4aee9ef52ebef3850b8cf586 1 parent b8240a4
Tobias Lütke authored July 20, 2010

Showing 2 changed files with 110 additions and 0 deletions. Show diff stats Hide diff stats

  1. 43  lock/lock.rb
  2. 67  lock/test.rb
43  lock/lock.rb
... ...
@@ -0,0 +1,43 @@
  1
+require 'rubygems'
  2
+require 'redis'
  3
+require 'digest/md5'
  4
+require 'active_support'
  5
+
  6
+$redis = Redis.new
  7
+
  8
+class Lock
  9
+  class Error < StandardError
  10
+  end
  11
+  
  12
+  def self.acquire(key, lock_time = 1.second)    
  13
+    start_time = Time.now.to_i
  14
+    loop do 
  15
+      now = Time.now.to_i
  16
+      
  17
+      if $redis.setnx(key, now + lock_time)
  18
+        begin
  19
+          yield
  20
+          return
  21
+        ensure
  22
+          $redis.del(key)
  23
+        end
  24
+      
  25
+      else
  26
+        time = $redis.get(key)
  27
+        if time.to_i < now
  28
+          if $redis.getset(key, now + lock_time) == time           
  29
+            $redis.del(key)
  30
+          end
  31
+        else                    
  32
+          
  33
+          # Give up after 3x lock_time
  34
+          if now > start_time + (lock_time * 3) 
  35
+            raise Error, 'could not aquire lock'
  36
+          end
  37
+          sleep 0.001
  38
+        end         
  39
+      end          
  40
+    end
  41
+  end
  42
+end
  43
+ 
67  lock/test.rb
... ...
@@ -0,0 +1,67 @@
  1
+require "test/unit"
  2
+
  3
+require File.dirname(__FILE__) + "/lock"
  4
+
  5
+class TestLock < Test::Unit::TestCase
  6
+  def setup
  7
+    $redis.flushall
  8
+  end
  9
+  
  10
+  def test_lock    
  11
+    run = false
  12
+    Lock.acquire 'a' do
  13
+      run = true
  14
+    end
  15
+    
  16
+    assert run
  17
+  end
  18
+  
  19
+  def test_expired_lock_is_overtaken    
  20
+    $redis['a'] = 5.minutes.ago.to_i
  21
+    
  22
+    run = false
  23
+    Lock.acquire 'a' do
  24
+      run = true
  25
+    end    
  26
+    assert run        
  27
+  end
  28
+  
  29
+  def test_cannot_get_lock
  30
+  
  31
+    $redis['b'] = 5.minutes.from_now.to_i
  32
+    
  33
+    assert_raise(Lock::Error) do
  34
+      Lock.acquire 'b' do
  35
+        raise 'should not happen'
  36
+      end    
  37
+    end
  38
+  end
  39
+  
  40
+  def test_lock_exclusive_access
  41
+    
  42
+    run = false
  43
+    
  44
+    results = []
  45
+    
  46
+    a = Thread.new do
  47
+      Lock.acquire('a', 1.minute) do
  48
+        sleep 5
  49
+        results << 'first'
  50
+      end    
  51
+    end
  52
+    
  53
+    b = Thread.new do
  54
+      sleep 1
  55
+
  56
+      Lock.acquire('a', 1.minute) do
  57
+        results << 'second'
  58
+      end
  59
+    
  60
+    end
  61
+    
  62
+
  63
+    a.join; b.join
  64
+    
  65
+    assert_equal ['first', 'second'], results
  66
+  end
  67
+end

0 notes on commit 9bbe06c

Please sign in to comment.
Something went wrong with that request. Please try again.