redis-em-mutex

Author

Rafał Michalski ([email protected])

DESCRIPTION

redis-em-mutex is the cross server-process-fiber EventMachine + Redis based semaphore.

FEATURES

  • only for EventMachine

  • no CPU-intensive sleep/polling while waiting for lock to become available

  • fibers waiting for the lock are signalled via Redis channel as soon as the lock has been released (~< 1 ms)

  • multi-locks (all-or-nothing) locking (to prevent possible deadlocks when multiple semaphores are required to be locked at once)

  • best served with EM-Synchrony (uses EM::Synchrony::ConnectionPool internally)

  • fiber-safe

  • deadlock detection (only trivial cases: locking twice the same resource from the same fiber)

  • mandatory lock expiration (with refreshing)

BUGS/LIMITATIONS

  • only for EventMachine

  • NOT thread-safe

  • locking order between concurrent processes is undetermined (no FIFO)

  • it’s not nifty, rather somewhat complicated

REQUIREMENTS

INSTALL

$ [sudo] gem install redis-em-mutex

Gemfile

gem "redis-em-mutex", "~> 0.1.1"

Github

git clone git://github.com/royaltm/redis-em-mutex.git

USAGE

require 'em-synchrony'
require 'em-redis-mutex'

Redis::EM::Mutex.setup(size: 10, url: 'redis:///1', expire: 600)

# or

Redis::EM::Mutex.setup do |opts|
  opts.size = 10
  opts.url = 'redis:///1'
  ...
end

EM.synchrony do
  Redis::EM::Mutex.synchronize('resource.lock') do
    ... do something with resource
  end

  # or 

  mutex = Redis::EM::Mutex.new('resource.lock')
  mutex.synchronize do
    ... do something with resource
  end

  # or

  begin
    mutex.lock
    ... do something with resource
  ensure
    mutex.unlock
  end

  ...

  Redis::EM::Mutex.stop_watcher
  EM.stop
end

Namespaces

Redis::EM::Mutex.setup(ns: 'my_namespace', ....)

# or multiple namespaces:

ns = Redis::EM::Mutex::NS.new('my_namespace')

EM.synchrony do
  ns.synchronize('foo') do
    .... do something with foo and bar
  end
  ...
  EM.stop
end

Multi-locks

EM.synchrony do
  Redis::EM::Mutex.synchronize('foo', 'bar', 'baz') do
    .... do something with foo, bar and baz
  end
  ...
  EM.stop
end

Locking options

EM.synchrony do
  begin
    Redis::EM::Mutex.synchronize('foo', 'bar', block: 0.25) do
      .... do something with foo and bar
    end
  rescue Redis::EM::Mutex::MutexTimeout
    ... locking timed out
  end

  Redis::EM::Mutex.synchronize('foo', 'bar', expire: 60) do |mutex|
    .... do something with foo and bar in less than 60 seconds
    if mutex.refresh(120)
      # now we have additional 120 seconds until lock expires
    else
      # too late
    end
  end

  ...
  EM.stop
end

Advanced

mutex = Redis::EM::Mutex.new('resource1', 'resource2', expire: 60)

EM.synchrony do
  mutex.lock

  EM.fork_reactor do
    Fiber.new do
      mutex.locked? # true
      mutex.owned?  # false
      mutex.synchronize do
        mutex.locked? # true
        mutex.owned?  # true

        ....
      end
      ...
      Redis::EM::Mutex.stop_watcher
      EM.stop
    end.resume
  end

  mutex.locked? # true
  mutex.owned?  # true

  mutex.unlock
  mutex.owned?  # false

  ...
  Redis::EM::Mutex.stop_watcher
  EM.stop
end

LICENCE

The MIT License - Copyright © 2012 Rafał Michalski