Repository: arturictus/sidekiq_alive
Branch: master
Commit: 6da0cfdadf7b
Files: 42
Total size: 60.1 KB
Directory structure:
gitextract_fpunre1x/
├── .github/
│ ├── dependabot.yml
│ ├── release.yml
│ └── workflows/
│ ├── changelog.yml
│ ├── release.yml
│ └── test.yml
├── .gitignore
├── .rspec
├── .rubocop.yml
├── .ruby-version
├── .tool-versions
├── CODE_OF_CONDUCT.md
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── bin/
│ ├── console
│ └── setup
├── docker-compose.yml
├── lib/
│ ├── sidekiq_alive/
│ │ ├── config.rb
│ │ ├── helpers.rb
│ │ ├── redis/
│ │ │ ├── base.rb
│ │ │ ├── redis_client_gem.rb
│ │ │ └── redis_gem.rb
│ │ ├── redis.rb
│ │ ├── server/
│ │ │ ├── base.rb
│ │ │ ├── default.rb
│ │ │ ├── http_server.rb
│ │ │ └── rack.rb
│ │ ├── server.rb
│ │ ├── version.rb
│ │ └── worker.rb
│ └── sidekiq_alive.rb
├── sidekiq_alive.gemspec
├── spec/
│ ├── config_spec.rb
│ ├── redis_spec.rb
│ ├── server/
│ │ ├── default_spec.rb
│ │ └── rack_spec.rb
│ ├── server_spec.rb
│ ├── sidekiq_alive_spec.rb
│ ├── spec_helper.rb
│ └── worker_spec.rb
└── tasks/
└── version.rake
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "bundler"
directory: "/"
schedule:
interval: "daily"
reviewers:
- "andrcuns"
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: "daily"
reviewers:
- andrcuns
labels:
- "ci"
================================================
FILE: .github/release.yml
================================================
changelog:
categories:
- title: '🚀 New feature or request'
labels:
- 'enhancement'
- title: '🐞 Bug Fixes'
labels:
- 'bug'
- title: '📦 Dependency updates'
labels:
- 'dependencies'
- title: '🧰 Maintenance'
labels:
- 'maintenance'
================================================
FILE: .github/workflows/changelog.yml
================================================
name: Changelog
on:
push:
tags:
- v[0-9]+.[0-9]+.[0-9]+
jobs:
release:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Create GitHub release
uses: softprops/action-gh-release@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
generate_release_notes: true
================================================
FILE: .github/workflows/release.yml
================================================
name: Release
on:
workflow_dispatch:
inputs:
semver:
description: Bump
required: true
type: choice
options:
- major
- minor
- patch
jobs:
release:
name: Ruby gem
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.RELEASE_GITHUB_TOKEN }}
-
name: Set up Ruby 3.3
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
-
name: Update version
run: |
git config user.name github-actions
git config user.email github-actions@github.com
bundle config unset deployment
bundle exec rake "version[${{ inputs.semver }}]" && git push
-
name: Create tag and push to rubygems
run: bundle exec rake release
env:
GEM_HOST_API_KEY: ${{ secrets.GEM_HOST_API_KEY }}
================================================
FILE: .github/workflows/test.yml
================================================
name: Test
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
rubocop:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
cache-version: 1
- name: Run lint
run: bundle exec rubocop --color
test:
runs-on: ubuntu-latest
needs: rubocop
strategy:
fail-fast: false
matrix:
ruby-version: ["3.4", "3.3", "3.2"]
sidekiq-version: ["~> 6.5", "~> 7", "~> 8"]
exclude:
- sidekiq-version: "~> 8"
ruby-version: "3.1"
# Service containers to run with `runner-job`
services:
# Label used to access the service container
redis:
# Docker Hub image
image: redis
# Set health checks to wait until redis has started
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
# Maps port 6379 on service container to the host
- 6379:6379
steps:
- uses: actions/checkout@v4
- name: Set up Ruby ${{ matrix.ruby-version }} with Sidekiq ${{ matrix.sidekiq-version }}
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby-version }}
bundler-cache: true
cache-version: 1
env:
SIDEKIQ_VERSION_RANGE: ${{ matrix.sidekiq-version }}
- name: Run tests
run: bundle exec rspec --force-color
env:
SIDEKIQ_VERSION_RANGE: ${{ matrix.sidekiq-version }}
- name: Add coverage report
uses: insightsengineering/coverage-action@v2
# TODO: Add coverage merging from different test runs
if: ${{ matrix.ruby-version == '3.4' && matrix.sidekiq-version == '~> 7' }}
with:
path: coverage/coverage.xml
publish: true
threshold: 90
pycobertura-exception-failure: false
diff: true
diff-branch: master
coverage-reduction-failure: true
================================================
FILE: .gitignore
================================================
/.bundle/
/.yardoc
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
Gemfile.lock
# rspec failure tracking
.rspec_status
vendor
================================================
FILE: .rspec
================================================
--format documentation
--color
--require spec_helper
--order random
================================================
FILE: .rubocop.yml
================================================
inherit_gem:
rubocop-shopify: rubocop.yml
AllCops:
TargetRubyVersion: 3.1
SuggestExtensions: false
NewCops: enable
Layout/ArgumentAlignment:
Enabled: true
EnforcedStyle: with_first_argument
Style/InvertibleUnlessCondition:
Exclude:
- "lib/sidekiq_alive.rb"
================================================
FILE: .ruby-version
================================================
3.4.4
================================================
FILE: .tool-versions
================================================
ruby 3.4.4
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at arturictus@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
================================================
FILE: Gemfile
================================================
# frozen_string_literal: true
source "https://rubygems.org"
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
# Specify your gem's dependencies in sidekiq_alive.gemspec
gemspec
gem "sidekiq", ENV["SIDEKIQ_VERSION_RANGE"] || "< 9"
gem "ruby-lsp", "~> 0.23.11", group: :development
group :test do
gem "simplecov", require: false
gem "simplecov-cobertura", require: false
# used for testing rack based server
gem "rack-test", "~> 2.2.0"
# rackup is not compatible with sidekiq < 7 due to rack version requirement
if ["7", "8"].any? { |range| ENV["SIDEKIQ_VERSION_RANGE"]&.include?(range) }
gem "rackup", "~> 2.2.0"
else
gem "rack", "< 3"
gem "webrick", "< 2"
end
end
================================================
FILE: LICENSE.txt
================================================
The MIT License (MIT)
Copyright (c) 2018 Artur Pañach
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: README.md
================================================
# SidekiqAlive
[](https://rubygems.org/gems/sidekiq_alive)
[](https://rubygems.org/gems/https://rubygems.org/gems/sidekiq_alive)

---
SidekiqAlive offers a solution to add liveness probe for a Sidekiq instance deployed in Kubernetes.
This library can be used to check sidekiq health outside kubernetes.
**How?**
A http server is started and on each requests validates that a liveness key is stored in Redis. If it is there means is working.
A Sidekiq worker is the responsible to storing this key. If Sidekiq stops processing workers
this key gets expired by Redis an consequently the http server will return a 500 error.
This worker is responsible to requeue itself for the next liveness probe.
Each instance in kubernetes will be checked based on `ENV` variable `HOSTNAME` (kubernetes sets this for each replica/pod).
On initialization SidekiqAlive will asign to Sidekiq::Worker a queue with the current host and add this queue to the current instance queues to process.
example:
```
hostname: foo
Worker queue: sidekiq_alive-foo
instance queues:
- sidekiq_alive-foo
*- your queues
hostname: bar
Worker queue: sidekiq_alive-bar
instance queues:
- sidekiq_alive-bar
*- your queues
```
## Installation
Add this line to your application's Gemfile:
```ruby
gem 'sidekiq_alive'
```
And then execute:
$ bundle
Or install it yourself as:
$ gem install sidekiq_alive
## Usage
SidekiqAlive will start when running `sidekiq` command.
Run `Sidekiq`
```
bundle exec sidekiq
```
```
curl localhost:7433
#=> Alive!
```
**how to disable?**
You can disabled by setting `ENV` variable `DISABLE_SIDEKIQ_ALIVE`
example:
```
DISABLE_SIDEKIQ_ALIVE=true bundle exec sidekiq
```
### Kubernetes setup
Set `livenessProbe` in your Kubernetes deployment
example with recommended setup:
#### Sidekiq < 6
```yaml
spec:
containers:
- name: my_app
image: my_app:latest
env:
- name: RAILS_ENV
value: production
command:
- bundle
- exec
- sidekiq
ports:
- containerPort: 7433
livenessProbe:
httpGet:
path: /
port: 7433
initialDelaySeconds: 80 # app specific. Time your sidekiq takes to start processing.
timeoutSeconds: 5 # can be much less
readinessProbe:
httpGet:
path: /
port: 7433
initialDelaySeconds: 80 # app specific
timeoutSeconds: 5 # can be much less
lifecycle:
preStop:
exec:
# SIGTERM triggers a quick exit; gracefully terminate instead
command: ['bundle', 'exec', 'sidekiqctl', 'quiet']
terminationGracePeriodSeconds: 60 # put your longest Job time here plus security time.
```
#### Sidekiq >= 6
Create file:
_kube/sidekiq_quiet_
```bash
#!/bin/bash
# Find Pid
SIDEKIQ_PID=$(ps aux | grep sidekiq | grep busy | awk '{ print $2 }')
# Send TSTP signal. Note: Alpine Linux needs to use `kill -s SIGTSTP` instead of `kill -TSTP`
kill -SIGTSTP $SIDEKIQ_PID
```
Make it executable:
```
$ chmod +x kube/sidekiq_quiet
```
Execute it in your deployment preStop:
```yaml
spec:
containers:
- name: my_app
image: my_app:latest
env:
- name: RAILS_ENV
value: production
command:
- bundle
- exec
- sidekiq
ports:
- containerPort: 7433
livenessProbe:
httpGet:
path: /
port: 7433
initialDelaySeconds: 80 # app specific. Time your sidekiq takes to start processing.
timeoutSeconds: 5 # can be much less
readinessProbe:
httpGet:
path: /
port: 7433
initialDelaySeconds: 80 # app specific
timeoutSeconds: 5 # can be much less
lifecycle:
preStop:
exec:
# SIGTERM triggers a quick exit; gracefully terminate instead
command: ['kube/sidekiq_quiet']
terminationGracePeriodSeconds: 60 # put your longest Job time here plus security time.
```
### Outside kubernetes
It's just up to you how you want to use it.
An example in local would be:
```
bundle exec sidekiq
# let it initialize ...
```
```
curl localhost:7433
#=> Alive!
```
## Options
```ruby
SidekiqAlive.setup do |config|
# ==> Server host
# Host to bind the server.
# Can also be set with the environment variable SIDEKIQ_ALIVE_HOST.
# default: 0.0.0.0
#
# config.host = 0.0.0.0
# ==> Server port
# Port to bind the server.
# Can also be set with the environment variable SIDEKIQ_ALIVE_PORT.
# default: 7433
#
# config.port = 7433
# ==> Server path
# HTTP path to respond to.
# Can also be set with the environment variable SIDEKIQ_ALIVE_PATH.
# default: '/'
#
# config.path = '/'
# ==> Custom Liveness Probe
# Extra check to decide if restart the pod or not for example connection to DB.
# `false`, `nil` or `raise` will not write the liveness probe
# default: proc { true }
#
# config.custom_liveness_probe = proc { db_running? }
# ==> Liveness key
# Key to be stored in Redis as probe of liveness
# default: "SIDEKIQ::LIVENESS_PROBE_TIMESTAMP"
#
# config.liveness_key = "SIDEKIQ::LIVENESS_PROBE_TIMESTAMP"
# ==> Time to live
# Time for the key to be kept by Redis.
# Here is where you can set de periodicity that the Sidekiq has to probe it is working
# Time unit: seconds
# default: 10 * 60 # 10 minutes
#
# config.time_to_live = 10 * 60
# ==> Callback
# After the key is stored in redis you can perform anything.
# For example a webhook or email to notify the team
# default: proc {}
#
# require 'net/http'
# config.callback = proc { Net::HTTP.get("https://status.com/ping") }
# ==> Shutdown callback
# When sidekiq process is shutting down, you can perform some arbitrary action.
# default: proc {}
#
# config.shutdown_callback = proc { puts "Sidekiq is shutting down" }
# ==> Queue Prefix
# SidekiqAlive will run in a independent queue for each instance/replica
# This queue name will be generated with: "#{queue_prefix}-#{hostname}.
# You can customize the prefix here.
# default: :sidekiq-alive
#
# config.queue_prefix = :other
# ==> Concurrency
# The maximum number of Redis connections requested for the SidekiqAlive pool.
# Can also be set with the environment variable SIDEKIQ_ALIVE_CONCURRENCY.
# NOTE: only effects Sidekiq 7 or greater.
# default: 2
#
# config.concurrency = 3
# ==> Rack server
# Web server used to serve an HTTP response. By default simple GServer based http server is used.
# To use specific server, rack gem version > 2 is required. For rack version >= 3, rackup gem is required.
# Can also be set with the environment variable SIDEKIQ_ALIVE_SERVER.
# default: nil
#
# config.server = 'puma'
# ==> Quiet mode timeout in seconds
# When sidekiq is shutting down, the Sidekiq process stops pulling jobs from the queue. This includes alive key update job. In case of
# long running jobs, alive key can expire before the job is finished. To avoid this, web server is set in to quiet mode
# and is returning 200 OK for healthcheck requests. To avoid infinite quiet mode in case sidekiq process is stuck in shutdown,
# timeout can be set. After timeout is reached, web server resumes normal operations and will return unhealthy status in case
# alive key is expired or purged from redis.
# default: 180
#
# config.quiet_timeout = 300
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`.
Here is an example [rails app](https://github.com/arturictus/sidekiq_alive_example)
## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/arturictus/sidekiq_alive. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
## License
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
================================================
FILE: Rakefile
================================================
# frozen_string_literal: true
require "bundler/gem_tasks"
require "rspec/core/rake_task"
RSpec::Core::RakeTask.new(:spec)
task default: :spec
load "tasks/version.rake"
SidekiqAlive::VersionTask.new
================================================
FILE: bin/console
================================================
#!/usr/bin/env ruby
# frozen_string_literal: true
require "bundler/setup"
require "sidekiq_alive"
# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.
# (If you use this, don't forget to add pry to your Gemfile!)
# require "pry"
# Pry.start
require "irb"
IRB.start(__FILE__)
================================================
FILE: bin/setup
================================================
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
set -vx
bundle install
# Do any other automated setup that you need to do here
================================================
FILE: docker-compose.yml
================================================
version: '3.6'
services:
redis:
image: redis
ports:
- 6379:6379
================================================
FILE: lib/sidekiq_alive/config.rb
================================================
# frozen_string_literal: true
module SidekiqAlive
class Config
include Singleton
attr_accessor :host,
:port,
:path,
:liveness_key,
:time_to_live,
:callback,
:registered_instance_key,
:queue_prefix,
:custom_liveness_probe,
:logger,
:shutdown_callback,
:concurrency,
:server,
:quiet_timeout
def initialize
set_defaults
end
def set_defaults
@host = ENV.fetch("SIDEKIQ_ALIVE_HOST", "0.0.0.0")
@port = ENV.fetch("SIDEKIQ_ALIVE_PORT", 7433)
@path = ENV.fetch("SIDEKIQ_ALIVE_PATH", "/")
@liveness_key = "SIDEKIQ::LIVENESS_PROBE_TIMESTAMP"
@time_to_live = 10 * 60
@callback = proc {}
@registered_instance_key = "SIDEKIQ_REGISTERED_INSTANCE"
@queue_prefix = :"sidekiq-alive"
@custom_liveness_probe = proc { true }
@shutdown_callback = proc {}
@concurrency = Integer(ENV.fetch("SIDEKIQ_ALIVE_CONCURRENCY", 2), exception: false) || 2
@server = ENV.fetch("SIDEKIQ_ALIVE_SERVER", nil)
@quiet_timeout = Integer(ENV.fetch("SIDEKIQ_ALIVE_QUIET_TIMEOUT", 180), exception: false) || 180
end
def registration_ttl
@registration_ttl || time_to_live * 3
end
def worker_interval
time_to_live / 2
end
end
end
================================================
FILE: lib/sidekiq_alive/helpers.rb
================================================
# frozen_string_literal: true
module SidekiqAlive
module Helpers
class << self
def sidekiq_7?
current_sidekiq_version >= Gem::Version.new("7")
end
def sidekiq_6?
current_sidekiq_version >= Gem::Version.new("6") &&
current_sidekiq_version < Gem::Version.new("7")
end
def sidekiq_5?
current_sidekiq_version >= Gem::Version.new("5") &&
current_sidekiq_version < Gem::Version.new("6")
end
def use_rack?
return @use_rack if defined?(@use_rack)
require "rack"
@use_rack = current_rack_version < Gem::Version.new("3")
rescue LoadError
# currently this won't happen because rack is a dependency of sidekiq
@use_rack = false
end
def use_rackup?
return @use_rackup if defined?(@use_rackup)
require "rackup"
@use_rackup = current_rack_version >= Gem::Version.new("3")
rescue LoadError
if current_rack_version >= Gem::Version.new("3")
SidekiqAlive.logger.warn("rackup gem required with rack >= 3, defaulting to default server")
end
@use_rackup = false
end
private
def current_sidekiq_version
Gem.loaded_specs["sidekiq"].version
end
def current_rack_version
Gem.loaded_specs["rack"].version
end
end
end
end
================================================
FILE: lib/sidekiq_alive/redis/base.rb
================================================
# frozen_string_literal: true
module SidekiqAlive
module Redis
class Base
def set(...)
raise(NotImplementedError)
end
def zadd(set_key, ex, key)
raise(NotImplementedError)
end
def zrange(set_key, start, stop)
raise(NotImplementedError)
end
def zrangebyscore(set_key, min, max)
raise(NotImplementedError)
end
def zrem(set_key, key)
raise(NotImplementedError)
end
def delete(key)
raise(NotImplementedError)
end
def ttl(...)
redis { |r| r.ttl(...) }
end
end
end
end
================================================
FILE: lib/sidekiq_alive/redis/redis_client_gem.rb
================================================
# frozen_string_literal: true
require_relative "base"
module SidekiqAlive
module Redis
# Wrapper for `redis-client` gem used by `sidekiq` > 7
# https://github.com/redis-rb/redis-client
class RedisClientGem < Base
def initialize(capsule = nil)
super()
@capsule = Sidekiq.default_configuration.capsules[capsule || CAPSULE_NAME]
end
def set(key, time:, ex:)
redis { |r| r.call("SET", key, time, ex: ex) }
end
def get(key)
redis { |r| r.call("GET", key) }
end
def zadd(set_key, ex, key)
redis { |r| r.call("ZADD", set_key, ex, key) }
end
def zrange(set_key, start, stop)
redis { |r| r.call("ZRANGE", set_key, start, stop) }
end
def zrangebyscore(set_key, min, max)
redis { |r| r.call("ZRANGEBYSCORE", set_key, min, max) }
end
def zrem(set_key, key)
redis { |r| r.call("ZREM", set_key, key) }
end
def delete(key)
redis { |r| r.call("DEL", key) }
end
private
def redis(&block)
# Default to Sidekiq.redis if capsule is not configured yet but redis adapter is accessed
(@capsule || Sidekiq).redis(&block)
end
end
end
end
================================================
FILE: lib/sidekiq_alive/redis/redis_gem.rb
================================================
# frozen_string_literal: true
require_relative "base"
module SidekiqAlive
module Redis
# Wrapper for `redis` gem used by sidekiq < 7
# https://github.com/redis/redis-rb
class RedisGem < Base
def set(key, time:, ex:)
redis { |r| r.set(key, time, ex: ex) }
end
def get(key)
redis { |r| r.get(key) }
end
def zadd(set_key, ex, key)
redis { |r| r.zadd(set_key, ex, key) }
end
def zrange(set_key, start, stop)
redis { |r| r.zrange(set_key, start, stop) }
end
def zrangebyscore(set_key, min, max)
redis { |r| r.zrangebyscore(set_key, min, max) }
end
def zrem(set_key, key)
redis { |r| r.zrem(set_key, key) }
end
def delete(key)
redis { |r| r.del(key) }
end
private
def redis(&block)
Sidekiq.redis(&block)
end
end
end
end
================================================
FILE: lib/sidekiq_alive/redis.rb
================================================
# frozen_string_literal: true
module SidekiqAlive
module Redis
class << self
def adapter(capsule = nil)
Helpers.sidekiq_7? ? Redis::RedisClientGem.new(capsule) : Redis::RedisGem.new
end
end
end
end
require_relative "redis/base"
require_relative "redis/redis_client_gem"
require_relative "redis/redis_gem"
================================================
FILE: lib/sidekiq_alive/server/base.rb
================================================
# frozen_string_literal: true
module SidekiqAlive
module Server
module Base
SHUTDOWN_SIGNAL = "TERM"
QUIET_SIGNAL = "TSTP"
# set web server to quiet mode
def quiet!
logger.info("[SidekiqAlive] Setting web server to quiet mode")
Process.kill(QUIET_SIGNAL, @server_pid) unless @server_pid.nil?
end
private
def configure_shutdown
Kernel.at_exit do
next if @server_pid.nil?
logger.info("Shutting down SidekiqAlive web server")
Process.kill(SHUTDOWN_SIGNAL, @server_pid)
Process.wait(@server_pid)
end
end
def configure_shutdown_signal(&block)
Signal.trap(SHUTDOWN_SIGNAL, &block)
end
def configure_quiet_signal(&block)
Signal.trap(QUIET_SIGNAL, &block)
end
def host
SidekiqAlive.config.host
end
def port
SidekiqAlive.config.port.to_i
end
def path
SidekiqAlive.config.path
end
def logger
SidekiqAlive.logger
end
end
end
end
================================================
FILE: lib/sidekiq_alive/server/default.rb
================================================
# frozen_string_literal: true
require_relative "http_server"
require_relative "base"
module SidekiqAlive
module Server
class Default < HttpServer
extend Base
class << self
def run!
logger.info("[SidekiqAlive] Starting default healthcheck server on #{host}:#{port}")
@server_pid = ::Process.fork do
@server = new(port, host, path)
# stop is wrapped in a thread because gserver calls synchrnonize which raises an error when in trap context
configure_shutdown_signal { Thread.new { @server.stop } }
configure_quiet_signal { @server.quiet! }
@server.start
@server.join
end
configure_shutdown
logger.info("[SidekiqAlive] Web server started in subprocess with pid #{@server_pid}")
self
end
end
def initialize(port, host, path, logger = SidekiqAlive.logger)
super(self, port, host, logger)
@path = path
end
def request_handler(req, res)
if req.path != path
res.status = 404
res.body = "Not found"
return logger.warn("[SidekiqAlive] Path '#{req.path}' not found")
end
if quiet?
res.status = 200
res.body = "Server is shutting down"
return logger.debug("[SidekiqAlive] Server in quiet mode, skipping alive key lookup!")
end
if SidekiqAlive.alive?
res.status = 200
res.body = "Alive!"
return logger.debug("[SidekiqAlive] Found alive key!")
end
response = "Can't find the alive key"
res.status = 404
res.body = response
logger.error("[SidekiqAlive] #{response}")
rescue StandardError => e
response = "Internal Server Error"
res.status = 500
res.body = response
logger.error("[SidekiqAlive] #{response} looking for alive key. Error: #{e.message}")
end
def quiet!
@quiet = Time.now
end
private
attr_reader :path
def quiet?
@quiet && (Time.now - @quiet) < SidekiqAlive.config.quiet_timeout
end
end
end
end
================================================
FILE: lib/sidekiq_alive/server/http_server.rb
================================================
# frozen_string_literal: true
require "gserver"
module SidekiqAlive
module Server
# Simple HTTP server implementation
#
class HttpServer < GServer
# Request class for HTTP server
#
class Request
attr_reader :data, :header, :method, :path, :proto
def initialize(data, method = nil, path = nil, proto = nil)
@header = {}
@data = data
@method = method
@path = path
@proto = proto
end
def content_length
len = @header["Content-Length"]
return if len.nil?
len.to_i
end
end
# Response class for HTTP server
#
class Response
attr_reader :header
attr_accessor :body, :status, :status_message
def initialize(status = 200)
@status = status
@status_message = nil
@header = {}
end
end
def initialize(handle_obj, port, host, logger = Logger.new($stdout))
@handler = handle_obj
@logger = logger
super(port, host, 1, nil, logger.debug?, logger.debug?)
end
private
attr_reader :handler, :logger
CRLF = "\r\n"
HTTP_PROTO = "HTTP/1.1"
SERVER_NAME = "SidekiqAlive/#{SidekiqAlive::VERSION} (Ruby/#{RUBY_VERSION})"
# Default header for the server name
DEFAULT_HEADER = {
"Server" => SERVER_NAME,
}
# Mapping of status codes and error messages
STATUS_CODE_MAPPING = {
200 => "OK",
400 => "Bad Request",
403 => "Forbidden",
404 => "Not Found",
405 => "Method Not Allowed",
411 => "Length Required",
500 => "Internal Server Error",
}
def serve(io)
# parse first line
if io.gets =~ /^(\S+)\s+(\S+)\s+(\S+)/
request = Request.new(io, ::Regexp.last_match(1), ::Regexp.last_match(2), ::Regexp.last_match(3))
else
io << http_resp(status_code: 400)
return
end
# parse HTTP headers
while (line = io.gets) !~ /^(\n|\r)/
if line =~ /^([\w-]+):\s*(.*)$/
request.header[::Regexp.last_match(1)] = ::Regexp.last_match(2).strip
end
end
io.binmode
response = Response.new
# execute request handler
handler.request_handler(request, response)
http_response = http_resp(
status_code: response.status,
status_message: response.status_message,
header: response.header,
body: response.body,
)
# write response back to the client
io << http_response
rescue StandardError
io << http_resp(status_code: 500)
end
def http_header(header = nil)
new_header = DEFAULT_HEADER.dup
new_header.merge(header) unless header.nil?
new_header["Connection"] = "Keep-Alive"
new_header["Date"] = http_date(Time.now)
new_header
end
def http_resp(status_code:, status_message: nil, header: nil, body: nil)
status_message ||= STATUS_CODE_MAPPING[status_code]
status_line = "#{HTTP_PROTO} #{status_code} #{status_message}".rstrip + CRLF
resp_header = http_header(header)
resp_header["Content-Length"] = body.bytesize.to_s unless body.nil?
header_lines = resp_header.map { |k, v| "#{k}: #{v}#{CRLF}" }.join
[status_line, header_lines, CRLF, body].compact.join
end
def http_date(a_time)
a_time.gmtime.strftime("%a, %d %b %Y %H:%M:%S GMT")
end
def log(msg)
logger.debug(msg)
end
end
end
end
================================================
FILE: lib/sidekiq_alive/server/rack.rb
================================================
# frozen_string_literal: true
require_relative "base"
module SidekiqAlive
module Server
class Rack
extend Base
class << self
def run!
logger.info("[SidekiqAlive] Starting healthcheck '#{server}' server")
@server_pid = ::Process.fork do
@handler = handler
configure_shutdown_signal { @handler.shutdown }
configure_quiet_signal { @quiet = Time.now }
@handler.run(self, Port: port, Host: host, AccessLog: [], Logger: logger)
end
configure_shutdown
self
end
def call(env)
req = ::Rack::Request.new(env)
if req.path != path
logger.warn("[SidekiqAlive] Path '#{req.path}' not found")
return [404, {}, ["Not found"]]
end
if quiet?
logger.debug("[SidekiqAlive] [SidekiqAlive] Server in quiet mode, skipping alive key lookup!")
return [200, {}, ["Server is shutting down"]]
end
if SidekiqAlive.alive?
logger.debug("[SidekiqAlive] Found alive key!")
return [200, {}, ["Alive!"]]
end
response = "Can't find the alive key"
logger.error("[SidekiqAlive] #{response}")
[404, {}, [response]]
rescue StandardError => e
logger.error("[SidekiqAlive] #{response} looking for alive key. Error: #{e.message}")
[500, {}, ["Internal Server Error"]]
end
private
def quiet?
@quiet && (Time.now - @quiet) < SidekiqAlive.config.quiet_timeout
end
def handler
Helpers.use_rackup? ? ::Rackup::Handler.get(server) : ::Rack::Handler.get(server)
end
def server
SidekiqAlive.config.server
end
end
end
end
end
================================================
FILE: lib/sidekiq_alive/server.rb
================================================
# frozen_string_literal: true
module SidekiqAlive
module Server
class << self
def run!
server.run!
end
private
def server
use_rack? ? Rack : Default
end
def use_rack?
return false unless SidekiqAlive.config.server
Helpers.use_rackup? || Helpers.use_rack?
end
def logger
SidekiqAlive.logger
end
end
end
end
require_relative "server/default"
require_relative "server/rack"
================================================
FILE: lib/sidekiq_alive/version.rb
================================================
# frozen_string_literal: true
module SidekiqAlive
VERSION = "2.5.0"
end
================================================
FILE: lib/sidekiq_alive/worker.rb
================================================
# frozen_string_literal: true
module SidekiqAlive
class Worker
include Sidekiq::Worker
sidekiq_options retry: false
# Passing the hostname argument it's only for debugging enqueued jobs
def perform(_hostname = SidekiqAlive.hostname)
# Checks if custom liveness probe passes should fail or return false
return unless config.custom_liveness_probe.call
# Writes the liveness in Redis
write_living_probe
remove_orphaned_queues
# schedules next living probe
self.class.perform_in(config.worker_interval, current_hostname)
end
def write_living_probe
# Write liveness probe
SidekiqAlive.store_alive_key
# Increment ttl for current registered instance
SidekiqAlive.register_current_instance
# after callbacks
begin
config.callback.call
rescue StandardError
nil
end
end
# Removes orphaned Sidekiq queues left behind by unexpected instance shutdowns (e.g., due to OOM)
def remove_orphaned_queues
# If the worker isn't executed within this window, the lifeness key expires
latency_threshold = config.time_to_live - config.worker_interval
Sidekiq::Queue.all
.filter { |q| q.name.start_with?(config.queue_prefix.to_s) }
.filter { |q| q.latency > latency_threshold }
.filter { |q| q.size == 1 && q.all? { |job| job.klass == self.class.name } }
.each(&:clear)
end
def current_hostname
SidekiqAlive.hostname
end
def config
SidekiqAlive.config
end
end
end
================================================
FILE: lib/sidekiq_alive.rb
================================================
# frozen_string_literal: true
require "sidekiq"
require "sidekiq/api"
require "singleton"
require "sidekiq_alive/version"
require "sidekiq_alive/config"
require "sidekiq_alive/helpers"
require "sidekiq_alive/redis"
module SidekiqAlive
HOSTNAME_REGISTRY = "sidekiq-alive-hostnames"
CAPSULE_NAME = "sidekiq-alive"
class << self
def start
Sidekiq.configure_server do |sq_config|
sq_config.on(:startup) do
SidekiqAlive::Worker.sidekiq_options(queue: current_queue)
if Helpers.sidekiq_7?
sq_config.capsule(CAPSULE_NAME) do |cap|
cap.concurrency = config.concurrency
cap.queues = [current_queue]
end
else
(sq_config.respond_to?(:[]) ? sq_config[:queues] : sq_config.options[:queues]).unshift(current_queue)
end
logger.info("[SidekiqAlive] #{startup_info}")
register_current_instance
store_alive_key
# Passing the hostname argument it's only for debugging enqueued jobs
SidekiqAlive::Worker.perform_async(hostname)
@server = SidekiqAlive::Server.run!
logger.info("[SidekiqAlive] #{successful_startup_text}")
end
sq_config.on(:quiet) do
logger.info("[SidekiqAlive] #{shutdown_info}")
purge_pending_jobs
# set web server to quiet mode
@server&.quiet!
end
sq_config.on(:shutdown) do
remove_queue
# make sure correct redis connection pool is used
# sidekiq will terminate non internal capsules
Redis.adapter("internal").zrem(HOSTNAME_REGISTRY, current_instance_register_key)
config.shutdown_callback.call
end
end
end
def current_queue
"#{config.queue_prefix}-#{hostname}"
end
def register_current_instance
register_instance(current_instance_register_key)
end
def registered_instances
# before we return we make sure we expire old keys
expire_old_keys
redis.zrange(HOSTNAME_REGISTRY, 0, -1)
end
def purge_pending_jobs
schedule_set = Sidekiq::ScheduledSet.new
jobs = if Helpers.sidekiq_5?
schedule_set.select { |job| job.klass == "SidekiqAlive::Worker" && job.queue == current_queue }
else
schedule_set.scan('"class":"SidekiqAlive::Worker"').select { |job| job.queue == current_queue }
end
unless jobs.empty?
logger.info("[SidekiqAlive] Purging #{jobs.count} pending jobs for #{hostname}")
jobs.each(&:delete)
end
end
def remove_queue
logger.info("[SidekiqAlive] Removing queue #{current_queue}")
Sidekiq::Queue.new(current_queue).clear
end
def current_instance_register_key
"#{config.registered_instance_key}::#{hostname}"
end
def current_instance_registered?
redis.get(current_instance_register_key)
end
def store_alive_key
redis.set(current_lifeness_key, time: Time.now.to_i, ex: config.time_to_live.to_i)
end
def redis
@redis ||= Redis.adapter
end
def alive?
redis.ttl(current_lifeness_key) != -2
end
# CONFIG ---------------------------------------
def setup
yield(config)
end
def logger
config.logger || Sidekiq.logger
end
def config
@config ||= SidekiqAlive::Config.instance
end
def current_lifeness_key
"#{config.liveness_key}::#{hostname}"
end
def hostname
ENV["HOSTNAME"] || "HOSTNAME_NOT_SET"
end
def shutdown_info
"Shutting down sidekiq-alive!"
end
def startup_info
info = {
hostname: hostname,
port: config.port,
ttl: config.time_to_live,
queue: current_queue,
register_set: HOSTNAME_REGISTRY,
liveness_key: current_lifeness_key,
register_key: current_instance_register_key,
}
"Starting sidekiq-alive: #{info}"
end
def successful_startup_text
"Successfully started sidekiq-alive, registered with key: " \
"#{current_instance_register_key} on set #{HOSTNAME_REGISTRY}"
end
def expire_old_keys
# we get every key that should be expired by now
keys_to_expire = redis.zrangebyscore(HOSTNAME_REGISTRY, 0, Time.now.to_i)
# then we remove it
keys_to_expire.each { |key| redis.zrem(HOSTNAME_REGISTRY, key) }
end
def register_instance(instance_name)
expiration = Time.now.to_i + config.registration_ttl.to_i
redis.zadd(HOSTNAME_REGISTRY, expiration, instance_name)
expire_old_keys
end
end
end
require "sidekiq_alive/worker"
require "sidekiq_alive/server"
SidekiqAlive.start unless ENV.fetch("DISABLE_SIDEKIQ_ALIVE", "").casecmp("true").zero?
================================================
FILE: sidekiq_alive.gemspec
================================================
# frozen_string_literal: true
lib = File.expand_path("lib", __dir__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "sidekiq_alive/version"
Gem::Specification.new do |spec|
spec.name = "sidekiq_alive"
spec.authors = ["Andrejs Cunskis", "Artur Pañach"]
spec.email = ["andrejs.cunskis@gmail.com", "arturictus@gmail.com"]
spec.version = SidekiqAlive::VERSION
spec.required_ruby_version = Gem::Requirement.new(">= 3.1")
spec.homepage = "https://github.com/arturictus/sidekiq_alive"
spec.summary = "Liveness probe for sidekiq on Kubernetes deployments."
spec.license = "MIT"
spec.description = <<~DSC
SidekiqAlive offers a solution to add liveness probe of a Sidekiq instance.
How?
A http server is started and on each requests validates that a liveness key is stored in Redis. If it is there means is working.
A Sidekiq job is the responsable to storing this key. If Sidekiq stops processing jobs
this key gets expired by Redis an consequently the http server will return a 500 error.
This Job is responsible to requeue itself for the next liveness probe.
DSC
spec.metadata = {
"homepage_uri" => spec.homepage,
"source_code_uri" => spec.homepage,
"changelog_uri" => "#{spec.homepage}/releases",
"documentation_uri" => "#{spec.homepage}/blob/v#{spec.version}/README.md",
"bug_tracker_uri" => "#{spec.homepage}/issues",
}
spec.files = Dir["README.md", "lib/**/*"]
spec.require_paths = ["lib"]
spec.add_development_dependency("bundler", "> 1.16")
spec.add_development_dependency("debug", "~> 1.6")
spec.add_development_dependency("rake", "~> 13.0")
spec.add_development_dependency("rspec", "~> 3.0")
spec.add_development_dependency("rspec-sidekiq", "~> 5.0")
spec.add_development_dependency("rubocop-shopify", "~> 2.10")
spec.add_development_dependency("semver2", "~> 3.4")
spec.add_development_dependency("solargraph", "~> 0.54.0")
spec.add_dependency("base64", ">= 0", "< 1") # sidekiq 6 requires base64 which is not part of stdlib in Ruby 3.4+
spec.add_dependency("gserver", "~> 0.0.1")
spec.add_dependency("sidekiq", ">= 5", "< 9")
end
================================================
FILE: spec/config_spec.rb
================================================
# frozen_string_literal: true
RSpec.describe(SidekiqAlive::Config) do
subject(:config) { described_class.instance }
describe "#worker_interval" do
it "less than ttl" do
expect(config.worker_interval).to(satisfy { |i| i < config.time_to_live })
end
end
end
================================================
FILE: spec/redis_spec.rb
================================================
# frozen_string_literal: true
RSpec.describe(SidekiqAlive::Redis) do
let(:redis) { SidekiqAlive::Redis.adapter }
it "Works" do
time = Time.now.to_s
redis.set("hello", time: time, ex: 60)
expect(redis.ttl("hello") > 1).to(be(true))
expect(redis.get("hello")).to(eq(time))
redis.zadd("test_set", Time.now.to_i, "test-key-1")
redis.zadd("test_set", Time.now.to_i, "test-key-2")
expect(redis.zrange("test_set", 0, -1)).to(eq(["test-key-1", "test-key-2"]))
expect(redis.zrem("test_set", "test-key-1"))
expect(redis.zrange("test_set", 0, -1)).to(eq(["test-key-2"]))
end
end
================================================
FILE: spec/server/default_spec.rb
================================================
# frozen_string_literal: true
require "net/http"
RSpec.describe(SidekiqAlive::Server::Default) do
let(:port) { 7433 }
let(:path) { "/" }
let(:server) { SidekiqAlive::Server::Default.new(port, "0.0.0.0", path) }
before do
server.start
end
after do
server.stop
end
def get(uri)
@last_response = Net::HTTP.get_response(URI("http://localhost:#{port}#{uri}"))
end
context "with default configuration" do
it "responds with success when the service is alive" do
allow(SidekiqAlive).to(receive(:alive?) { true })
get "/"
expect(@last_response.code).to(eq("200"))
expect(@last_response.body).to(eq("Alive!"))
end
it "responds with an error when the service is not alive" do
allow(SidekiqAlive).to(receive(:alive?) { false })
get "/"
expect(@last_response.code).to(eq("404"))
expect(@last_response.body).to(eq("Can't find the alive key"))
end
it "responds not found on an unknown path" do
get "/unknown-path"
expect(@last_response.code).to(eq("404"))
expect(@last_response.body).to(eq("Not found"))
end
end
context "with custom path" do
let(:path) { "/sidekiq-probe" }
it "responds ok to the given path" do
allow(SidekiqAlive).to(receive(:alive?) { true })
get "/sidekiq-probe"
expect(@last_response.code).to(eq("200"))
end
end
context "with quiet mode" do
before do
server.quiet!
end
it "responds with success and server is shutting down message" do
get "/"
expect(@last_response.code).to(eq("200"))
expect(@last_response.body).to(eq("Server is shutting down"))
end
end
end
================================================
FILE: spec/server/rack_spec.rb
================================================
# frozen_string_literal: true
require "rack/test"
ENV["RACK_ENV"] = "test"
RSpec.describe(SidekiqAlive::Server::Rack) do
include Rack::Test::Methods
subject(:app) { described_class }
before do
described_class.instance_variable_set(:@quiet, nil)
end
context "with default configuration" do
it "responds with success when the service is alive" do
allow(SidekiqAlive).to(receive(:alive?) { true })
get "/"
expect(last_response).to(be_ok)
expect(last_response.body).to(eq("Alive!"))
end
it "responds with an error when the service is not alive" do
allow(SidekiqAlive).to(receive(:alive?) { false })
get "/"
expect(last_response).not_to(be_ok)
expect(last_response.body).to(eq("Can't find the alive key"))
end
it "responds not found on an unknown path" do
get "/unknown-path"
expect(last_response).not_to(be_ok)
expect(last_response.body).to(eq("Not found"))
end
end
context "with custom path" do
let(:path) { "/sidekiq-probe" }
before do
ENV["SIDEKIQ_ALIVE_PATH"] = path
SidekiqAlive.config.set_defaults
end
after do
ENV["SIDEKIQ_ALIVE_PATH"] = nil
end
it "responds ok to the given path" do
allow(SidekiqAlive).to(receive(:alive?) { true })
get "/sidekiq-probe"
expect(last_response).to(be_ok)
end
end
context "with quiet mode" do
before do
described_class.instance_variable_set(:@quiet, Time.now)
end
it "responds with success and server is shutting down message" do
get "/"
expect(last_response).to(be_ok)
expect(last_response.body).to(eq("Server is shutting down"))
end
end
end
================================================
FILE: spec/server_spec.rb
================================================
# frozen_string_literal: true
around_config = proc do |example|
ENV["SIDEKIQ_ALIVE_HOST"] = "1.2.3.4"
ENV["SIDEKIQ_ALIVE_PORT"] = "4567"
ENV["SIDEKIQ_ALIVE_PATH"] = "/health"
SidekiqAlive.config.set_defaults
example.run
ENV["SIDEKIQ_ALIVE_HOST"] = nil
ENV["SIDEKIQ_ALIVE_PORT"] = nil
ENV["SIDEKIQ_ALIVE_PATH"] = nil
end
RSpec.describe(SidekiqAlive::Server, :aggregate_failures) do
subject(:app) { described_class }
let(:pid) { Random.rand(1000) }
before do
allow(Process).to(receive(:fork).and_yield.and_return(pid))
allow(Process).to(receive(:kill))
allow(Process).to(receive(:wait))
allow(Signal).to(receive(:trap))
allow(Kernel).to(receive(:at_exit))
end
context "with default server" do
let(:fake_server) do
instance_double(
SidekiqAlive::Server::Default,
start: nil,
stop: nil,
join: nil,
quiet!: nil,
)
end
before do
allow(SidekiqAlive::Server::Default).to(receive(:new).and_return(fake_server))
allow(Thread).to(receive(:new).and_yield)
app.run!
end
context "with default config" do
it "starts server with default arguments and configures lifecycle" do
expect(SidekiqAlive::Server::Default).to(have_received(:new).with(7433, "0.0.0.0", "/"))
expect(fake_server).to(have_received(:start))
expect(fake_server).to(have_received(:join))
end
it "configures signals" do
expect(Signal).to(have_received(:trap).with("TERM")) do |&arg|
arg.call
expect(fake_server).to(have_received(:stop))
end
expect(Signal).to(have_received(:trap).with("TSTP")) do |&arg|
arg.call
expect(fake_server).to(have_received(:quiet!))
end
end
it "configures shutdown" do
allow(Kernel).to(receive(:at_exit)) do |&arg|
arg.call
expect(Process).to(have_received(:kill).with("TERM", pid))
expect(Process).to(have_received(:wait).with(pid))
end
end
end
context "with changed host, port and path configuration" do
around(&around_config)
it "starts with updated configuration" do
expect(SidekiqAlive::Server::Default).to(have_received(:new).with(4567, "1.2.3.4", "/health"))
end
end
end
context "rack based server" do
let(:fake_server) { double("rack server", run: nil, shutdown: nil) }
let(:handler) { SidekiqAlive::Helpers.use_rackup? ? Rackup::Handler : Rack::Handler }
before do
ENV["SIDEKIQ_ALIVE_SERVER"] = "webrick"
SidekiqAlive.config.set_defaults
allow(handler).to(receive(:get).and_return(fake_server))
SidekiqAlive::Server::Rack.instance_variable_set(:@quiet, nil)
app.run!
end
after { ENV["SIDEKIQ_ALIVE_SERVER"] = nil }
context "with default config" do
it "starts server with default arguments and traps shutdown", :aggregate_failures do
expect(handler).to(have_received(:get).with("webrick"))
expect(fake_server).to(have_received(:run).with(
SidekiqAlive::Server::Rack, Port: 7433, Host: "0.0.0.0", AccessLog: [], Logger: SidekiqAlive.logger
))
end
it "configures signals" do
expect(Signal).to(have_received(:trap).with("TERM")) do |&arg|
arg.call
expect(fake_server).to(have_received(:shutdown))
end
expect(Signal).to(have_received(:trap).with("TSTP")) do |&arg|
arg.call
expect(SidekiqAlive::Server::Rack.instance_variable_get(:@quiet)).to(be_instance_of(Time))
end
end
it "configures shutdown" do
allow(Kernel).to(receive(:at_exit)) do |&arg|
arg.call
expect(Process).to(have_received(:kill).with("TERM", pid))
expect(Process).to(have_received(:wait).with(pid))
end
end
end
context "with changed host, port and path configuration" do
around(&around_config)
it "starts with updated configuration" do
expect(fake_server).to(have_received(:run).with(
SidekiqAlive::Server::Rack, Port: 4567, Host: "1.2.3.4", AccessLog: [], Logger: SidekiqAlive.logger
))
end
end
end
end
================================================
FILE: spec/sidekiq_alive_spec.rb
================================================
# frozen_string_literal: true
begin
# this is needed for spec to work with sidekiq >7
require "sidekiq/capsule"
rescue LoadError # rubocop:disable Lint/SuppressedException
end
RSpec.describe(SidekiqAlive) do
context "with configuration" do
it "has a version number" do
expect(SidekiqAlive::VERSION).not_to(be(nil))
end
it "configures the host from the #setup" do
described_class.setup do |config|
config.host = "1.2.3.4"
end
expect(described_class.config.host).to(eq("1.2.3.4"))
end
it "configures the host from the SIDEKIQ_ALIVE_HOST ENV var" do
ENV["SIDEKIQ_ALIVE_HOST"] = "1.2.3.4"
SidekiqAlive.config.set_defaults
expect(described_class.config.host).to(eq("1.2.3.4"))
ENV["SIDEKIQ_ALIVE_HOST"] = nil
end
it "configures the port from the #setup" do
described_class.setup do |config|
config.port = 4567
end
expect(described_class.config.port).to(eq(4567))
end
it "configures the port from the SIDEKIQ_ALIVE_PORT ENV var" do
ENV["SIDEKIQ_ALIVE_PORT"] = "4567"
SidekiqAlive.config.set_defaults
expect(described_class.config.port).to(eq("4567"))
ENV["SIDEKIQ_ALIVE_PORT"] = nil
end
it "configures the concurrency from the SIDEKIQ_ALIVE_CONCURRENCY ENV var" do
ENV["SIDEKIQ_ALIVE_CONCURRENCY"] = "3"
SidekiqAlive.config.set_defaults
expect(described_class.config.concurrency).to(eq(3))
ENV["SIDEKIQ_ALIVE_CONCURRENCY"] = nil
end
it "configurations behave as expected" do
k = described_class.config
expect(k.host).to(eq("0.0.0.0"))
k.host = "1.2.3.4"
expect(k.host).to(eq("1.2.3.4"))
expect(k.port).to(eq(7433))
k.port = 4567
expect(k.port).to(eq(4567))
expect(k.liveness_key).to(eq("SIDEKIQ::LIVENESS_PROBE_TIMESTAMP"))
k.liveness_key = "key"
expect(k.liveness_key).to(eq("key"))
expect(k.time_to_live).to(eq(10 * 60))
k.time_to_live = 2 * 60
expect(k.time_to_live).to(eq(2 * 60))
expect(k.callback.call).to(eq(nil))
k.callback = proc { "hello" }
expect(k.callback.call).to(eq("hello"))
expect(k.queue_prefix).to(eq(:"sidekiq-alive"))
k.queue_prefix = :other
expect(k.queue_prefix).to(eq(:other))
expect(k.shutdown_callback.call).to(eq(nil))
k.shutdown_callback = proc { "hello" }
expect(k.shutdown_callback.call).to(eq("hello"))
end
end
context "with redis" do
let(:sidekiq_7) { SidekiqAlive::Helpers.sidekiq_7? }
# Older versions of sidekiq yielded Sidekiq module as configuration object
# With sidekiq > 7, configuration is a separate class
let(:sq_config) { sidekiq_7 ? Sidekiq.default_configuration : Sidekiq }
before do
allow(Sidekiq).to(receive(:server?) { true })
allow(sq_config).to(receive(:on))
if sidekiq_7
allow(sq_config).to(receive(:capsule).and_call_original)
elsif sq_config.respond_to?(:[])
allow(sq_config).to(receive(:[]).and_call_original)
else
allow(sq_config).to(receive(:options).and_call_original)
end
end
it '::store_alive_key" stores key with the expected ttl' do
redis = SidekiqAlive.redis
expect(redis.ttl(SidekiqAlive.current_lifeness_key)).to(eq(-2))
SidekiqAlive.store_alive_key
expect(redis.ttl(SidekiqAlive.current_lifeness_key)).to(eq(SidekiqAlive.config.time_to_live))
end
it "::current_lifeness_key" do
expect(SidekiqAlive.current_lifeness_key).to(include("::test-hostname"))
end
it "::hostname" do
expect(SidekiqAlive.hostname).to(eq("test-hostname"))
end
it "::alive?" do
expect(SidekiqAlive.alive?).to(be(false))
SidekiqAlive.store_alive_key
expect(SidekiqAlive.alive?).to(be(true))
end
context "::start" do
let(:server) { double("Server", quiet!: nil) }
let(:queue_prefix) { :heathcheck }
let(:queues) do
next Sidekiq.default_configuration.capsules[SidekiqAlive::CAPSULE_NAME].queues if sidekiq_7
sq_config.options[:queues]
end
before do
allow(SidekiqAlive::Server).to(receive(:run!) { server })
allow(sq_config).to(receive(:on).with(:startup).and_yield)
SidekiqAlive.instance_variable_set(:@redis, nil)
end
it "::registered_instances" do
SidekiqAlive.start
expect(SidekiqAlive.registered_instances.count).to(eq(1))
expect(SidekiqAlive.registered_instances.first).to(include("test-hostname"))
end
it "::on(:quiet)" do
SidekiqAlive.start
expect(sq_config).to(have_received(:on).with(:quiet)) do |&arg|
arg.call
expect(server).to(have_received(:quiet!))
end
end
it "::on(:shutdown)" do
callback = double("callback", call: nil)
SidekiqAlive.config.shutdown_callback = callback
SidekiqAlive.start
expect(sq_config).to(have_received(:on).with(:shutdown)) do |&arg|
arg.call
expect(SidekiqAlive.registered_instances.count).to(eq(0))
expect(callback).to(have_received(:call))
end
end
it "::queues" do
SidekiqAlive.config.queue_prefix = queue_prefix
SidekiqAlive.start
expect(queues.first).to(eq("#{queue_prefix}-test-hostname"))
end
end
end
end
================================================
FILE: spec/spec_helper.rb
================================================
# frozen_string_literal: true
require "simplecov"
SimpleCov.start
require "simplecov-cobertura"
SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter
require "bundler/setup"
require "sidekiq_alive"
require "rspec-sidekiq"
require "debug"
require "rack"
ENV["HOSTNAME"] = "test-hostname"
Sidekiq.logger.level = Logger::FATAL
RSpec.configure do |config|
# Enable flags like --only-failures and --next-failure
config.example_status_persistence_file_path = ".rspec_status"
# Disable RSpec exposing methods globally on `Module` and `main`
config.disable_monkey_patching!
config.expect_with(:rspec) do |c|
c.syntax = :expect
end
config.prepend_before do
Sidekiq.redis(&:flushall)
SidekiqAlive.config.set_defaults
end
end
================================================
FILE: spec/worker_spec.rb
================================================
# frozen_string_literal: true
RSpec.describe(SidekiqAlive::Worker) do
subject(:perform) do
described_class.new.perform
end
context "When being executed in the same instance" do
it "stores alive key and requeues it self" do
SidekiqAlive.register_current_instance
expect(described_class).to(receive(:perform_in))
n = 0
SidekiqAlive.config.callback = proc { n = 2 }
perform
expect(n).to(eq(2))
expect(SidekiqAlive.alive?).to(be(true))
end
end
context "custom liveness probe" do
it "on error" do
expect(described_class).not_to(receive(:perform_in))
n = 0
SidekiqAlive.config.custom_liveness_probe = proc do
n = 2
raise "Nop"
end
begin
perform
rescue StandardError
nil
end
expect(n).to(eq(2))
expect(SidekiqAlive.alive?).to(be(false))
end
it "on success" do
expect(described_class).to(receive(:perform_in))
n = 0
SidekiqAlive.config.custom_liveness_probe = proc { n = 2 }
perform
expect(n).to(eq(2))
expect(SidekiqAlive.alive?).to(be(true))
end
end
describe "orphaned queues removal" do
it "removes orphaned queues" do
queue = instance_double(Sidekiq::Queue, name: "notifications", latency: 10_000, size: 1, clear: nil)
orphaning_queue = instance_double(Sidekiq::Queue, name: "sidekiq-alive-bar", latency: 200, size: 1, clear: nil)
orphaned_queue = instance_double(Sidekiq::Queue, name: "sidekiq-alive-foo", latency: 350, size: 1, clear: nil)
alive_job = instance_double(Sidekiq::JobRecord, klass: "SidekiqAlive::Worker")
allow(orphaned_queue).to(receive(:all?).and_yield(alive_job))
imposter_queue = instance_double(Sidekiq::Queue, name: "sidekiq-aliveness", latency: 10_000, size: 1, clear: nil)
job = instance_double(Sidekiq::JobRecord, klass: "AlivenessWorker")
allow(imposter_queue).to(receive(:all?).and_yield(job))
allow(Sidekiq::Queue).to(receive(:all).and_return([queue, imposter_queue, orphaned_queue, orphaning_queue]))
perform
expect(queue).not_to(have_received(:clear))
expect(imposter_queue).not_to(have_received(:clear))
expect(orphaned_queue).to(have_received(:clear))
expect(orphaning_queue).not_to(have_received(:clear))
end
end
end
================================================
FILE: tasks/version.rake
================================================
# frozen_string_literal: true
require "semver"
module SidekiqAlive
# Update app version
#
class VersionTask
include Rake::DSL
VERSION_FILE = "lib/sidekiq_alive/version.rb"
def initialize
add_version_task
end
# Add version bump task
#
def add_version_task
desc("Bump application version [major, minor, patch]")
task(:version, [:semver]) do |_task, args|
new_version = send(args[:semver]).format("%M.%m.%p").to_s
update_version(new_version)
commit_and_tag(new_version)
end
end
private
# Update version file
#
# @param [SemVer] new_version
# @return [void]
def update_version(new_version)
u_version = File.read(VERSION_FILE).gsub(SidekiqAlive::VERSION, new_version)
File.write(VERSION_FILE, u_version)
end
# Commit updated version file and Gemfile.lock
#
# @return [void]
def commit_and_tag(new_version)
sh("git add #{VERSION_FILE}")
sh("git commit -m 'Update version to #{new_version}'")
end
# Semver of ref from
#
# @return [SemVer]
def semver
@semver ||= SemVer.parse(SidekiqAlive::VERSION)
end
# Increase patch version
#
# @return [SemVer]
def patch
semver.tap { |ver| ver.patch += 1 }
end
# Increase minor version
#
# @return [SemVer]
def minor
semver.tap do |ver|
ver.minor += 1
ver.patch = 0
end
end
# Increase major version
#
# @return [SemVer]
def major
semver.tap do |ver|
ver.major += 1
ver.minor = 0
ver.patch = 0
end
end
end
end
gitextract_fpunre1x/
├── .github/
│ ├── dependabot.yml
│ ├── release.yml
│ └── workflows/
│ ├── changelog.yml
│ ├── release.yml
│ └── test.yml
├── .gitignore
├── .rspec
├── .rubocop.yml
├── .ruby-version
├── .tool-versions
├── CODE_OF_CONDUCT.md
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── bin/
│ ├── console
│ └── setup
├── docker-compose.yml
├── lib/
│ ├── sidekiq_alive/
│ │ ├── config.rb
│ │ ├── helpers.rb
│ │ ├── redis/
│ │ │ ├── base.rb
│ │ │ ├── redis_client_gem.rb
│ │ │ └── redis_gem.rb
│ │ ├── redis.rb
│ │ ├── server/
│ │ │ ├── base.rb
│ │ │ ├── default.rb
│ │ │ ├── http_server.rb
│ │ │ └── rack.rb
│ │ ├── server.rb
│ │ ├── version.rb
│ │ └── worker.rb
│ └── sidekiq_alive.rb
├── sidekiq_alive.gemspec
├── spec/
│ ├── config_spec.rb
│ ├── redis_spec.rb
│ ├── server/
│ │ ├── default_spec.rb
│ │ └── rack_spec.rb
│ ├── server_spec.rb
│ ├── sidekiq_alive_spec.rb
│ ├── spec_helper.rb
│ └── worker_spec.rb
└── tasks/
└── version.rake
SYMBOL INDEX (139 symbols across 16 files)
FILE: lib/sidekiq_alive.rb
type SidekiqAlive (line 11) | module SidekiqAlive
function start (line 16) | def start
function current_queue (line 57) | def current_queue
function register_current_instance (line 61) | def register_current_instance
function registered_instances (line 65) | def registered_instances
function purge_pending_jobs (line 71) | def purge_pending_jobs
function remove_queue (line 85) | def remove_queue
function current_instance_register_key (line 90) | def current_instance_register_key
function current_instance_registered? (line 94) | def current_instance_registered?
function store_alive_key (line 98) | def store_alive_key
function redis (line 102) | def redis
function alive? (line 106) | def alive?
function setup (line 112) | def setup
function logger (line 116) | def logger
function config (line 120) | def config
function current_lifeness_key (line 124) | def current_lifeness_key
function hostname (line 128) | def hostname
function shutdown_info (line 132) | def shutdown_info
function startup_info (line 136) | def startup_info
function successful_startup_text (line 150) | def successful_startup_text
function expire_old_keys (line 155) | def expire_old_keys
function register_instance (line 162) | def register_instance(instance_name)
FILE: lib/sidekiq_alive/config.rb
type SidekiqAlive (line 3) | module SidekiqAlive
class Config (line 4) | class Config
method initialize (line 22) | def initialize
method set_defaults (line 26) | def set_defaults
method registration_ttl (line 42) | def registration_ttl
method worker_interval (line 46) | def worker_interval
FILE: lib/sidekiq_alive/helpers.rb
type SidekiqAlive (line 3) | module SidekiqAlive
type Helpers (line 4) | module Helpers
function sidekiq_7? (line 6) | def sidekiq_7?
function sidekiq_6? (line 10) | def sidekiq_6?
function sidekiq_5? (line 15) | def sidekiq_5?
function use_rack? (line 20) | def use_rack?
function use_rackup? (line 30) | def use_rackup?
function current_sidekiq_version (line 44) | def current_sidekiq_version
function current_rack_version (line 48) | def current_rack_version
FILE: lib/sidekiq_alive/redis.rb
type SidekiqAlive (line 3) | module SidekiqAlive
type Redis (line 4) | module Redis
function adapter (line 6) | def adapter(capsule = nil)
FILE: lib/sidekiq_alive/redis/base.rb
type SidekiqAlive (line 3) | module SidekiqAlive
type Redis (line 4) | module Redis
class Base (line 5) | class Base
method set (line 6) | def set(...)
method zadd (line 10) | def zadd(set_key, ex, key)
method zrange (line 14) | def zrange(set_key, start, stop)
method zrangebyscore (line 18) | def zrangebyscore(set_key, min, max)
method zrem (line 22) | def zrem(set_key, key)
method delete (line 26) | def delete(key)
method ttl (line 30) | def ttl(...)
FILE: lib/sidekiq_alive/redis/redis_client_gem.rb
type SidekiqAlive (line 5) | module SidekiqAlive
type Redis (line 6) | module Redis
class RedisClientGem (line 9) | class RedisClientGem < Base
method initialize (line 10) | def initialize(capsule = nil)
method set (line 16) | def set(key, time:, ex:)
method get (line 20) | def get(key)
method zadd (line 24) | def zadd(set_key, ex, key)
method zrange (line 28) | def zrange(set_key, start, stop)
method zrangebyscore (line 32) | def zrangebyscore(set_key, min, max)
method zrem (line 36) | def zrem(set_key, key)
method delete (line 40) | def delete(key)
method redis (line 46) | def redis(&block)
FILE: lib/sidekiq_alive/redis/redis_gem.rb
type SidekiqAlive (line 5) | module SidekiqAlive
type Redis (line 6) | module Redis
class RedisGem (line 9) | class RedisGem < Base
method set (line 10) | def set(key, time:, ex:)
method get (line 14) | def get(key)
method zadd (line 18) | def zadd(set_key, ex, key)
method zrange (line 22) | def zrange(set_key, start, stop)
method zrangebyscore (line 26) | def zrangebyscore(set_key, min, max)
method zrem (line 30) | def zrem(set_key, key)
method delete (line 34) | def delete(key)
method redis (line 40) | def redis(&block)
FILE: lib/sidekiq_alive/server.rb
type SidekiqAlive (line 3) | module SidekiqAlive
type Server (line 4) | module Server
function run! (line 6) | def run!
function server (line 12) | def server
function use_rack? (line 16) | def use_rack?
function logger (line 22) | def logger
FILE: lib/sidekiq_alive/server/base.rb
type SidekiqAlive (line 3) | module SidekiqAlive
type Server (line 4) | module Server
type Base (line 5) | module Base
function quiet! (line 10) | def quiet!
function configure_shutdown (line 17) | def configure_shutdown
function configure_shutdown_signal (line 27) | def configure_shutdown_signal(&block)
function configure_quiet_signal (line 31) | def configure_quiet_signal(&block)
function host (line 35) | def host
function port (line 39) | def port
function path (line 43) | def path
function logger (line 47) | def logger
FILE: lib/sidekiq_alive/server/default.rb
type SidekiqAlive (line 6) | module SidekiqAlive
type Server (line 7) | module Server
class Default (line 8) | class Default < HttpServer
method run! (line 12) | def run!
method initialize (line 30) | def initialize(port, host, path, logger = SidekiqAlive.logger)
method request_handler (line 36) | def request_handler(req, res)
method quiet! (line 66) | def quiet!
method quiet? (line 74) | def quiet?
FILE: lib/sidekiq_alive/server/http_server.rb
type SidekiqAlive (line 5) | module SidekiqAlive
type Server (line 6) | module Server
class HttpServer (line 9) | class HttpServer < GServer
class Request (line 12) | class Request
method initialize (line 15) | def initialize(data, method = nil, path = nil, proto = nil)
method content_length (line 23) | def content_length
class Response (line 33) | class Response
method initialize (line 37) | def initialize(status = 200)
method initialize (line 44) | def initialize(handle_obj, port, host, logger = Logger.new($stdout))
method serve (line 75) | def serve(io)
method http_header (line 110) | def http_header(header = nil)
method http_resp (line 120) | def http_resp(status_code:, status_message: nil, header: nil, body...
method http_date (line 131) | def http_date(a_time)
method log (line 135) | def log(msg)
FILE: lib/sidekiq_alive/server/rack.rb
type SidekiqAlive (line 5) | module SidekiqAlive
type Server (line 6) | module Server
class Rack (line 7) | class Rack
method run! (line 11) | def run!
method call (line 25) | def call(env)
method quiet? (line 53) | def quiet?
method handler (line 57) | def handler
method server (line 61) | def server
FILE: lib/sidekiq_alive/version.rb
type SidekiqAlive (line 3) | module SidekiqAlive
FILE: lib/sidekiq_alive/worker.rb
type SidekiqAlive (line 3) | module SidekiqAlive
class Worker (line 4) | class Worker
method perform (line 10) | def perform(_hostname = SidekiqAlive.hostname)
method write_living_probe (line 21) | def write_living_probe
method remove_orphaned_queues (line 35) | def remove_orphaned_queues
method current_hostname (line 45) | def current_hostname
method config (line 49) | def config
FILE: spec/server/default_spec.rb
function get (line 18) | def get(uri)
FILE: tasks/version.rake
type SidekiqAlive (line 5) | module SidekiqAlive
class VersionTask (line 8) | class VersionTask
method initialize (line 13) | def initialize
method add_version_task (line 19) | def add_version_task
method update_version (line 35) | def update_version(new_version)
method commit_and_tag (line 43) | def commit_and_tag(new_version)
method semver (line 51) | def semver
method patch (line 58) | def patch
method minor (line 65) | def minor
method major (line 75) | def major
Condensed preview — 42 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (67K chars).
[
{
"path": ".github/dependabot.yml",
"chars": 296,
"preview": "version: 2\nupdates:\n - package-ecosystem: \"bundler\"\n directory: \"/\"\n schedule:\n interval: \"daily\"\n review"
},
{
"path": ".github/release.yml",
"chars": 306,
"preview": "changelog:\n categories:\n - title: '🚀 New feature or request'\n labels: \n - 'enhancement'\n - title: '🐞 "
},
{
"path": ".github/workflows/changelog.yml",
"chars": 377,
"preview": "name: Changelog\n\non:\n push:\n tags:\n - v[0-9]+.[0-9]+.[0-9]+\n\njobs:\n release:\n runs-on: ubuntu-latest\n st"
},
{
"path": ".github/workflows/release.yml",
"chars": 987,
"preview": "name: Release\n\non:\n workflow_dispatch:\n inputs:\n semver:\n description: Bump\n required: true\n "
},
{
"path": ".github/workflows/test.yml",
"chars": 2143,
"preview": "name: Test\n\non:\n push:\n branches:\n - master\n pull_request:\n branches:\n - master\n\njobs:\n rubocop:\n "
},
{
"path": ".gitignore",
"chars": 133,
"preview": "/.bundle/\n/.yardoc\n/_yardoc/\n/coverage/\n/doc/\n/pkg/\n/spec/reports/\n/tmp/\nGemfile.lock\n\n# rspec failure tracking\n.rspec_s"
},
{
"path": ".rspec",
"chars": 68,
"preview": "--format documentation\n--color\n--require spec_helper\n--order random\n"
},
{
"path": ".rubocop.yml",
"chars": 278,
"preview": "inherit_gem:\n rubocop-shopify: rubocop.yml\n\nAllCops:\n TargetRubyVersion: 3.1\n SuggestExtensions: false\n NewCops: ena"
},
{
"path": ".ruby-version",
"chars": 6,
"preview": "3.4.4\n"
},
{
"path": ".tool-versions",
"chars": 11,
"preview": "ruby 3.4.4\n"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 3228,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": "Gemfile",
"chars": 719,
"preview": "# frozen_string_literal: true\n\nsource \"https://rubygems.org\"\n\ngit_source(:github) { |repo_name| \"https://github.com/#{re"
},
{
"path": "LICENSE.txt",
"chars": 1080,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2018 Artur Pañach\n\nPermission is hereby granted, free of charge, to any person obt"
},
{
"path": "README.md",
"chars": 8565,
"preview": "# SidekiqAlive\n\n[](https://rubygems.org/gems/sidekiq_alive)\n[!"
},
{
"path": "Rakefile",
"chars": 201,
"preview": "# frozen_string_literal: true\n\nrequire \"bundler/gem_tasks\"\nrequire \"rspec/core/rake_task\"\n\nRSpec::Core::RakeTask.new(:sp"
},
{
"path": "bin/console",
"chars": 378,
"preview": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\nrequire \"bundler/setup\"\nrequire \"sidekiq_alive\"\n\n# You can add fixtur"
},
{
"path": "bin/setup",
"chars": 131,
"preview": "#!/usr/bin/env bash\nset -euo pipefail\nIFS=$'\\n\\t'\nset -vx\n\nbundle install\n\n# Do any other automated setup that you need "
},
{
"path": "docker-compose.yml",
"chars": 80,
"preview": "version: '3.6'\nservices:\n redis:\n image: redis\n ports:\n - 6379:6379\n"
},
{
"path": "lib/sidekiq_alive/config.rb",
"chars": 1465,
"preview": "# frozen_string_literal: true\n\nmodule SidekiqAlive\n class Config\n include Singleton\n\n attr_accessor :host,\n "
},
{
"path": "lib/sidekiq_alive/helpers.rb",
"chars": 1376,
"preview": "# frozen_string_literal: true\n\nmodule SidekiqAlive\n module Helpers\n class << self\n def sidekiq_7?\n curre"
},
{
"path": "lib/sidekiq_alive/redis/base.rb",
"chars": 622,
"preview": "# frozen_string_literal: true\n\nmodule SidekiqAlive\n module Redis\n class Base\n def set(...)\n raise(NotImp"
},
{
"path": "lib/sidekiq_alive/redis/redis_client_gem.rb",
"chars": 1247,
"preview": "# frozen_string_literal: true\n\nrequire_relative \"base\"\n\nmodule SidekiqAlive\n module Redis\n # Wrapper for `redis-clie"
},
{
"path": "lib/sidekiq_alive/redis/redis_gem.rb",
"chars": 910,
"preview": "# frozen_string_literal: true\n\nrequire_relative \"base\"\n\nmodule SidekiqAlive\n module Redis\n # Wrapper for `redis` gem"
},
{
"path": "lib/sidekiq_alive/redis.rb",
"chars": 339,
"preview": "# frozen_string_literal: true\n\nmodule SidekiqAlive\n module Redis\n class << self\n def adapter(capsule = nil)\n "
},
{
"path": "lib/sidekiq_alive/server/base.rb",
"chars": 1081,
"preview": "# frozen_string_literal: true\n\nmodule SidekiqAlive\n module Server\n module Base\n SHUTDOWN_SIGNAL = \"TERM\"\n "
},
{
"path": "lib/sidekiq_alive/server/default.rb",
"chars": 2185,
"preview": "# frozen_string_literal: true\n\nrequire_relative \"http_server\"\nrequire_relative \"base\"\n\nmodule SidekiqAlive\n module Serv"
},
{
"path": "lib/sidekiq_alive/server/http_server.rb",
"chars": 3672,
"preview": "# frozen_string_literal: true\n\nrequire \"gserver\"\n\nmodule SidekiqAlive\n module Server\n # Simple HTTP server implement"
},
{
"path": "lib/sidekiq_alive/server/rack.rb",
"chars": 1834,
"preview": "# frozen_string_literal: true\n\nrequire_relative \"base\"\n\nmodule SidekiqAlive\n module Server\n class Rack\n extend "
},
{
"path": "lib/sidekiq_alive/server.rb",
"chars": 484,
"preview": "# frozen_string_literal: true\n\nmodule SidekiqAlive\n module Server\n class << self\n def run!\n server.run!\n"
},
{
"path": "lib/sidekiq_alive/version.rb",
"chars": 75,
"preview": "# frozen_string_literal: true\n\nmodule SidekiqAlive\n VERSION = \"2.5.0\"\nend\n"
},
{
"path": "lib/sidekiq_alive/worker.rb",
"chars": 1575,
"preview": "# frozen_string_literal: true\n\nmodule SidekiqAlive\n class Worker\n include Sidekiq::Worker\n\n sidekiq_options retry"
},
{
"path": "lib/sidekiq_alive.rb",
"chars": 4776,
"preview": "# frozen_string_literal: true\n\nrequire \"sidekiq\"\nrequire \"sidekiq/api\"\nrequire \"singleton\"\nrequire \"sidekiq_alive/versio"
},
{
"path": "sidekiq_alive.gemspec",
"chars": 2215,
"preview": "# frozen_string_literal: true\n\nlib = File.expand_path(\"lib\", __dir__)\n$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?"
},
{
"path": "spec/config_spec.rb",
"chars": 278,
"preview": "# frozen_string_literal: true\n\nRSpec.describe(SidekiqAlive::Config) do\n subject(:config) { described_class.instance }\n\n"
},
{
"path": "spec/redis_spec.rb",
"chars": 612,
"preview": "# frozen_string_literal: true\n\nRSpec.describe(SidekiqAlive::Redis) do\n let(:redis) { SidekiqAlive::Redis.adapter }\n\n i"
},
{
"path": "spec/server/default_spec.rb",
"chars": 1682,
"preview": "# frozen_string_literal: true\n\nrequire \"net/http\"\n\nRSpec.describe(SidekiqAlive::Server::Default) do\n let(:port) { 7433 "
},
{
"path": "spec/server/rack_spec.rb",
"chars": 1712,
"preview": "# frozen_string_literal: true\n\nrequire \"rack/test\"\n\nENV[\"RACK_ENV\"] = \"test\"\n\nRSpec.describe(SidekiqAlive::Server::Rack)"
},
{
"path": "spec/server_spec.rb",
"chars": 4239,
"preview": "# frozen_string_literal: true\n\naround_config = proc do |example|\n ENV[\"SIDEKIQ_ALIVE_HOST\"] = \"1.2.3.4\"\n ENV[\"SIDEKIQ_"
},
{
"path": "spec/sidekiq_alive_spec.rb",
"chars": 5420,
"preview": "# frozen_string_literal: true\n\nbegin\n # this is needed for spec to work with sidekiq >7\n require \"sidekiq/capsule\"\nres"
},
{
"path": "spec/spec_helper.rb",
"chars": 760,
"preview": "# frozen_string_literal: true\n\nrequire \"simplecov\"\nSimpleCov.start\nrequire \"simplecov-cobertura\"\nSimpleCov.formatter = S"
},
{
"path": "spec/worker_spec.rb",
"chars": 2357,
"preview": "# frozen_string_literal: true\n\nRSpec.describe(SidekiqAlive::Worker) do\n subject(:perform) do\n described_class.new.pe"
},
{
"path": "tasks/version.rake",
"chars": 1668,
"preview": "# frozen_string_literal: true\n\nrequire \"semver\"\n\nmodule SidekiqAlive\n # Update app version\n #\n class VersionTask\n "
}
]
About this extraction
This page contains the full source code of the arturictus/sidekiq_alive GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 42 files (60.1 KB), approximately 17.1k tokens, and a symbol index with 139 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.