Module: WithAdvisoryLock::MySQLAdvisory

Extended by:
ActiveSupport::Concern
Defined in:
lib/with_advisory_lock/mysql_advisory.rb

Instance Method Summary collapse

Instance Method Details

#advisory_lock_exists_for?(lock_name, shared: false) ⇒ Boolean

Non-blocking check via IS_USED_LOCK, no lock is acquired as a side effect

Returns:

  • (Boolean)


60
61
62
63
64
65
66
67
# File 'lib/with_advisory_lock/mysql_advisory.rb', line 60

def advisory_lock_exists_for?(lock_name, shared: false)
  raise ArgumentError, 'shared locks are not supported on MySQL' if shared

  lock_keys = lock_keys_for(lock_name)
  !query_value("SELECT IS_USED_LOCK(#{quote(lock_keys.first)})").nil?
rescue ActiveRecord::StatementInvalid
  nil
end

#lock_keys_for(lock_name) ⇒ Object



50
51
52
53
# File 'lib/with_advisory_lock/mysql_advisory.rb', line 50

def lock_keys_for(lock_name)
  lock_str = "#{ENV.fetch(CoreAdvisory::LOCK_PREFIX_ENV, nil)}#{lock_name}"
  [lock_str]
end

#release_advisory_lock(*args, **kwargs) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/with_advisory_lock/mysql_advisory.rb', line 33

def release_advisory_lock(*args, **kwargs)
  # Handle both signatures - ActiveRecord's built-in and ours
  if args.length == 1 && kwargs.empty?
    # ActiveRecord's signature: release_advisory_lock(lock_id)
    # Called by Rails migrations with a single positional argument
    super
  else
    # Our signature: release_advisory_lock(lock_keys, lock_name:, **)
    lock_keys = args.first
    execute_successful?("RELEASE_LOCK(#{quote(lock_keys.first)})")
  end
rescue ActiveRecord::StatementInvalid => e
  # If the connection is broken, the lock is automatically released by MySQL
  # No need to fail the release operation
  raise unless connection_lost_error?(e)
end

#supports_database_timeout?Boolean

Returns:

  • (Boolean)


55
56
57
# File 'lib/with_advisory_lock/mysql_advisory.rb', line 55

def supports_database_timeout?
  true
end

#try_advisory_lock(lock_keys, lock_name:, shared:, transaction:, timeout_seconds: nil, blocking: false) ⇒ Object

Raises:

  • (ArgumentError)


9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/with_advisory_lock/mysql_advisory.rb', line 9

def try_advisory_lock(lock_keys, lock_name:, shared:, transaction:, timeout_seconds: nil, blocking: false)
  raise ArgumentError, 'shared locks are not supported on MySQL' if shared
  raise ArgumentError, 'transaction level locks are not supported on MySQL' if transaction

  # Note: blocking parameter is accepted for API compatibility but ignored for MySQL
  # MySQL's GET_LOCK already provides native timeout support, making the blocking
  # parameter redundant. MySQL doesn't have separate try/blocking functions like PostgreSQL.

  # MySQL/MariaDB GET_LOCK supports native timeout:
  # - timeout_seconds = nil: wait indefinitely
  # - timeout_seconds = 0: try once, no wait (0)
  # - timeout_seconds > 0: wait up to timeout_seconds
  #
  # Note: MySQL accepts -1 for infinite wait, but MariaDB does not.
  # Using a large value (1 year) for cross-compatibility.
  mysql_timeout = case timeout_seconds
                  when nil then 31_536_000 # 1 year in seconds
                  when 0 then 0
                  else timeout_seconds.to_i
                  end

  execute_successful?("GET_LOCK(#{quote(lock_keys.first)}, #{mysql_timeout})")
end