Class: ZK::Locker::LockerBase

Inherits:
Object
  • Object
show all
Includes:
Exceptions, ZK::Logger
Defined in:
lib/zk/locker/locker_base.rb

Overview

Common code for the shared and exclusive lock implementations

One thing to note about this implementation is that the API unfortunately does not follow the convention where bang ('!') methods raise exceptions when they fail. This was an oversight on the part of the author, and it may be corrected sometime in the future.

Direct Known Subclasses

ExclusiveLocker, Semaphore, SharedLocker

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from ZK::Logger

#logger, wrapped_logger, wrapped_logger=

Constructor Details

#initialize(client, name, root_lock_node = nil) ⇒ LockerBase

Create a new lock instance.

Parameters:

  • client (Client::Threaded)

    a client instance

  • name (String)

    Unique name that will be used to generate a key. All instances created with the same root_lock_node and name will be holding the same lock.

  • root_lock_node (String) (defaults to: nil)

    the root path on the server under which all locks will be generated, the default is Locker.default_root_lock_node



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/zk/locker/locker_base.rb', line 48

def initialize(client, name, root_lock_node=nil) 
  @zk = client
  @root_lock_node = root_lock_node || Locker.default_root_lock_node

  @path           = name
  @locked         = false
  @waiting        = false
  @lock_path      = nil
  @parent_stat    = nil
  @root_lock_path = "#{@root_lock_node}/#{@path.gsub("/", "__")}"

  @mutex  = Monitor.new
  @cond   = @mutex.new_cond
  @node_deletion_watcher = nil
end

Instance Attribute Details

#lock_pathString (readonly)

our absolute lock node path

Examples:


'/_zklocking/foobar/__blah/lock000000007'

Returns:

  • (String)


24
25
26
# File 'lib/zk/locker/locker_base.rb', line 24

def lock_path
  @lock_path
end

Instance Method Details

#acquirable?Boolean

Note:

It should be obvious, but there is no way to guarantee that between the time this method checks the server and taking any action to acquire the lock, another client may grab the lock before us (or converseley, another client may release the lock). This is simply meant as an advisory, and may be useful in some cases.

  • If this instance holds the lock is true we return true (as we have already succeeded in acquiring the lock)
  • If this instance doesn't hold the lock, we'll do a check on the server to see if there are any participants who hold the lock and would prevent us from acquiring the lock.
    • If this instance could acquire the lock we will return true.
    • If another client would prevent us from acquiring the lock, we return false.

Returns:

  • (Boolean)

Raises:

  • (NotImplementedError)


138
139
140
# File 'lib/zk/locker/locker_base.rb', line 138

def acquirable?
  raise NotImplementedError
end

#assertObject



287
288
289
290
291
292
# File 'lib/zk/locker/locker_base.rb', line 287

def assert
  assert!
  true
rescue LockAssertionFailedError
  false
end

#assert!Object

This is for users who wish to check that the assumption is correct that they actually still hold the lock. (check for session interruption, perhaps a lock is obtained in one method and handed to another)

This, unlike #locked? will actually go and check the conditions that constitute "holding the lock" with the server.

Examples:


def process_jobs
  @lock.with_lock do
    @jobs.each do |j| 
      @lock.assert!
      perform_job(j)
    end
  end
end

def perform_job(j)
  puts "hah! he thinks we're workin!"
  sleep(60)
end

Raises:



277
278
279
280
281
282
283
284
285
# File 'lib/zk/locker/locker_base.rb', line 277

def assert!
  @mutex.synchronize do
    raise LockAssertionFailedError, "have not obtained the lock yet"            unless locked?
    raise LockAssertionFailedError, "lock_path was #{lock_path.inspect}"        unless lock_path
    raise LockAssertionFailedError, "the lock path #{lock_path} did not exist!" unless zk.exists?(lock_path)
    raise LockAssertionFailedError, "the parent node was replaced!"             unless root_lock_path_same?
    raise LockAssertionFailedError, "we do not actually hold the lock"          unless got_lock?
  end
end

#interrupt!Object

interrupt caller blocked on acquring a lock by delegating to ZK::NodeDeletionWatcher#interrupt!

this does nothing if the watcher is not currently blocked.

Raises:



301
302
303
304
305
# File 'lib/zk/locker/locker_base.rb', line 301

def interrupt!
  synchronize do
    @node_deletion_watcher and @node_deletion_watcher.interrupt!
  end
end

#lock(blocking = false) ⇒ true, ... #lock(opts = {}) ⇒ true, ...

