Skip to content

Store freeze opt-out flag outside IsolatedExecutionState#157

Open
joeljunstrom wants to merge 1 commit into
basecamp:masterfrom
joeljunstrom:fix-fiber-isolation-flag-wipe
Open

Store freeze opt-out flag outside IsolatedExecutionState#157
joeljunstrom wants to merge 1 commit into
basecamp:masterfrom
joeljunstrom:fix-fiber-isolation-flag-wipe

Conversation

@joeljunstrom
Copy link
Copy Markdown

We saw our prod consoles starting to break after switching to fiber isolation. This happened immediately when Rails tries to connect to postgres.

The flag was a thread_mattr_accessor, which routes through ActiveSupport::IsolatedExecutionState. Rails 8.1 defaults isolation_level to :fiber and flips it inside after_initialize, the setter clears the old scope's storage before swapping, wiping the false written at eager-load by Ext::Core::Object and Ext::Core::String.

The default true then takes over and Refrigerator#freeze_all installs instance_variable_get/set overrides on Object, breaking anything that introspects ivars (e.g. the postgres adapter in destroy_all/update_all).

Storing the flag in a singleton-class ivar on the host keeps each Freezeable independent (which is why mattr_accessor was avoided here, its class variable would leak through Object's ancestor chain) and is unaffected by the isolation_level switch.

The flag was a thread_mattr_accessor, which routes through
ActiveSupport::IsolatedExecutionState. Rails 8.1 defaults isolation_level
to :fiber and flips it inside after_initialize; the setter clears the old
scope's storage before swapping, wiping the false written at eager-load
by Ext::Core::Object and Ext::Core::String. The default true then takes
over and Refrigerator#freeze_all installs instance_variable_get/set
overrides on Object, breaking anything that introspects ivars (e.g. the
postgres adapter in destroy_all/update_all).

Storing the flag in a singleton-class ivar on the host keeps each
Freezeable independent (which is why mattr_accessor was avoided here -
its class variable would leak through Object's ancestor chain) and is
unaffected by the isolation_level switch.
Copilot AI review requested due to automatic review settings May 25, 2026 17:24
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

Copy link
Copy Markdown

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

Note

Copilot was unable to run its full agentic suite in this review.

This PR updates how Console1984::Freezeable stores the prevent_instance_data_manipulation_after_freezing flag so it doesn’t reset when ActiveSupport::IsolatedExecutionState is cleared, and adds tests to lock in the intended behavior.

Changes:

  • Replace thread_mattr_accessor storage with per-host storage on the including class.
  • Add tests covering default value, persistence across IsolatedExecutionState.clear, and non-leakage between hosts.

Reviewed changes

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

File Description
test/freezeable_test.rb Adds coverage for default behavior, persistence across isolation state clearing, and isolation between hosts.
lib/console1984/freezeable.rb Changes flag storage to avoid IsolatedExecutionState clearing and prevent ancestor-chain leakage.

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

Comment on lines +26 to +36
# true by default. Stored as a singleton-class instance variable on the host so it survives
# +ActiveSupport::IsolatedExecutionState+ being cleared when Rails switches +isolation_level+
# (e.g. the +:thread+ -> +:fiber+ flip the Rails 8.1 default triggers in +after_initialize+).
# A +mattr_accessor+ would leak the flag across the ancestor chain because +Console1984::Ext::Core::Object+
# is included into +Object+; this storage keeps each host independent.
base.singleton_class.class_eval do
attr_writer :prevent_instance_data_manipulation_after_freezing

define_method :prevent_instance_data_manipulation_after_freezing do
return @prevent_instance_data_manipulation_after_freezing if defined?(@prevent_instance_data_manipulation_after_freezing)
true
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

idk up to maintainer, feels like we are trading a lot of complexity for something that would be rare indeed in a console session?

Comment on lines +26 to +30
# true by default. Stored as a singleton-class instance variable on the host so it survives
# +ActiveSupport::IsolatedExecutionState+ being cleared when Rails switches +isolation_level+
# (e.g. the +:thread+ -> +:fiber+ flip the Rails 8.1 default triggers in +after_initialize+).
# A +mattr_accessor+ would leak the flag across the ancestor chain because +Console1984::Ext::Core::Object+
# is included into +Object+; this storage keeps each host independent.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Pretty darn nit-picky mr robot =)

attr_writer :prevent_instance_data_manipulation_after_freezing

define_method :prevent_instance_data_manipulation_after_freezing do
return @prevent_instance_data_manipulation_after_freezing if defined?(@prevent_instance_data_manipulation_after_freezing)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants