Class: Familia::Lock

Inherits:
StringKey show all
Defined in:
lib/familia/data_type/types/lock.rb

Instance Attribute Summary collapse

Attributes included from Settings

#current_key_version, #default_expiration, #delim, #encryption_hkdf_salt, #encryption_hkdf_salt_history, #encryption_keys, #encryption_personalization, #encryption_personalization_history, #logical_database, #prefix, #raise_on_unsaved_parent_write, #schema_path, #schema_validator, #schemas, #strict_write_order, #suffix, #transaction_mode

Instance Method Summary collapse

Methods included from DataType::ScalarBase

#scalar_type?

Methods included from Features::Autoloader

autoload_files, included, normalize_to_config_name

Methods included from DataType::Serialization

#deserialize_value, #deserialize_values, #deserialize_values_with_nil, #serialize_value, #strip_legacy_json_encoding

Methods included from DataType::DatabaseCommands

#current_expiration, #delete!, #echo, #exists?, #expire, #expireat, #move, #persist, #rename, #renamenx, #type

Methods included from DataType::Connection

#dbclient, #dbkey, #uri

Methods included from Connection::Behavior

#connect, #create_dbclient, #multi, #normalize_uri, #pipeline, #pipelined, #transaction, #uri=, #url, #url=

Methods included from Settings

#configure, #default_suffix, #dirty_write_warnings, #dirty_write_warnings=, #pipelined_mode, #pipelined_mode=

Methods included from Base

add_feature, #as_json, #expired?, #expires?, find_feature, #generate_id, #to_json, #to_s, #ttl, #update_expiration, #uuid

Constructor Details

#initialize(*args) ⇒ Lock

Returns a new instance of Lock.



7
8
9
10
# File 'lib/familia/data_type/types/lock.rb', line 7

def initialize(*args)
  super
  @opts[:default] = nil
end

Instance Attribute Details

#features_enabledObject (readonly) Originally defined in module Features

Returns the value of attribute features_enabled.

#logical_database(val = nil) ⇒ Object Originally defined in module DataType::ClassMethods

#parentObject Originally defined in module DataType::ClassMethods

Returns the value of attribute parent.

#prefixObject Originally defined in module DataType::ClassMethods

Returns the value of attribute prefix.

#suffixObject Originally defined in module DataType::ClassMethods

Returns the value of attribute suffix.

#uri(val = nil) ⇒ Object Originally defined in module DataType::ClassMethods

Returns the value of attribute uri.

Instance Method Details

#acquire(token = nil, ttl: 10) ⇒ String, false

Acquire a lock with optional TTL

With a positive ttl this is a single atomic SET NX EX command, so the value and its expiry land together -- a crash can never strand a permanent TTL-less lock (previously SETNX followed by EXPIRE).

Parameters:

  • token (String) (defaults to: nil)

    Unique token to identify lock holder (auto-generated if nil)

  • ttl (Integer, nil) (defaults to: 10)

    Time-to-live in seconds. nil = no expiration, <=0 rejected

Returns:

  • (String, false)

    Returns token if acquired successfully, false otherwise

Raises:

  • (Familia::OperationModeError)

    inside a transaction or pipeline, where the SET is only queued and returns a Redis::Future -- ownership cannot be decided before EXEC, so a truthy Future would report a lock as acquired while another holder still owns it



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/familia/data_type/types/lock.rb', line 25

def acquire(token = nil, ttl: 10)
  if Fiber[:familia_transaction] || Fiber[:familia_pipeline]
    raise Familia::OperationModeError,
          'Lock#acquire cannot run inside a transaction or pipeline: ' \
          'the NX verdict resolves at EXEC, after the caller has already ' \
          'proceeded. Acquire the lock outside the block.'
  end

  # An explicitly passed nil would serialize to "" -- honor the documented
  # auto-generation contract instead.
  token ||= SecureRandom.uuid

  # Reject invalid TTLs before touching the server
  return false if ttl&.<=(0)

  if ttl
    # redis-rb returns true/false for SET with NX (BoolifySet). Token goes
    # through serialize_value so held_by?/release comparisons stay
    # consistent (identity for plain strings).
    return dbclient.set(dbkey, serialize_value(token), nx: true, ex: ttl) ? token : false
  end

  # nil TTL: setnx applies the :expiration feature's default TTL via
  # update_expiration when present; otherwise the key stays TTL-less.
  success = setnx(token)
  # Handle both integer (1/0) and boolean (true/false) return values
  [1, true].include?(success) ? token : false
end

#force_unlock!Object



68
69
70
# File 'lib/familia/data_type/types/lock.rb', line 68

def force_unlock!
  del
end

#held_by?(token) ⇒ Boolean

Returns:

  • (Boolean)


64
65
66
# File 'lib/familia/data_type/types/lock.rb', line 64

def held_by?(token)
  value == token
end

#locked?Boolean

Returns:

  • (Boolean)


60
61
62
# File 'lib/familia/data_type/types/lock.rb', line 60

def locked?
  !value.nil?
end

#release(token) ⇒ Object



54
55
56
57
58
# File 'lib/familia/data_type/types/lock.rb', line 54

def release(token)
  # Lua script to atomically check token and delete
  script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"
  dbclient.eval(script, [dbkey], [token]) == 1
end