Add broker_default with nil - #124
Open
skunkworker wants to merge 7 commits into
Open
Conversation
Introduce an Appraisal matrix covering Rails 6.1 through 8.1, with the default-gem shims (logger, mutex_m, bigdecimal, drb, base64, benchmark) that ActiveSupport < 7.1 needs on Ruby >= 3.4. CircleCI now runs build_and_test as a parameterized job across the Ruby and JRuby images and each appraisal gemfile. Rails 8.0/8.1 require Ruby >= 3.2, so they get their own matrix (cimg/ruby:3.4, jruby:10.0) rather than exclude entries against the Ruby 3.1-class images. Coverage is unchanged at 20 jobs. Also add a spec helper that waits for RabbitMQ before the integration suite starts, so a cold broker fails with one clear message instead of a flurry of reconnect warnings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds a queue_type setting, configurable globally (config.queue_type) or per route (:queue_type => ...). Values are nil (default), :classic, :quorum and :stream, with :broker_default accepted as a readable alias for nil. nil leaves x-queue-type off the wire so RabbitMQ applies its own default_queue_type. :quorum and :stream force the route to be durable, since RabbitMQ only supports those as durable queues. Values are normalized on assignment, so an invalid value raises where it was set rather than at route-draw time or inside MessageRetry at runtime. BREAKING on JRuby. The two drivers disagreed: march_hare reads its :type option with fetch(:type, ... Types::CLASSIC), and fetch only falls back when the key is absent, so omitting :type injected x-queue-type: classic on every declare. bunny reads @options[:type] and sent no argument at all. Both drivers are now passed :type explicitly, so neither sends x-queue-type by default. MRI behavior is unchanged. On JRuby, newly declared queues change from classic to whatever the broker defaults to. Set config.queue_type to :classic to retain the previous behavior. Because queue type is fixed at declaration, redeclaring an existing queue against a vhost whose default_queue_type is not classic will fail with PRECONDITION_FAILED -- audit vhosts before upgrading a JRuby deployment. Known limitation, documented in the README: MessageRetry declares retry queues from the global config.queue_type rather than the originating route's, so a route-level type is not propagated to its retry queue. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bump VERSION to 7.5.0 and date the changelog entry for the queue_type setting and the JRuby x-queue-type behavior change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous commit bumped to 7.5.0, which skipped the 6.x line and read as a minor bump. Use 6.0.0 instead: a major bump is warranted because the x-queue-type change is breaking for JRuby consumers, where queues that march_hare previously declared as classic are now declared with whatever the broker defaults to. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The gemfiles/ directory is fully derived from the Appraisals file, so committing the stubs meant keeping generated output in sync by hand. Gitignore the whole directory and regenerate it in CI instead. The generate step has to override BUNDLE_GEMFILE to the root Gemfile: the job sets it to the target appraisal gemfile, which does not exist until this step writes it. Verified that `appraisal generate` runs from a clean checkout with no prior bundle install, and that its output is byte-identical to the stubs being removed here. Cache keys now also checksum Appraisals, so editing it busts the bundle cache, and are bumped to v3 since the previous caches predate this. Document the local workflow in the README, since gemfiles/ no longer exists after a fresh clone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The generate step ran `appraisal generate` without installing the root bundle first. appraisal runs under bundler, so it aborted with: Could not find gem 'active_publisher (= 1.6.0)' in locally installed gems. Run `bundle install --gemfile Gemfile` to install missing gems. Install the root Gemfile before generating. The two bundles differ only in their Rails pins and share vendor/bundle, so the follow-up install is mostly a no-op. Also cache ./gemfiles/vendor/bundle alongside ./vendor/bundle. Because `bundle config --local` resolves relative to the directory holding BUNDLE_GEMFILE, the appraisal bundle installs under gemfiles/, so caching only ./vendor/bundle reinstalled the gems the tests actually use on every run. Cache keys bumped to v4. Verified the full sequence from a clean clone with an isolated GEM_HOME under JRuby 10: root install, generate, and the rails_8.0 target install all exit 0 and resolve activesupport 8.0.5.1. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a first party
queue_typesetting so queues can be declared asclassic,quorum, orstream— or, by default, with no opinion at all so the broker's owndefault_queue_typeapplies.Configurable globally or per route:
x-queue-typesentnil(default), or:broker_defaultdefault_queue_type:classicclassic:quorumquorum:streamstreamnilis the canonical "defer to the broker" value;:broker_defaultis accepted as a more readable spelling and normalizes tonil. Values are normalized on assignment, so a typo raises anArgumentErrorwhere it was set rather than at route-draw time or insideMessageRetryat runtime.:quorumand:streamforcedurable => true, since RabbitMQ only supports those as durable queues.The bug this uncovered
The two drivers have been disagreeing about queue type this whole time.
fetchonly falls back when the key is absent. Sincesetup_queuenever passed:type, JRuby has been declaring every queue withx-queue-type: classicwhile MRI sent no argument at all. Verified against a realMarchHare::Queue:Passing
:type => nilexplicitly — key present, value nil — is what suppresses the argument. That is the whole mechanism behind:broker_default, and it's why the option has to reach the driver as an explicitnilrather than by omitting it.Worth noting for reviewers: you cannot send
x-queue-typewith a null value as a middle ground.amq-protocolwill encode it as AMQP void ("\x00\x00\x00\x0E\fx-queue-typeV"), but it can't decode its own output, and RabbitMQ resolves the type viarabbit_queue_type:discover/1, which matches no known type for a void and fails the declare. Omitting the key is the only way to defer to the broker.MRI behavior is unchanged. On JRuby, newly declared queues change from
classicto whatever the broker defaults to.This is not limited to new queues. Because queue type is fixed at declaration, an existing
classicqueue is now redeclared withoutx-queue-type. That's harmless on a vhost whosedefault_queue_typeis classic, since the broker resolves to the same type — but it fails withPRECONDITION_FAILEDon a vhost defaulting toquorumorstream.Before rolling this out to a JRuby deployment, audit every vhost it connects to:
If any are non-classic, set
config.queue_type = :classicbefore deploying — that restores the previous JRuby behavior exactly.Known limitation
MessageRetrydeclares its*.retry_*queues from the globalconfig.queue_type, not the type of the route that produced the message, so a route-level type is not propagated to its retry queue. Documented in the README rather than fixed here — propagating it isn't a straight pass-through, since retry queues carryx-message-ttlandx-dead-letter-exchangeand streams support neither. Happy to take it in a follow-up.Testing
Full suite green on JRuby 10.0.4 against a live broker: 130 examples, 0 failures (1 pending is pre-existing). New coverage in
spec/lib/action_subscriber/queue_type_spec.rbincludes JRuby-only examples that assert the actualMarchHare::Queueargument table for each option — including one pinning the surprisingomitted :type -> classicbehavior, so a driver change that alters it fails loudly.Note on commit scope
This branch carries two commits.
d08277b(Appraisal matrix, CI split by Rails version, RabbitMQ spec helper) is largely pre-existing work that was already in the working tree; the only new part is giving Rails 8.0/8.1 their own CI matrix instead ofexcludeentries against the Ruby 3.1-class images — job coverage is unchanged at 20.1a348ecis thequeue_typefeature. Review the second commit for the substance here.🤖 Generated with Claude Code