module RaceBlock

Block for preventing race conditions across multiple threads and instances

Constants

VERSION

Attributes

configuration[RW]

Public Class Methods

client() click to toggle source
# File lib/race_block.rb, line 37
def self.client
  config.redis
end
config() { |configuration| ... } click to toggle source
# File lib/race_block.rb, line 31
def self.config
  self.configuration ||= Configuration.new
  yield(configuration) if block_given?
  configuration
end
key(key) click to toggle source
# File lib/race_block.rb, line 45
def self.key(key)
  "race_block_#{key}"
end
logger() click to toggle source
# File lib/race_block.rb, line 41
def self.logger
  @logger ||= Logger.new($stdout)
end
reset(key) click to toggle source
# File lib/race_block.rb, line 49
def self.reset(key)
  RaceBlock.client.del(RaceBlock.key(key))
end
set_token_and_wait(key, sleep_delay: config.sleep_delay, desync_tokens: 0) click to toggle source
# File lib/race_block.rb, line 81
def self.set_token_and_wait(key, sleep_delay: config.sleep_delay, desync_tokens: 0)
  sleep desync_tokens # Used for testing only
  token = SecureRandom.hex
  RaceBlock.client.set(key, token)
  RaceBlock.client.expire(key, (sleep_delay + 15).round)
  sleep sleep_delay
  # Okay, so I feel like this is pseudo science, but whatever. Our
  # race condition comes from when the same cron job is called by
  # several different server instances at the same time
  # (theoretically) all within the same second (much less really).
  # By waiting a second we can let all the same cron jobs that were
  # called at roughly the exact same time finish their write to the
  # redis cache so that by the time the sleep is over, only one
  # token is still accurate. I'm hesitant to believe this actually
  # works, but I can't find any flaws in the logic at the current
  # moment, and I also believe this is what is keep the EmailQueue
  # stable which seems to have no duplicate sending problems.

  return true if RaceBlock.client.get(@key) == token

  # Token out of sync
  logger.debug("Token out of sync")
  false
end
start(key, expire: config.expire, expiration_delay: config.expiration_delay, **args) { || ... } click to toggle source
# File lib/race_block.rb, line 53
def self.start(key, expire: config.expire, expiration_delay: config.expiration_delay, **args)
  raise("A key must be provided to start a RaceBlock") if key.empty?

  @key = RaceBlock.key(key)

  # Set an expiration for the token if the key is defined but doesn't
  # have an expiration set (happens sometimes if a thread dies early).
  # `-1` means the key is set but does not expire, `-2` means the key is
  # not set
  RaceBlock.client.expire(@key, 10) if RaceBlock.client.ttl(@key) == -1

  # Token already exists
  return logger.debug("Token already exists") if RaceBlock.client.get(@key)

  return unless set_token_and_wait(@key, **args)

  RaceBlock.client.expire(@key, expire)
  logger.debug("Running block")

  r = yield

  # I have lots of internal debates on whether I should full
  # delete the key here or still let it sit for a few seconds
  RaceBlock.client.expire(@key, expiration_delay)

  r
end