Overloads:

  • #lock(blocking = false) ⇒ true, ...
    Deprecated.

    in favor of the options hash style

    Parameters:

    • blocking (true, false) (defaults to: false)

      if true we block the caller until we can obtain a lock on the resource

  • #lock(opts = {}) ⇒ true, ...

    Options Hash (opts):

    • :wait (true, false, Numeric) — default: false

      If true we block the caller until we obtain a lock on the resource. If false, we do not block. If a Numeric, the number of seconds we should wait for the lock to be acquired. Will raise LockWaitTimeoutError if we exceed the timeout.

    Since:

    • 1.7

Returns:

  • (true)

    if we're already obtained a shared lock, or if we were able to obtain the lock in non-blocking mode.

  • (false)

    if we did not obtain the lock in non-blocking mode

  • (void)

    if we obtained the lock in blocking mode.

Raises:

  • (InterruptedSession)

    raised when blocked waiting for a lock and the underlying client's session is interrupted.

  • (LockWaitTimeoutError)

    if the given timeout is exceeded waiting for the lock to be acquired

See Also:



200
201
202
203
204
205
206
207
208
209
# File 'lib/zk/locker/locker_base.rb', line 200

def lock(opts={})
  return true if @mutex.synchronize { @locked }

  case opts
  when TrueClass, FalseClass      # old style boolean argument
    opts = { :wait => opts }
  end

  lock_with_opts_hash(opts)
end

#lock!(opts = {}) ⇒ Object

Deprecated.

the use of lock! is deprecated and may be removed or have its semantics changed in a future release

delegates to #lock



215
216
217
# File 'lib/zk/locker/locker_base.rb', line 215

def lock!(opts={})
  lock(opts)
end

#lock_basenamenil, String

the basename of our lock path

Examples:


> locker.lock_path
# => '/_zklocking/foobar/__blah/lock000000007'
> locker.lock_basename
# => 'lock000000007'

Returns:

  • (nil)

    if lock_path is not set

  • (String)

    last path component of our lock path



101
102
103
# File 'lib/zk/locker/locker_base.rb', line 101

def lock_basename
  synchronize { lock_path and File.basename(lock_path) }
end

#locked?true, false

returns our current idea of whether or not we hold the lock, which does not actually check the state on the server.

The reason for the equivocation around thinking we hold the lock is to contrast our current state and the actual state on the server. If you want to make double-triple certain of the state of the lock, use #assert!

Returns:

  • (true)

    if we hold the lock

  • (false)

    if we don't hold the lock



120
121
122
# File 'lib/zk/locker/locker_base.rb', line 120

def locked?
  synchronize { !!@locked }
end

#unlocktrue, false

Note:

There is more than one way you might not "own the lock" see issue #34

Returns:

  • (true)

    if we held the lock and this method has unlocked it successfully

  • (false)

    if we did not own the lock.



150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/zk/locker/locker_base.rb', line 150

def unlock
  rval = false
  @mutex.synchronize do
    if @locked
      logger.debug { "unlocking" }
      rval = cleanup_lock_path!
      @locked = false
      @node_deletion_watcher = nil
      @cond.broadcast
    end
  end
  rval
end

#unlock!true, false

Deprecated.

the use of unlock! is deprecated and may be removed or have its semantics changed in a future release

Note:

There is more than one way you might not "own the lock" see issue #34

Returns:

  • (true)

    if we held the lock and this method has unlocked it successfully

  • (false)

    if we did not own the lock.



167
168
169
# File 'lib/zk/locker/locker_base.rb', line 167

def unlock!
  unlock
end

#with_lock(opts = {}) {|lock| ... } ⇒ Object

block caller until lock is aquired, then yield

there is no non-blocking version of this method

Parameters:

  • opts (Hash) (defaults to: {})

    a customizable set of options

Options Hash (opts):

  • :wait (Numeric, true) — default: nil

    if non-nil, the amount of time to wait for the lock to be acquired. since with_lock is only blocking, false isn't a valid option. true is ignored (as it is the default). If a Numeric (float or integer) option is given, maximum amount of time to wait for lock acquisition.

Yields:

  • (lock)

    calls the block with the lock instance when acquired

Raises:

  • (LockWaitTimeoutError)

    if the :wait timeout is exceeded

  • (ArgumentError)

    if :wait is false (since you can't do non-blocking)



78
79
80
81
82
83
84
85
86
87
88
# File 'lib/zk/locker/locker_base.rb', line 78

def with_lock(opts={})
  if opts[:wait].kind_of?(FalseClass)
    raise ArgumentError, ":wait cannot be false, with_lock is only used in blocking mode"
  end

  opts = { :wait => true }.merge(opts)
  lock(opts)
  yield self
ensure
  unlock
end