Skip to content

fix(storage): acquire lock in InMemoryBackend.get/get_last (#145) - #155

Closed
itsnevu wants to merge 3 commits into
rajfirke:mainfrom
itsnevu:fix/145-inmemory-lock
Closed

fix(storage): acquire lock in InMemoryBackend.get/get_last (#145)#155
itsnevu wants to merge 3 commits into
rajfirke:mainfrom
itsnevu:fix/145-inmemory-lock

Conversation

@itsnevu

@itsnevu itsnevu commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Summary

InMemoryBackend.get() and get_last() read self._records without holding self._lock, unlike every other method on the backend (append, count, query, all_records, add_annotation, get_annotations). This is a TOCTOU race: get_last()'s if self._records / self._records[-1] can raise IndexError under a concurrent append(), and on non-CPython runtimes the read can observe a partially-initialized dict.

Change

Wrap both reads in with self._lock:, exactly as the issue's Expected behavior describes.

Testing

Added TestInMemoryBackend::test_get_and_get_last_hold_lock, which wraps the real lock and asserts both methods enter it. Fails on main, passes with the fix.

  • Tests added/updated for the change
  • ruff check src/ tests/ passes
  • ruff format --check src/ tests/ passes
  • mypy src/provena/ passes
  • pytest passes with no failures
  • CHANGELOG.md updated

Closes #145

)

Added thread safety to record retrieval methods.
Add regression test for lock acquisition in get() and get_last() methods to prevent TOCTOU race conditions.
Ensure backend lock is held during read operations to prevent race conditions.
@rajfirke

Copy link
Copy Markdown
Owner

Merged manually as commit c27e139 — CHANGELOG had a conflict after #154 landed first. The fix itself is identical to the PR. Thanks @itsnevu!

@rajfirke rajfirke closed this Aug 28, 2026
lavneethora pushed a commit to lavneethora/provena that referenced this pull request Aug 28, 2026
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.

InMemoryBackend.get() and get_last() read _records without holding _lock

2 participants