Class: NoBrainer::Lock

Inherits:
Object
  • Object
show all
Includes:
Document
Defined in:
lib/no_brainer/lock.rb

Direct Known Subclasses

ReentrantLock

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Autoload

#autoload, #autoload_and_include, #eager_autoload, #eager_load!, extended

Constructor Details

#initialize(key, options = {}) ⇒ Lock

Returns a new instance of Lock.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/no_brainer/lock.rb', line 21

def initialize(key, options={})
  if options[:from_db]
    super
    # We reset our instance_token to allow recoveries.
    self.instance_token = get_new_instance_token
  else
    @default_options = options.slice(:expire, :timeout)
    options.delete(:expire); options.delete(:timeout);

    key = key.to_s if key.is_a?(Symbol)
    super(options.merge(:key => key))
    raise ArgumentError unless valid?
  end
end

Class Method Details

.find(key) ⇒ Object



17
18
19
# File 'lib/no_brainer/lock.rb', line 17

def self.find(key)
  super(Digest::SHA1.base64digest(key.to_s))
end

Instance Method Details

#deleteObject

Raises:

  • (NotImplementedError)


116
# File 'lib/no_brainer/lock.rb', line 116

def delete(*); raise NotImplementedError; end

#get_new_instance_tokenObject



36
37
38
# File 'lib/no_brainer/lock.rb', line 36

def get_new_instance_token
  NoBrainer::Document::PrimaryKey::Generator.generate
end

#lock(options = {}) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/no_brainer/lock.rb', line 49

def lock(options={})
  options.assert_valid_keys(:expire, :timeout)
  timeout = get_option_value(options, :timeout)
  sleep_amount = 0.1

  start_at = Time.now
  loop do
    return if try_lock(options.slice(:expire))
    raise_lock_unavailable! if Time.now - start_at + sleep_amount > timeout
    sleep(sleep_amount)
    sleep_amount = [1, sleep_amount * 2].min
  end
end

#refresh(options = {}) ⇒ Object



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/no_brainer/lock.rb', line 93

def refresh(options={})
  options.assert_valid_keys(:expire)
  raise_unless_locked!

  set_expiration(options.merge(:use_previous_expire => true))

  result = NoBrainer.run do |r|
    selector.update do |doc|
      r.branch(doc[:instance_token].eq(self.instance_token),
               { :expires_at => self.expires_at }, nil)
    end
  end

  # Note: If we are too quick, expires_at may not change, and the returned
  # 'replaced' won't be 1. We'll generate a spurious error. This is very
  # unlikely to happen and should not harmful.
  unless result['replaced'] == 1
    @locked = false
    raise_lost_lock!
  end
end

#save?Boolean

Returns:

Raises:

  • (NotImplementedError)


115
# File 'lib/no_brainer/lock.rb', line 115

def save?(*);  raise NotImplementedError; end

#synchronize(options = {}, &block) ⇒ Object



40
41
42
43
44
45
46
47
# File 'lib/no_brainer/lock.rb', line 40

def synchronize(options={}, &block)
  lock(options)
  begin
    block.call
  ensure
    unlock if @locked
  end
end

#try_lock(options = {}) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/no_brainer/lock.rb', line 63

def try_lock(options={})
  options.assert_valid_keys(:expire)
  raise_if_locked!

  set_expiration(options)

  result = NoBrainer.run do |r|
    selector.replace do |doc|
      r.branch(doc.eq(nil).or(doc[:expires_at] < r.now),
               self.attributes, doc)
    end
  end

  return @locked = (result['inserted'] + result['replaced']) == 1
end

#unlockObject



79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/no_brainer/lock.rb', line 79

def unlock
  raise_unless_locked!

  result = NoBrainer.run do |r|
    selector.replace do |doc|
      r.branch(doc[:instance_token].default(nil).eq(self.instance_token),
               nil, doc)
    end
  end

  @locked = false
  raise_lost_lock! unless result['deleted'] == 1
end