Class: Redstruct::Lock

Inherits:
Factory::Object show all
Includes:
Utils::Coercion, Utils::Scriptable
Defined in:
lib/redstruct/lock.rb

Overview

Implementation of a simple binary lock (locked/not locked), with option to block and wait for the lock. Uses two redis structures: a string for the lease, and a list for blocking operations.

Constant Summary collapse

DEFAULT_EXPIRY =

The default expiry on the underlying redis keys, in seconds; can be between 0 and 1 as a float for milliseconds

1
DEFAULT_TIMEOUT =

The default timeout when blocking, in seconds

nil

Instance Attribute Summary collapse

Attributes inherited from Factory::Object

#factory

Instance Method Summary collapse

Methods included from Utils::Coercion

coerce_array, coerce_bool

Methods included from Utils::Scriptable

included

Methods inherited from Factory::Object

#connection

Methods included from Utils::Inspectable

#inspect

Constructor Details

#initialize(resource, expiry: DEFAULT_EXPIRY, timeout: DEFAULT_TIMEOUT, **options) ⇒ Lock

Returns a new instance of Lock.

Parameters:

  • resource (String)

    the name of the resource to be locked (or ID)

  • expiry (Integer) (defaults to: DEFAULT_EXPIRY)

    in seconds; to prevent infinite locking, you should pass a minimum expiry; you can pass 0 if you want to control it yourself

  • timeout (Integer) (defaults to: DEFAULT_TIMEOUT)

    in seconds; if > 0, will block when trying to obtain the lock; if 0, blocks indefinitely; if nil, does not block



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/redstruct/lock.rb', line 36

def initialize(resource, expiry: DEFAULT_EXPIRY, timeout: DEFAULT_TIMEOUT, **options)
  super(**options)

  @resource = resource
  @token = nil
  @expiry = expiry
  @timeout = case timeout
  when nil then nil
  when Float::INFINITY then 0
  else
    timeout.to_i
  end

  factory = @factory.factory(@resource)
  @lease = factory.string('lease')
  @tokens = factory.list('tokens')
end

Instance Attribute Details

#expiryFloat, Integer (readonly)

Returns the expiry of the underlying redis structure in seconds.

Returns:

  • (Float, Integer)

    the expiry of the underlying redis structure in seconds



28
29
30
# File 'lib/redstruct/lock.rb', line 28

def expiry
  @expiry
end

#resourceString (readonly)

Returns the resource name (or ID of the lock).

Returns:

  • (String)

    the resource name (or ID of the lock)



22
23
24
# File 'lib/redstruct/lock.rb', line 22

def resource
  @resource
end

#timeoutInteger (readonly)

Returns if greater than 0, will block until timeout is reached or the lock is acquired.

Returns:

  • (Integer)

    if greater than 0, will block until timeout is reached or the lock is acquired



31
32
33
# File 'lib/redstruct/lock.rb', line 31

def timeout
  @timeout
end

#tokenString (readonly)

Returns the current token.

Returns:

  • (String)

    the current token



25
26
27
# File 'lib/redstruct/lock.rb', line 25

def token
  @token
end

Instance Method Details

#acquireBoolean

Attempts to acquire the lock. First attempts to grab the lease (a redis string). If the current token is already the lease token, the lock is considered acquired. If there is no current lease, then sets it to the current token. If there is a current lease that is not the current token, then:

1) If this not a blocking lock (see Lock#blocking?), return false
2) If this is a blocking lock, block and wait for the next token to be pushed on the tokens list
3) If a token was pushed, set it as our token and refresh the expiry

Returns:

  • (Boolean)

    True if acquired, false otherwise



90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/redstruct/lock.rb', line 90

def acquire
  acquired = false

  token = non_blocking_acquire
  token = blocking_acquire if token.nil? && blocking?

  unless token.nil?
    @lease.expire(@expiry)
    @token = token
    acquired = true
  end

  return acquired
end

#blocking?Boolean

Whether or not the lock will block when attempting to acquire it

Returns:

  • (Boolean)


78
79
80
# File 'lib/redstruct/lock.rb', line 78

def blocking?
  return !@timeout.nil?
end

#deleteBoolean

Deletes all traces of this lock

Returns:

  • (Boolean)

    true if deleted, false otherwise



56
57
58
# File 'lib/redstruct/lock.rb', line 56

def delete
  return coerce_bool(delete_script(keys: [@lease.key, @tokens.key]))
end

#locked { ... } ⇒ Object

Executes the given block if the lock can be acquired

Yields:

  • Block to be executed if the lock is acquired



62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/redstruct/lock.rb', line 62

def locked
  Thread.handle_interrupt(Exception => :never) do
    begin
      if acquire
        Thread.handle_interrupt(Exception => :immediate) do
          yield
        end
      end
    ensure
      release
    end
  end
end

#releaseBoolean

Releases the lock only if the current token is the value of the lease. If the lock is a blocking lock (see Lock#blocking?), push the next token on the tokens list.

Returns:

  • (Boolean)

    True if released, false otherwise



108
109
110
111
112
113
114
115
116
117
118
# File 'lib/redstruct/lock.rb', line 108

def release
  return false if @token.nil?

  keys = [@lease.key, @tokens.key]
  argv = [@token, generate_token, (@expiry.to_f * 1000).floor]

  released = release_script(keys: keys, argv: argv)
  @token = nil

  return coerce_bool(released)
end