Skip to content

Sidekiq middleware#53

Merged
dduugg merged 3 commits intorubyatscale:mainfrom
Envek:feat/sidekiq-middleware
Mar 6, 2026
Merged

Sidekiq middleware#53
dduugg merged 3 commits intorubyatscale:mainfrom
Envek:feat/sidekiq-middleware

Conversation

@Envek
Copy link
Contributor

@Envek Envek commented Feb 26, 2026

Resolves #36

For discussion: flamegraph { } method by default opens captured flamegraph in browser and prints some messages to stdout, should we revise it? (maybe in separate pull request)

@github-project-automation github-project-automation bot moved this to Triage in Modularity Feb 26, 2026
@dduugg dduugg requested a review from a team March 5, 2026 17:52
Copy link
Contributor

@dduugg dduugg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review: "Sidekiq middleware"

Goal

Resolves #36. Adds a Singed::Sidekiq::ServerMiddleware so flamegraphs can be captured for Sidekiq jobs, with support for ActiveJob-wrapped jobs and three opt-in mechanisms: the x-singed payload key, a class-level capture_flamegraph? method, and the SINGED_MIDDLEWARE_ALWAYS_CAPTURE env var.


Implementation

  • lib/singed/sidekiq.rb (new): The middleware itself. call checks capture_flamegraph? and either delegates to the block or wraps it in flamegraph(label). Supports ActiveJob wrapping via job_payload["wrapped"], and resolves job classes via constantize or Object.const_get.
  • spec/singed/sidekiq_spec.rb (new): Good coverage of plain Sidekiq jobs, class-level capture_flamegraph?, SINGED_MIDDLEWARE_ALWAYS_CAPTURE, and ActiveJob jobs.
  • spec/support/sidekiq.rb (new): Test harness with a SidekiqTestingInlineWithMiddlewares module that patches Sidekiq::Client#push to invoke server middlewares (Sidekiq's inline test mode normally skips them). Defines fixture job classes.
  • README.md: Documents the new middleware and its three opt-in mechanisms.
  • Gemfile / Gemfile.lock: Adds sidekiq and activejob as dev dependencies.

Risk Assessment

Low–Medium risk. This is additive (no changes to existing code paths), well-tested, and opt-in. A few things worth noting:

1. README example has a syntax error

The README shows:

MyJob.set(x-singed: true).perform_async

x-singed is not a valid Ruby symbol literal — it will be parsed as x - singed (subtraction). It should be:

MyJob.set("x-singed" => true).perform_async

2. job_class resolution could raise on unknown constants

Object.const_get(job_class.to_s) will raise NameError if the class can't be found (e.g. a stale job referencing a renamed/deleted class). This is an edge case but worth wrapping in a rescue if you want the middleware to be defensive:

Object.const_get(job_class.to_s)
rescue NameError
  nil

(Then capture_flamegraph? and flamegraph_label would need to handle nil gracefully.)

3. capture_flamegraph? reads job_payload["x-singed"] as a raw value

The value is returned as-is without coercion, so job_payload["x-singed"] = false would correctly skip capture, but job_payload["x-singed"] = "false" (a string) would be truthy and incorrectly trigger capture. Since the Rack middleware uses TRUTHY_STRINGS for env var coercion, consider applying the same treatment here, or at least documenting that the value must be a boolean.

4. SidekiqTestingInlineWithMiddlewares patches Sidekiq::Client#push globally

The config.before(:suite) block in spec/support/sidekiq.rb prepends this module for the entire test suite. If other specs rely on standard inline behavior, this could affect them. A narrower scope (e.g. before in the specific spec file) would be safer.

5. Singed.output_directory is set globally in before(:suite)

Singed.output_directory = Dir.mktmpdir("singed-sidekiq-spec")

This is a global side effect that affects all other specs run in the same suite. Consider resetting it after the suite or scoping it to the sidekiq specs only.

6. Minor: self.job_class shadowing

In capture_flamegraph?, the method calls self.job_class(...) but job_class is also a private instance method defined below. This works, but using self. on a private method is unusual in Ruby and could be confusing. Drop self. since it's called within the same object.


Summary

Solid, additive feature with good test coverage. Main items before merging:

  • Fix the README example: x-singed:"x-singed" =>
  • Consider coercing job_payload["x-singed"] to boolean (consistent with how other flags are handled)
  • Consider guarding Object.const_get against NameError
  • Scope the SidekiqTestingInlineWithMiddlewares prepend and Singed.output_directory assignment more narrowly to avoid global test suite side effects

Envek added 2 commits March 6, 2026 21:42
…ware

* upstream/main:
  Bump rexml from 3.3.3 to 3.4.2
  Add checks: write permission to standardrb workflow
  Fix inaccuracies in AGENTS.md
  Add AGENTS.md and CLAUDE.md for AI coding agent guidance
  Trigger CI
  Add CODEOWNERS file
  Trigger CI on pull_request events
  Install latest bundler instead of pinned version
  Remove Ruby 3.1, simplify matrix, use bundler 4.0.7; bump required_ruby_version to >= 3.2.0
  Update Ruby CI matrix: remove 2.7 and 3.0, add 3.3, 3.4, 4.0; bump required_ruby_version to >= 3.1.0
  Bump rexml from 3.2.6 to 3.3.3
@Envek
Copy link
Contributor Author

Envek commented Mar 6, 2026

1. README example has a syntax error

Fixed

2. job_class resolution could raise on unknown constants

Fixed

3. capture_flamegraph? reads job_payload["x-singed"] as a raw value

Added check against truthy values too

4. SidekiqTestingInlineWithMiddlewares patches Sidekiq::Client#push globally

There is nothing else Sidekiq-related planned, so I believe it is fine

5. Singed.output_directory is set globally in before(:suite)

Fixed

6. Minor: self.job_class shadowing

Fixed

@Envek Envek requested a review from dduugg March 6, 2026 13:15
Copy link
Contributor

@dduugg dduugg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Envek for the thoughtful replies and follow-up fixes! The TRUTHY_STRINGS coercion for x-singed, the NameError rescue, the README fix, and the per-test output_directory scoping are all clean. The pragmatic call on keeping the global prepend is fine given there are no other Sidekiq specs to conflict with. Merging.

@dduugg dduugg merged commit 28e8573 into rubyatscale:main Mar 6, 2026
@github-project-automation github-project-automation bot moved this from Triage to Done in Modularity Mar 6, 2026
Envek added a commit to Envek/singed that referenced this pull request Mar 9, 2026
* upstream/main:
  Sidekiq middleware (rubyatscale#53)
  Methods for explicit start and stop flamegraph recording (rubyatscale#52)
Envek added a commit to Envek/singed that referenced this pull request Mar 9, 2026
* upstream/main:
  Sidekiq middleware (rubyatscale#53)
  Methods for explicit start and stop flamegraph recording (rubyatscale#52)
  Bump rexml from 3.3.3 to 3.4.2
  Add checks: write permission to standardrb workflow
  Fix inaccuracies in AGENTS.md
  Add AGENTS.md and CLAUDE.md for AI coding agent guidance
  Trigger CI
  Add CODEOWNERS file
  Trigger CI on pull_request events
  Install latest bundler instead of pinned version
  Remove Ruby 3.1, simplify matrix, use bundler 4.0.7; bump required_ruby_version to >= 3.2.0
  Update Ruby CI matrix: remove 2.7 and 3.0, add 3.3, 3.4, 4.0; bump required_ruby_version to >= 3.1.0
  Bump rexml from 3.2.6 to 3.3.3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Sidekiq middleware

2 participants