Skip to content

Fix in-place session data mutations being silently dropped - #238

Open
albertski wants to merge 1 commit into
rails:mainfrom
albertski:fix-in-place-session-mutation
Open

Fix in-place session data mutations being silently dropped#238
albertski wants to merge 1 commit into
rails:mainfrom
albertski:fix-in-place-session-mutation

Conversation

@albertski

@albertski albertski commented Aug 2, 2026

Copy link
Copy Markdown

Fixes: #236

Since 2.3.0, in-place mutations to nested session data are silently lost:

session[:items] << new_item   # change never persisted

The 2.3.0 optimization added change detection in data= by comparing the incoming value against self.data. The problem is that get_session returns the raw @data object reference. When user code mutates it in-place, @data is also mutated. By the time write_session calls record.data = session_data, both sides of the comparison point to the same already-mutated object, so changed? returns false and the save is skipped.

How I experienced the issue

We use Devise's Timeoutable module, which updates last_request_at on the session on every request. The bug meant this updated timestamp was never actually saved to the database and each write was silently skipped.

Devise would read back the stale last_request_at value from when the user first logged in. Since that timestamp was always older than the timeout window, users would get logged out regardless of whether they had been actively using the app.

Instead of timing out after a period of inactivity, the session would expire at a fixed point after login.

Having said all that, the intention of the optimization is valid. I created heartcombo/devise#5857 to have apps using :timeoutable to take advantage of the optimization.

Fix

When session data is first lazily deserialized from the database, store a deep copy (@data_snapshot via Marshal.load(Marshal.dump(@data))). The data= setter then compares against this snapshot instead of the live @data reference, correctly detecting in-place mutations.

When session data is mutated, the change detection in data= compared against the already-mutated @DaTa object, so changed? returned false and the save was skipped.
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.

Title: 2.3.0 drops in-place nested session writes (changed? misses mutations)

1 participant