# frozen_string_literal: true module Dynflow module Executors module Sidekiq module RedisLocking REDIS_LOCK_KEY = 'dynflow_orchestrator_uuid' REDIS_LOCK_TTL = 60 REDIS_LOCK_POLL_INTERVAL = 15 ACQUIRE_OK = 0 ACQUIRE_MISSING = 1 ACQUIRE_TAKEN = 2 RELEASE_SCRIPT = <<~LUA if redis.call("get", KEYS[1]) == ARGV[1] then redis.call("del", KEYS[1]) end return #{ACQUIRE_OK} LUA REACQUIRE_SCRIPT = <<~LUA if redis.call("exists", KEYS[1]) == 1 then local owner = redis.call("get", KEYS[1]) if owner == ARGV[1] then redis.call("set", KEYS[1], ARGV[1], "XX", "EX", #{REDIS_LOCK_TTL}) return #{ACQUIRE_OK} else return #{ACQUIRE_TAKEN} end else redis.call("set", KEYS[1], ARGV[1], "NX", "EX", #{REDIS_LOCK_TTL}) return #{ACQUIRE_MISSING} end LUA def release_orchestrator_lock ::Sidekiq.redis { |conn| conn.eval RELEASE_SCRIPT, [REDIS_LOCK_KEY], [@world.id] } end def wait_for_orchestrator_lock mode = nil loop do active = ::Sidekiq.redis do |conn| conn.set(REDIS_LOCK_KEY, @world.id, :ex => REDIS_LOCK_TTL, :nx => true) end break if active if mode.nil? mode = :passive @logger.info('Orchestrator lock already taken, entering passive mode.') end sleep REDIS_LOCK_POLL_INTERVAL end @logger.info('Acquired orchestrator lock, entering active mode.') end def reacquire_orchestrator_lock case ::Sidekiq.redis { |conn| conn.eval REACQUIRE_SCRIPT, [REDIS_LOCK_KEY], [@world.id] } when ACQUIRE_MISSING @logger.error('The orchestrator lock was lost, reacquired') when ACQUIRE_TAKEN owner = ::Sidekiq.redis { |conn| conn.get REDIS_LOCK_KEY } @logger.fatal("The orchestrator lock was stolen by #{owner}, aborting.") Process.kill('INT', Process.pid) end end end end end end