Skip to content

Fix Sequel postgres SQL threading race condition#2739

Merged
rkoster merged 1 commit into
mainfrom
TNZ-104200-sequel-threading-mitigation
Jun 4, 2026
Merged

Fix Sequel postgres SQL threading race condition#2739
rkoster merged 1 commit into
mainfrom
TNZ-104200-sequel-threading-mitigation

Conversation

@colins
Copy link
Copy Markdown
Member

@colins colins commented Jun 2, 2026

Sequel's Dataset#select_sql caches and returns the same mutable String object to all concurrent callers. Under high concurrency, log_connection_yield prepends "(conn: NNNNN)" in-place to that shared buffer, which is then sent verbatim to Postgres as SQL:
PG::SyntaxError: ERROR: syntax error at or near "conn"

Add apply_postgres_thread_safety_patch to Config#configure_db, called after Sequel.connect when the postgres adapter is loaded. The patch prepends a module onto Sequel::Postgres::Adapter that wraps execute_query to pass String.new(sql).freeze to super, giving each call its own frozen independent C-level string buffer so concurrent PQexec calls cannot interfere.

An idempotency guard prevents double-application if configure_db is called multiple times during startup.

Specs pin the upstream Sequel caching behaviour so any gem upgrade that fixes the issue upstream will alert us to remove the patch. Unit tests cover: String.new.freeze isolation, args passthrough, idempotency, and no-op for non-postgres adapters.

We have a threading test that exposes the issue. The corruption occurs in the form of SQL the query string being mutated before being sent to Postgres. Examples of the mutated thread are this string being sent to Postgres
(conn: 152) SELECT * FROM "bug_templates" INNER JOIN "join_r...

The string corruption occurs in all of the tested 3.3 ruby release; 3.3.9, 3.3.11, 3.4.9

The string corruption does not occur in ruby 4.0.5

This patch will be short lived until the bump to ruby 4.0

[TNZ-104200, TNZ-103317]
ai-assisted=yes

What is this change about?

A customer reported Postgres failures caused by a syntax error in the received SQL string. This seems to have
been caused by a concurrency problem that was mutating a string. This patches the symptom of the failure.
The long term fix is to bumnp ruby to 4.0+

Please provide contextual information.

We created an issue with the sequel gem but it appears that ruby is the real cuplrit.
jeremyevans/sequel#2368

What tests have you run against this PR?

cd src/spec
bundle exec rake spec:unit:parallel

How should this change be described in bosh release notes?

A temporary fix to mitigate a string corruption race condition that only affects the ruby 3.3 releases

Does this PR introduce a breaking change?

No

Tag your pair, your PM, and/or team!

@aramprice

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR adds a Postgres thread-safety patch to the BOSH Director's DB configuration. It validates Sequel's upstream cached-string behavior via new specs, then adds Config.apply_postgres_thread_safety_patch which, when Sequel::Postgres::Adapter is present, prepends a module overriding execute_query to call String.new(sql).freeze before super. The patch is idempotent, invoked after establishing the DB connection, and safely no-ops if the adapter is absent; unit tests verify SQL copying, freezing, arg forwarding, and idempotency.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: applying a threading race condition fix for Sequel's Postgres SQL handling.
Description check ✅ Passed The pull request description addresses all required template sections: what the change is about, contextual links, tests run, release notes guidance, breaking changes, and team tags are all provided.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch TNZ-104200-sequel-threading-mitigation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/bosh-director/lib/bosh/director/config.rb (1)

353-393: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Gate the Postgres thread-safety patch on the active DB adapter (not just Sequel::Postgres::Adapter being loaded)

configure_db calls apply_postgres_thread_safety_patch for every connection, but the patch guard only checks defined?(Sequel::Postgres::Adapter). If Sequel’s Postgres adapter constant is already loaded (e.g., from earlier requires/tests) while running with a non-Postgres adapter like mysql2, the patch still flips @postgres_thread_safety_patched and prepends the module incorrectly.

Suggested fix
-        apply_postgres_thread_safety_patch
+        apply_postgres_thread_safety_patch(connection_config['adapter'])
...
-      def apply_postgres_thread_safety_patch
+      def apply_postgres_thread_safety_patch(adapter_name)
+        return unless adapter_name == 'postgres'
         return unless defined?(Sequel::Postgres::Adapter)
         return if `@postgres_thread_safety_patched`
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bosh-director/lib/bosh/director/config.rb` around lines 353 - 393, The
patch currently only checks for defined?(Sequel::Postgres::Adapter) which can be
true even when the active connection uses a non-Postgres adapter; update
apply_postgres_thread_safety_patch so it only flips
`@postgres_thread_safety_patched` and prepends the module when the active DB
adapter is Postgres (e.g., inspect the actual DB/adapter in use from the
connection passed into configure_db or via Sequel.database/Sequel::DATABASES to
confirm adapter_scheme == :postgres or database_type == :postgres) before
applying the patch; keep the `@postgres_thread_safety_patched` guard but
move/extend the check to verify the real adapter (refer to
apply_postgres_thread_safety_patch, configure_db,
`@postgres_thread_safety_patched`, and Sequel::Postgres::Adapter).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/bosh-director/spec/unit/bosh/director/postgres_thread_safety_patch_spec.rb`:
- Around line 55-63: Isolate the non-postgres-adapter context by ensuring
Sequel::Postgres::Adapter is undefined when testing
apply_postgres_thread_safety_patch: wrap the examples in a block that uses
RSpec's hide_const("Sequel::Postgres::Adapter") (or temporarily remove/restore
the constant) and reset
described_class.instance_variable_set(:`@postgres_thread_safety_patched`, nil)
before calling described_class.send(:apply_postgres_thread_safety_patch) so the
spec deterministically verifies that the method does not raise and does not set
`@postgres_thread_safety_patched`.

In `@src/bosh-director/spec/unit/bosh/director/sequel_spec.rb`:
- Around line 38-45: The test named 'concurrent callers receive the same
object_id, demonstrating the shared-buffer race' currently calls
dataset.select_sql sequentially; change it to spawn multiple real threads that
each call dataset.select_sql and store the returned String's object_id (use
Thread.new to call dataset.select_sql and push results into a thread-safe array,
then join all threads), then assert ids.uniq.length == 1; reference the existing
dataset variable and the select_sql calls so the concurrent behavior is
exercised across threads rather than sequentially.

---

Outside diff comments:
In `@src/bosh-director/lib/bosh/director/config.rb`:
- Around line 353-393: The patch currently only checks for
defined?(Sequel::Postgres::Adapter) which can be true even when the active
connection uses a non-Postgres adapter; update
apply_postgres_thread_safety_patch so it only flips
`@postgres_thread_safety_patched` and prepends the module when the active DB
adapter is Postgres (e.g., inspect the actual DB/adapter in use from the
connection passed into configure_db or via Sequel.database/Sequel::DATABASES to
confirm adapter_scheme == :postgres or database_type == :postgres) before
applying the patch; keep the `@postgres_thread_safety_patched` guard but
move/extend the check to verify the real adapter (refer to
apply_postgres_thread_safety_patch, configure_db,
`@postgres_thread_safety_patched`, and Sequel::Postgres::Adapter).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 90d3261e-a9d1-4b54-a5e9-c6f80e3a8e92

📥 Commits

Reviewing files that changed from the base of the PR and between 1e733d5 and 6677ee2.

📒 Files selected for processing (3)
  • src/bosh-director/lib/bosh/director/config.rb
  • src/bosh-director/spec/unit/bosh/director/postgres_thread_safety_patch_spec.rb
  • src/bosh-director/spec/unit/bosh/director/sequel_spec.rb

Comment thread src/bosh-director/spec/unit/bosh/director/postgres_thread_safety_patch_spec.rb Outdated
Comment thread src/bosh-director/spec/unit/bosh/director/sequel_spec.rb
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a temporary mitigation in bosh-director for a Sequel/Postgres concurrency issue where a cached mutable SQL String can be mutated across threads and end up being sent to Postgres (e.g., with a (conn: NNNNN) prefix), causing PG::SyntaxError.

Changes:

  • Apply a Postgres-specific monkey patch after Sequel.connect that wraps Sequel::Postgres::Adapter#execute_query to pass String.new(sql).freeze, ensuring each call uses an isolated buffer.
  • Add unit specs to validate the patch behavior (copy + freeze, args passthrough, idempotency, and no-op when the adapter isn’t loaded).
  • Add specs that pin upstream Sequel Dataset#select_sql caching behavior so future Sequel upgrades surface when the workaround may no longer be needed.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/bosh-director/lib/bosh/director/config.rb Calls a new apply_postgres_thread_safety_patch from configure_db and prepends a wrapper module onto Sequel::Postgres::Adapter#execute_query.
src/bosh-director/spec/unit/bosh/director/sequel_spec.rb Adds “upstream behavior pinning” specs around Dataset#select_sql caching and a unit-level demonstration of the String.new(sql).freeze pattern.
src/bosh-director/spec/unit/bosh/director/postgres_thread_safety_patch_spec.rb Adds focused tests for patch behavior, idempotency, and safe no-op when the Postgres adapter isn’t defined.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/bosh-director/spec/unit/bosh/director/sequel_spec.rb
Comment thread src/bosh-director/spec/unit/bosh/director/sequel_spec.rb
Copy link
Copy Markdown
Member

@beyhan beyhan left a comment

Choose a reason for hiding this comment

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

It looks good to me. I just have the two minor points I commented.

Comment thread src/bosh-director/lib/bosh/director/config.rb Outdated
rkoster
rkoster previously requested changes Jun 3, 2026
Copy link
Copy Markdown
Contributor

@rkoster rkoster left a comment

Choose a reason for hiding this comment

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

Tests could be better, but probably not worth it, since we intent to remove this code once we bump to ruby 4.x.

Comment thread src/bosh-director/lib/bosh/director/config.rb Outdated
Comment thread src/bosh-director/spec/unit/bosh/director/postgres_thread_safety_patch_spec.rb Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/bosh-director/lib/bosh/director/config.rb (1)

381-392: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Make the idempotency check depend on the adapter, not on Config state.

Config.clear on Lines 80-92 nils every class ivar, including @postgres_thread_safety_patched, but it does not undo the prepend on Sequel::Postgres::Adapter. A later configure_db call can therefore stack another wrapper onto the same adapter class, so the “apply only once” guarantee is not actually stable across reconfiguration.

Suggested fix
+      POSTGRES_THREAD_SAFETY_PATCH = Module.new do
+        private
+
+        def execute_query(sql, args)
+          super(String.new(sql).freeze, args)
+        end
+      end
+
       def apply_postgres_thread_safety_patch
         return unless defined?(Sequel::Postgres::Adapter)
-        return if `@postgres_thread_safety_patched`
+        return if Sequel::Postgres::Adapter < POSTGRES_THREAD_SAFETY_PATCH
 
-        `@postgres_thread_safety_patched` = true
-        Sequel::Postgres::Adapter.prepend(Module.new do
-          private
-
-          def execute_query(sql, args)
-            super(String.new(sql).freeze, args)
-          end
-        end)
+        Sequel::Postgres::Adapter.prepend(POSTGRES_THREAD_SAFETY_PATCH)
       end
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bosh-director/lib/bosh/director/config.rb` around lines 381 - 392, The
idempotency check currently uses Config instance state
(`@postgres_thread_safety_patched`) which is cleared by Config.clear and doesn't
prevent repeated prepends on Sequel::Postgres::Adapter; change
apply_postgres_thread_safety_patch to create or reference a persistent module
(e.g., POSTGRES_THREAD_SAFETY_PATCH = Module.new) and check
Sequel::Postgres::Adapter.ancestors.include?(POSTGRES_THREAD_SAFETY_PATCH) (or
an equivalent adapter-level marker) before calling
Sequel::Postgres::Adapter.prepend, instead of relying on
`@postgres_thread_safety_patched`, so the prepend only happens once per adapter
regardless of Config.clear.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/bosh-director/lib/bosh/director/config.rb`:
- Around line 381-392: The idempotency check currently uses Config instance
state (`@postgres_thread_safety_patched`) which is cleared by Config.clear and
doesn't prevent repeated prepends on Sequel::Postgres::Adapter; change
apply_postgres_thread_safety_patch to create or reference a persistent module
(e.g., POSTGRES_THREAD_SAFETY_PATCH = Module.new) and check
Sequel::Postgres::Adapter.ancestors.include?(POSTGRES_THREAD_SAFETY_PATCH) (or
an equivalent adapter-level marker) before calling
Sequel::Postgres::Adapter.prepend, instead of relying on
`@postgres_thread_safety_patched`, so the prepend only happens once per adapter
regardless of Config.clear.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f88df64c-cdbe-4215-b4e9-7550a07b92d6

📥 Commits

Reviewing files that changed from the base of the PR and between 90a3d56 and 47c20a4.

📒 Files selected for processing (2)
  • src/bosh-director/lib/bosh/director/config.rb
  • src/bosh-director/spec/unit/bosh/director/postgres_thread_safety_patch_spec.rb

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 3, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/bosh-director/lib/bosh/director/config.rb (1)

381-387: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make the idempotency check survive Config.clear.

@postgres_thread_safety_patched lives on Bosh::Director::Config, but clear nils all class ivars at src/bosh-director/lib/bosh/director/config.rb:80-92 without removing the module already prepended onto Sequel::Postgres::Adapter. After a clear/reconfigure cycle in the same process, this method will prepend another wrapper and keep growing the ancestor chain. Please key the guard off the adapter itself instead (for example, prepend a named module constant and return when Sequel::Postgres::Adapter < ThatModule).

Possible fix
+      POSTGRES_THREAD_SAFETY_PATCH = Module.new do
+        private
+
+        def execute_query(sql, args)
+          super(String.new(sql).freeze, args)
+        end
+      end
+
       def apply_postgres_thread_safety_patch(adapter_name)
         return unless adapter_name == 'postgres'
         return unless defined?(Sequel::Postgres::Adapter)
-        return if `@postgres_thread_safety_patched`
+        return if Sequel::Postgres::Adapter < POSTGRES_THREAD_SAFETY_PATCH
 
-        `@postgres_thread_safety_patched` = true
-        Sequel::Postgres::Adapter.prepend(Module.new do
-          private
-
-          def execute_query(sql, args)
-            super(String.new(sql).freeze, args)
-          end
-        end)
+        Sequel::Postgres::Adapter.prepend(POSTGRES_THREAD_SAFETY_PATCH)
       end
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bosh-director/lib/bosh/director/config.rb` around lines 381 - 387, The
idempotency guard using `@postgres_thread_safety_patched` can be lost by
Config.clear and causes repeated prepends; instead create a named module
constant (e.g. POSTGRES_THREAD_SAFETY_PATCH_MODULE) containing the wrapper and
in apply_postgres_thread_safety_patch check the adapter itself (return if
Sequel::Postgres::Adapter < POSTGRES_THREAD_SAFETY_PATCH_MODULE) before
prepending that constant onto Sequel::Postgres::Adapter; replace the
instance-variable guard (`@postgres_thread_safety_patched`) with this module-based
ancestor check so repeated clear/reconfigure cycles do not re-prepend the
wrapper.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/bosh-director/spec/unit/bosh/director/postgres_thread_safety_patch_spec.rb`:
- Around line 58-60: The test uses %w[...] which produces only strings so the
nil branch of apply_postgres_thread_safety_patch is never exercised; change the
test data to include a real nil (e.g. ['mysql2', 'sqlite', nil]) so
described_class.send(:apply_postgres_thread_safety_patch, adapter) is invoked
with adapter == nil and adapter.inspect in the example name still behaves as
expected; update the iterated array in the spec accordingly to cover the
adapter_name == nil case.

---

Outside diff comments:
In `@src/bosh-director/lib/bosh/director/config.rb`:
- Around line 381-387: The idempotency guard using
`@postgres_thread_safety_patched` can be lost by Config.clear and causes repeated
prepends; instead create a named module constant (e.g.
POSTGRES_THREAD_SAFETY_PATCH_MODULE) containing the wrapper and in
apply_postgres_thread_safety_patch check the adapter itself (return if
Sequel::Postgres::Adapter < POSTGRES_THREAD_SAFETY_PATCH_MODULE) before
prepending that constant onto Sequel::Postgres::Adapter; replace the
instance-variable guard (`@postgres_thread_safety_patched`) with this module-based
ancestor check so repeated clear/reconfigure cycles do not re-prepend the
wrapper.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ba8785d8-7d88-4ead-ac69-d252daf40272

📥 Commits

Reviewing files that changed from the base of the PR and between 47c20a4 and f7f408f.

📒 Files selected for processing (2)
  • src/bosh-director/lib/bosh/director/config.rb
  • src/bosh-director/spec/unit/bosh/director/postgres_thread_safety_patch_spec.rb

Comment thread src/bosh-director/spec/unit/bosh/director/postgres_thread_safety_patch_spec.rb Outdated
@colins colins force-pushed the TNZ-104200-sequel-threading-mitigation branch 2 times, most recently from 8aa4a65 to cab9619 Compare June 3, 2026 21:47
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 3, 2026
Comment thread src/bosh-director/spec/unit/bosh/director/sequel_spec.rb Outdated
Comment thread src/bosh-director/spec/unit/bosh/director/sequel_spec.rb Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

Comment thread src/bosh-director/lib/bosh/director/config.rb
Comment thread src/bosh-director/lib/bosh/director/config.rb
Comment thread src/bosh-director/spec/unit/bosh/director/sequel_spec.rb
@colins colins force-pushed the TNZ-104200-sequel-threading-mitigation branch from cab9619 to 96bacea Compare June 3, 2026 22:35
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 3, 2026
@aramprice aramprice requested a review from rkoster June 3, 2026 22:39
@aramprice aramprice dismissed rkoster’s stale review June 3, 2026 22:40

internal ticket id's removed from code

@colins colins force-pushed the TNZ-104200-sequel-threading-mitigation branch 2 times, most recently from 9a64f25 to 6c15687 Compare June 4, 2026 00:49
@linux-foundation-easycla
Copy link
Copy Markdown

linux-foundation-easycla Bot commented Jun 4, 2026

CLA Signed
The committers listed above are authorized under a signed CLA.

  • ✅ login: colins / name: Colin Shield (13fe959)

Copy link
Copy Markdown
Member

@aramprice aramprice left a comment

Choose a reason for hiding this comment

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

EasyCLA appears confused by the git-bot co-signing on the latest commit.

We can override, or you can re-push the commit without any git-bot attribution, and that should fix it.

@github-project-automation github-project-automation Bot moved this from Waiting for Changes | Open for Contribution to Pending Merge | Prioritized in Foundational Infrastructure Working Group Jun 4, 2026
@aramprice
Copy link
Copy Markdown
Member

/easycla

@colins colins force-pushed the TNZ-104200-sequel-threading-mitigation branch 2 times, most recently from 13fe959 to 91594eb Compare June 4, 2026 02:41
Sequel's Dataset#select_sql caches and returns the same mutable
String object to all concurrent callers. Under high concurrency,
log_connection_yield prepends "(conn: NNNNN)" in-place to that
shared buffer, which is then sent verbatim to Postgres as SQL:
  PG::SyntaxError: ERROR: syntax error at or near "conn"

Add apply_postgres_thread_safety_patch to Config#configure_db,
called after Sequel.connect when the postgres adapter is loaded.
The patch prepends a module onto Sequel::Postgres::Adapter that
wraps execute_query to pass String.new(sql).freeze to super,
giving each call its own frozen independent C-level string buffer
so concurrent PQexec calls cannot interfere.

An idempotency guard prevents double-application if configure_db
is called multiple times during startup.

Specs pin the upstream Sequel caching behaviour so any gem upgrade
that fixes the issue upstream will alert us to remove the patch.
Unit tests cover: String.new.freeze isolation, args passthrough,
idempotency, and no-op for non-postgres adapters.

We have a threading test that exposes the issue. The corruption
occurs in the form of SQL the query string being mutated before
being sent to Postgres. Examples of the mutated thread are this
string being sent to Postgres
(conn: 152) SELECT * FROM "bug_templates" INNER JOIN "join_r...

The string corruption occurs in all of the tested 3.3 ruby releases;
3.3.9, 3.3.11, 3.4.9

The string corruption does not occur in ruby 4.0.5

This patch will be short lived until the bump to ruby 4.0

There is a test that fails intentionally once the runtime is upgraded,
prompting removal of Config#apply_postgres_thread_safety_patch
and its call site.

postgres_thread_safety_patch_spec is DB-agnostic

[TNZ-104200, TNZ-103317]
ai-assisted=yes
@colins colins force-pushed the TNZ-104200-sequel-threading-mitigation branch from 91594eb to 7263d8c Compare June 4, 2026 02:44
@rkoster rkoster merged commit ad09ae6 into main Jun 4, 2026
22 checks passed
@rkoster rkoster deleted the TNZ-104200-sequel-threading-mitigation branch June 4, 2026 12:59
@github-project-automation github-project-automation Bot moved this from Pending Merge | Prioritized to Done in Foundational Infrastructure Working Group Jun 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

5 participants