Skip to content

A Ruby code block wrapper to help prevent race conditions across multiple threads and even separate servers.

License

Notifications You must be signed in to change notification settings

joeyparis/race_block

Repository files navigation

RaceBlock

Gem Version Ruby Master Test Coverage Develop Test Coverage Maintainability

A Ruby code block wrapper to help prevent race conditions across multiple threads and even separate servers.

Disclaimer This code has been used in production for several years now without incident, but I make no guarantee about the thread-safeness it has. Use at your own risk.

Concept

Problem

Multiple servers all have the same cron jobs, but we only want one of them to actually execute the job.

Solution

Since all the cron jobs should fire at roughly the same time, we can have each one of them generate a unique identifier and "claim" the cron job. We wait 0.5 seconds (or sleep_delay) for all of the servers to generate a unique identifier and claim the job by writing their unique identifier to a Redis cache. After the 0.5 seconds, each server checks the Redis cache to see if the stored value matches the one they generated. Only one server will still have a match and successfully claim the job to execute.

Installation

Add this line to your application's Gemfile:

gem 'race_block'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install race_block

Requirements:

  • Ruby >= 2.5
  • A Redis server

Potential Use Cases

The original inspiration behind this gem was running cron jobs across multiple servers to send emails. Since all of the servers would trigger the cron job simultaneously, they would all attempt to send the same email at the same time leading to multiple copies of a single email being sent out. RaceBlock solved this problem by ensuring only one instance of RaceBlock across all the servers was allowed to work on a single email.

RaceBlock could also apply to any cron jobs or block of code that really should only be run once, regardless of which servers tried to initiate it.

Usage

Before calling RaceBlock.start you'll need to configure your own redis client in RaceBlock.config

RaceBlock.config do |config|
    config.redis = Redis.new
end

# or...

RaceBlock.config.redis = Redis.new

Any code that you want to be "thread-safe" and ensure is only executing in one location should be placed in a RaceBlock with a unique identifying key that will be checked across all instances.

RaceBlock.start('unique_key', **kwargs) do
    # Insert code that should only be executed once at a time here...
end

Configuration

Option Default Description
sleep_delay 0.5 How many seconds the RaceBlock should wait after generating its unique token before it checks if it can execute the RaceBlock. Important This value should be longer than the amount of time it takes your server to write to the Redis database. 0.5 seconds has worked for us, but longer sleep_delay values should technically be safer.
expire 60 How many seconds the key should be stored for while running. This number should be longer than the length of time the RaceBlock will take to execute once it starts. The key will be deleted 3 seconds after the block completes, regardless of the time left from expire.
expiration_delay 3 How long after block completion before the key is deleted. Setting to 0 will delete it instanttly upon block completion.
desync_tokens 0 TESTING ONLY This is purely for testing purposes to simulate inconsistent request times. It should never be used in a production environment.

Changing default values

You can change the default values using RaceBlock.config, otherwise they can be overridden on a per call basis.

RaceBlock.config do |config|
    config.sleep_delay = 1.5
    config.expire = 14
    config.expiration_delay = 4
end

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/joeyparis/race_block. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

I am also interested in porting this project to multiple languages and would love help in the languages I am less familiar with.

  • Ruby
  • JavaScript
  • Python
  • Go
  • LolCode
  • ..?

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the RaceBlock project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

About

A Ruby code block wrapper to help prevent race conditions across multiple threads and even separate servers.

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published