Skip to content

StateManagerDisk debounced writes can persist stale state after repeated set_state callsย #6839

Description

@harsh21234i

Description

StateManagerDisk.set_state only adds a token to _write_queue if that token is not already queued. When disk write debouncing is enabled, a second set_state call for the same token before the queued item is flushed does not replace the queued state.

That means the disk backend can persist an older state snapshot even though a newer set_state call completed successfully.

Relevant code path:

if self._write_debounce_seconds > 0:
    if token not in self._write_queue:
        self._write_queue[token] = QueueItem(
            token=token,
            state=state,
            timestamp=time.time(),
        )

Because the existing QueueItem is left unchanged, _flush_write_queue / _process_write_queue later writes the first queued value, not the latest value.

Reproduction

import asyncio
import tempfile
from pathlib import Path

from reflex.istate.manager.disk import StateManagerDisk
from reflex.istate.manager.token import StateToken

async def main():
    with tempfile.TemporaryDirectory() as tmp:
        manager = StateManagerDisk()
        manager.__dict__["states_directory"] = Path(tmp)
        manager._write_debounce_seconds = 60
        token = StateToken(ident="client", cls=int)

        await manager.set_state(token, 1)
        await manager.set_state(token, 2)

        print("queued_state", manager._write_queue[token].state)
        await manager.close()

        reloaded = StateManagerDisk()
        reloaded.__dict__["states_directory"] = Path(tmp)
        print("persisted", await reloaded.load_state(token))
        await reloaded.close()

asyncio.run(main())

Current output:

queued_state 1
persisted 1

Expected behavior

The latest completed set_state call should win. The queued and persisted value should be 2.

Actual behavior

The first queued value is retained and persisted. Later set_state calls for the same token during the debounce window are ignored by the write queue.

Impact

This is backend state persistence behavior specific to the disk state manager. It can cause stale state to be written to disk when callers use direct set_state with replacement state objects while debounced writes are pending.

The common modify_state path may hide this when the queued object is mutated in place, but direct replacement through set_state can lose the newer value.

Possible fix

When a token is already queued, update the queued item to reference the latest state. Depending on intended debounce semantics, either preserve the original timestamp to flush at the first scheduled time or refresh the timestamp to debounce from the latest update. The key requirement is that the queued state object must be the latest completed set_state value.

Duplicate check

I searched for existing open/closed issues and PRs using terms around StateManagerDisk, _write_queue, debounce, set_state, stale state, and lost disk persistence, and did not find an existing report for this behavior.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions