Class: Kaal::Backend::MySQLAdapter

Inherits:
Adapter
  • Object
show all
Includes:
DispatchLogging
Defined in:
lib/kaal/backend/mysql_adapter.rb

Overview

Distributed backend adapter using MySQL named locks (GET_LOCK/RELEASE_LOCK).

This adapter uses MySQL’s GET_LOCK and RELEASE_LOCK functions for distributed locking across multiple nodes. Locks are connection-based and automatically released when the database connection is closed.

**IMPORTANT LIMITATIONS:**

  • Locks are connection-scoped: if a process crashes, the lock persists until the database connection timeout occurs (typically 28,800 seconds or 8 hours). For critical systems, consider monitoring stale locks or using a time-based fallback mechanism.

  • MySQL named locks have a maximum length of 64 characters. Lock keys longer than 64 characters use a deterministic hash-based shortening scheme (prefix + SHA256 digest) to avoid collisions while respecting the limit.

  • Uses non-blocking acquisition: GET_LOCK is called with timeout=0 for immediate return (does not block waiting for the lock).

  • Ensure connection pooling is properly configured to release connections promptly when processes terminate.

Optionally logs all dispatch attempts to the database when enable_log_dispatch_registry is enabled in configuration.

Examples:

Using the MySQL adapter

Kaal.configure do |config|
  config.backend = Kaal::Backend::MySQLAdapter.new
  config.enable_log_dispatch_registry = true  # Enable dispatch logging
end

Constant Summary collapse

MAX_LOCK_NAME_LENGTH =

MySQL named locks have a maximum length of 64 characters

64

Instance Method Summary collapse

Methods included from DispatchLogging

#log_dispatch_attempt, #parse_lock_key, parse_lock_key

Methods inherited from Adapter

#with_lock

Constructor Details

#initializeMySQLAdapter

Returns a new instance of MySQLAdapter.



49
50
51
52
53
# File 'lib/kaal/backend/mysql_adapter.rb', line 49

def initialize
  super
  @lock_name_length_limit = MAX_LOCK_NAME_LENGTH
  @false_value_pattern = /\A(0|f|false|)\z/i
end

Instance Method Details

#acquire(key, _ttl) ⇒ Boolean

Attempt to acquire a distributed lock using MySQL GET_LOCK.

Uses MySQL’s GET_LOCK(name, timeout) function with a timeout of 0 seconds to perform non-blocking acquisition. If successful, logs the dispatch attempt when enable_log_dispatch_registry is enabled.

Note: The ttl parameter is ignored. MySQL named locks are connection-based and do not have automatic expiration. The lock will be held until explicitly released or the database connection is closed. See class documentation for limitations.

Parameters:

  • key (String)

    the lock key (format: “namespace:dispatch:cron_key:fire_time”)

  • ttl (Integer)

    time-to-live in seconds (ignored; see class docs)

Returns:

  • (Boolean)

    true if acquired, false if held by another process



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/kaal/backend/mysql_adapter.rb', line 89

def acquire(key, _ttl)
  lock_name = normalize_lock_name(key)

  # GET_LOCK returns 1 on success, 0 on timeout, NULL on error
  sql = ActiveRecord::Base.sanitize_sql_array(['SELECT GET_LOCK(?, 0) as lock_result', lock_name])
  result_set = ActiveRecord::Base.connection.execute(sql)
  # Convert result to array and get first row, then first column value
  result_row = result_set.to_a.first
  result_value = result_row.is_a?(Hash) ? result_row['lock_result'] : result_row&.first
  acquired = cast_to_boolean(result_value)

  log_dispatch_attempt(key) if acquired

  acquired
rescue StandardError => e
  raise LockAdapterError, "MySQL acquire failed for #{key}: #{e.message}"
end

#definition_registryKaal::Definition::DatabaseEngine

Get the definition registry for database-backed definition persistence.

Returns:



70
71
72
# File 'lib/kaal/backend/mysql_adapter.rb', line 70

def definition_registry
  @definition_registry ||= Kaal::Definition::DatabaseEngine.new
end

#dispatch_registryKaal::Dispatch::DatabaseEngine

Get the dispatch registry for database logging.

Returns:



62
63
64
# File 'lib/kaal/backend/mysql_adapter.rb', line 62

def dispatch_registry
  @dispatch_registry ||= Kaal::Dispatch::DatabaseEngine.new
end

#release(key) ⇒ Boolean

Release a distributed lock held by MySQL GET_LOCK.

Parameters:

  • key (String)

    the lock key

Returns:

  • (Boolean)

    true if released, false if not held



112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/kaal/backend/mysql_adapter.rb', line 112

def release(key)
  lock_name = normalize_lock_name(key)

  # RELEASE_LOCK returns 1 if held and released, 0 if not held, NULL on error
  sql = ActiveRecord::Base.sanitize_sql_array(['SELECT RELEASE_LOCK(?) as lock_result', lock_name])
  result_set = ActiveRecord::Base.connection.execute(sql)
  # Convert result to array and get first row, then first column value
  result_row = result_set.to_a.first
  result_value = result_row.is_a?(Hash) ? result_row['lock_result'] : result_row&.first
  cast_to_boolean(result_value)
rescue StandardError => e
  raise LockAdapterError, "MySQL release failed for #{key}: #{e.message}"
end