Module: MegaMutex

Defined in:
lib/mega_mutex.rb,
lib/mega_mutex/distributed_mutex.rb

Overview

Why

Sometimes I need to do this:

unless enough_things?
  make_more_things
end

If I’m running several processes in parallel, I can get a race condition that means two of the processes both think there are not enough things. So we go and make some more, even though we don’t need to.

How

Suppose you have a ThingMaker:

class ThingMaker
  include MegaMutex

  def ensure_just_enough_things  
    with_cross_process_mutex("ThingMaker Mutex ID") do
      unless enough_things?
        make_more_things
      end
    end
  end
end

Now, thanks to the magic of MegaMutex, you can be sure that all processes trying to run this code will wait their turn, so each one will have the chance to make exactly the right number of things, without anyone else poking their nose in.

Configuration

MegaMutex uses github.com/mperham/dalli to store the mutex, so your infrastructure must be set up to use memcache servers.

By default, MegaMutex will attempt to connect to either ENV or localhost, but you can configure any number of servers like so:

MegaMutex.configure do |config|
  config.memcache_servers = ['mc1', 'mc2']
end

Defined Under Namespace

Classes: Configuration, DistributedMutex, TimeoutError

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.configurationObject



101
102
103
# File 'lib/mega_mutex.rb', line 101

def configuration
  @configuration ||= Configuration.new
end

.configure {|configuration| ... } ⇒ Object

Yields:



97
98
99
# File 'lib/mega_mutex.rb', line 97

def configure
  yield configuration
end

.get_current_lock(mutex_id) ⇒ Object



44
45
46
# File 'lib/mega_mutex.rb', line 44

def self.get_current_lock(mutex_id)
  DistributedMutex.new(mutex_id).current_lock
end

Instance Method Details

#mega_mutex_insert_into_backtrace(exception, re, newline) ⇒ Object

inserts a line into a backtrace at the correct location



75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/mega_mutex.rb', line 75

def mega_mutex_insert_into_backtrace(exception, re, newline)
  loc = nil
  exception.backtrace.each_with_index do |line, index|
    if line =~ re
      loc = index
      break
    end
  end
  if loc
    exception.backtrace.insert(loc, newline)
  end
end

#with_distributed_mutex(mutex_id, options = {}, &block) ⇒ Object Also known as: with_cross_process_mutex

Wraps code that should only be run when the mutex has been obtained.

The mutex_id uniquely identifies the section of code being run.

You can optionally specify a :timeout to control how long to wait for the lock to be released before raising a MegaMutex::TimeoutError

with_distributed_mutex('my_mutex_id_1234', :timeout => 20) do
  do_something!
end


59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/mega_mutex.rb', line 59

def with_distributed_mutex(mutex_id, options = {}, &block)
  mutex = DistributedMutex.new(mutex_id, options[:timeout])
  begin
    mutex.run(&block)
  rescue Object => e
    mega_mutex_insert_into_backtrace(
      e, 
      /mega_mutex\.rb.*with_(distributed|cross_process)_mutex/, 
      "MegaMutex lock #{mutex_id}"
    )
    raise e
  end
end