Skip to content

Return an error when a ClientResponse is reused - #1902

Closed
DanGould wants to merge 1 commit into
payjoin:masterfrom
DanGould:ffi-client-response-reuse
Closed

DanGould wants to merge 1 commit into
payjoin:masterfrom
DanGould:ffi-client-response-reuse

Conversation

@DanGould

Copy link
Copy Markdown
Member

A ClientResponse can only be used once, because process_response takes the OHTTP context out of it. Nothing in the bindings says so: the object is handed out as a shared reference and every process_response borrows it. A caller that retries after a transient failure with the same ClientResponse hit expect("ClientResponse moved out of memory"). Across uniffi that panic reaches the caller as an opaque internal error, or aborts the process in builds with panic = "abort".

This makes the conversion fallible and adds ClientResponseError::AlreadyUsed. The five methods that take a ClientResponse return it instead of panicking:

  • WithReplyKey::process_response
  • PollingForProposal::process_response
  • Initialized::process_response
  • PayjoinProposal::process_response
  • HasReplyableError::process_error_response

In Rust these now return Result<Transition, ClientResponseError>. In the generated bindings the return types are unchanged and the methods can now throw the new error (ClientResponseException in Kotlin, ClientResponseError.AlreadyUsed in Python). Existing binding code compiles as is. To retry, create a new request, which gives a fresh context. The payjoin crate is untouched.

The new sender and receiver tests fail a first process_response transiently with an undersized body, then call it again with the same context and expect AlreadyUsed. Both panicked on master.

The transition save() methods have the same take-then-expect pattern. I left them out to keep this change small.

Disclosure: co-authored by Claude Code. The bug was found by an automated code scanner.

Each process_response call takes the OHTTP context out of the
ClientResponse it is given. Bindings hand ClientResponse out as a
shared object and pass it by reference, so nothing stops a caller
from passing the same one twice, for example when retrying after a
transient failure. The second call panicked, which crosses the FFI
boundary as an opaque exception, or aborts the process when built
with panic=abort.

Make the conversion fallible and return a new ClientResponseError
from every process_response and process_error_response instead.
These methods now throw in the foreign bindings; their return types
are unchanged.
@DanGould DanGould closed this Sep 24, 2026
@DanGould
DanGould deleted the ffi-client-response-reuse branch September 24, 2026 05:26
@coveralls

Copy link
Copy Markdown
Collaborator

Coverage Report for CI Build 35959851359

Coverage remained the same at 86.869%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 17013
Covered Lines: 14779
Line Coverage: 86.87%
Coverage Strength: 333.81 hits per line

💛 - Coveralls

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