Skip to content

Make request() cancellable - #51

Merged
matejdro merged 3 commits into
pebble-dev:mainfrom
neelts:cancellable-request
Sep 18, 2026
Merged

matejdro merged 3 commits into
pebble-dev:mainfrom
neelts:cancellable-request

Conversation

@neelts

@neelts neelts commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

UniversalRequestResponse.request() used suspendCoroutine, which does not support cancellation. A caller that cancels the wait, for example with a timeout, stays suspended until the answer arrives or forever, when the target app dies after it received the request.

request() now uses suspendCancellableCoroutine.
Cancellation resumes the caller, and a late answer is ignored.

Motivated by review feedback on coredevices/mobileapp#378, which wraps its data logging calls in a timeout that could not fire.

request() used suspendCoroutine, which does not support cancellation.
A caller that timed out stayed suspended until the answer arrived, or
forever when the target app died after it received the request.

request() now uses suspendCancellableCoroutine. The caller can cancel
the wait, and a late answer is ignored.
@matejdro

matejdro commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

I'm not sure this is a good idea as the underlying request will not get cancelled (there is no mechanism to do so) and will get left dangling.

Have you actually experienced infinite suspend here?

request() now links to the death of the target binder. When the target
app dies after it received the request, the caller resumes with null
instead of a permanent suspend. The death link is removed when the call
completes or the caller cancels.
@neelts

neelts commented Sep 17, 2026

Copy link
Copy Markdown
Contributor Author

no, I did not see it in production but the case is real. code catches DeadObjectException only around the synchronous call. sp when the target dies after it receiving request, the caller stays suspended forever. sjp4 flagged this in the coredevices/mobileapp#378 review

now PR is extended with linkToDeath way, could this work maybe?

try {
return result.await()
} finally {
runCatching { binder.unlinkToDeath(deathRecipient, 0) }

@matejdro matejdro Sep 18, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please use try catch instead, runCatching without passing the Result is just a try..catch with extra object allocations.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done, thanks!

@matejdro

matejdro commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

This looks fine to me, other than the minor runCatching comment. It also handles the cancellation (which I changed my mind now, is probably a good idea that we allow, even if we cannot propagate it).

runCatching allocates a Result object for no gain here.
@matejdro
matejdro merged commit b3389dd into pebble-dev:main Sep 18, 2026
1 check passed
@matejdro

Copy link
Copy Markdown
Contributor

thanks!

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