Skip to content

Ship InMemoryPersister to close #1519#1528

Merged
spacebear21 merged 4 commits intopayjoin:masterfrom
DanGould:persister-cleanup-1519
May 6, 2026
Merged

Ship InMemoryPersister to close #1519#1528
spacebear21 merged 4 commits intopayjoin:masterfrom
DanGould:persister-cleanup-1519

Conversation

@DanGould
Copy link
Copy Markdown
Contributor

@DanGould DanGould commented May 6, 2026

Used Claude Opus 4.7 on max effort by driving a modified version of Jesse Posner's /meta-agent skill that had the #1519 context of the issue and preceding comment and a Q&A session with me marshaled to a worker commit-gated by a pre-commit hook that resembles CI. Meta strangely had the worker produce changelog updates in commits, which I had to manually remove because that's just not our process.

Closes #1519 with a net delete!

Pinging @Arowolokehinde, who stepped up to help on this one, and looking forward to your review. I appreciate you! But I don't want to block on you so no big pressure, and formally requesting @spacebear21 as the ultimately responsible reviewer for this PR.

Pull Request Checklist

Please confirm the following before requesting review:

DanGould added 4 commits May 7, 2026 01:19
Move the in-memory persister out of the feature-gated test_utils
module so external consumers can use it directly. The sync persister
is now publicly available at payjoin::persist::InMemoryPersister
without any feature gate. The async companion is renamed to
InMemoryAsyncPersister but stays cfg(test)-only since tokio remains
a dev dependency and only internal unit tests touch it.

Update the payjoin-test-utils re-export and all in-tree consumers
to import from the new location.
InMemoryPersister::default() is a strict superset of
NoopSessionPersister: it accumulates events instead of discarding
them, but no existing test asserts the no-events-saved behavior.
Swap every call site over and tidy the variable names so the next
commit can delete NoopSessionPersister.
Drop NoopSessionPersister and NoopPersisterEvent now that every
caller uses InMemoryPersister::default(). InMemoryPersister gives
the same drop-in ergonomics for callers that ignore the event log,
plus the option to inspect events when needed.

Swap the payjoin-ffi public re-export to InMemoryPersister so
downstream FFI consumers keep a usable persister type.
Nothing in the payjoin crate is gated on this feature anymore now
that InMemoryPersister is public unconditionally. Drop the feature
declaration and stop requesting it from payjoin-test-utils.

The unrelated _test-utils feature on payjoin-ffi is left alone; it
gates the BitcoindEnv / TestServices uniffi exports used by FFI
test scripts.
@DanGould DanGould requested a review from spacebear21 May 6, 2026 17:40
@spacebear21
Copy link
Copy Markdown
Collaborator

cACK. noting for follow-up: Each language binding reimplements InMemoryPersisters for the receiver and sender in tests, we could do a big code deletion there now that it's exposed.

@DanGould
Copy link
Copy Markdown
Contributor Author

DanGould commented May 6, 2026

Also want to say that while the commit that moves InMemoryTestPersister should be mechanically identical, I sicced robots to check explicitly and the gaze down the line suggests that is indeed what changed here.

@coveralls
Copy link
Copy Markdown
Collaborator

Coverage Report for CI Build 25451287814

Coverage increased (+0.03%) to 85.169%

Details

  • Coverage increased (+0.03%) from the base build.
  • Patch coverage: 95 of 95 lines across 5 files are fully covered (100%).
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 13519
Covered Lines: 11514
Line Coverage: 85.17%
Coverage Strength: 400.01 hits per line

💛 - Coveralls

Copy link
Copy Markdown
Collaborator

@spacebear21 spacebear21 left a comment

Choose a reason for hiding this comment

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

ACK 9c8edf6

@spacebear21 spacebear21 merged commit 676ede9 into payjoin:master May 6, 2026
23 checks passed
@nothingmuch
Copy link
Copy Markdown
Contributor

InMemoryAsyncPersister doesn't make sense to use except in tests, at least in rust based apps. providing an RWLock wrapper with interior for it seems like an invitation to use it for writing from two threads which seems like a bad idea.

is it necessary for FFI stuff? if it was possible to split into a sharable reader that can replay the event log but not persist new events and a single writer that has ownership? something based on a channel abstraction that ensures there's only a single producer but allows multiple readers seems like a safer thing for sharing session events with say a GUI

@DanGould
Copy link
Copy Markdown
Contributor Author

DanGould commented May 7, 2026

InMemoryAsyncPersister is indeed only used in tests here and only compiled with cfg(test), and I believe the in-memory implementations in FFI, including async, are also test-only.

@DanGould
Copy link
Copy Markdown
Contributor Author

DanGould commented May 7, 2026

If we want to write from two threads, I think the change might need to apply to the regular InMemoryPersister too. My intent was that to be used for tests,doctests, examples, and prototypes/draft prs before real persistence was done. But I started to vaguely imagine some situations where maybe a complete in-memory session could be useful for short-lived sessions, but the short lived sessions we've actually had demand for are in business cases that revolve around the bitcoin exchange rate quote expiration and therefore bitcoin URI expiration enforced by payjoin session expiration at BTCPay and Bull Bitcoin Exchange. In those business settings, you're definitely going to want payjoin state persisted for your records.

All this is to say it may make more sense to EITHER

  1. follow up with a doc comment stating this is for a single owner / not designed for concurrent producers unless it's super simple to implement the alternative
  2. the owned writer + sharable read-only replay handle + channel abstraction into our InMemoryPersister implementations, plural.

since review burden on this is the same either way (we need another PR), I'm getting clankers to write option 2 and we can just remove the footgun.

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.

Clean up Persister implementations that ship with PDK

4 participants