Skip to content

Refactor how RPCs are delivered#4161

Merged
kentonv merged 9 commits intomainfrom
kenton/reentry-callback
May 20, 2025
Merged

Refactor how RPCs are delivered#4161
kentonv merged 9 commits intomainfrom
kenton/reentry-callback

Conversation

@kentonv
Copy link
Member

@kentonv kentonv commented May 17, 2025

This introduces IoContext::makeReentryCallback(), which takes a function intended to run under the isolate lock, and returns a function which can be invoked outside the lock, and which will do all the necessary things to enter the isolate. This has proven more complicated than it looks. See the doc comment for details.

I created this for a different use case, but in this PR I went ahead and used it to clean up how JsRpcTarget works, as I was fairly unhappy with the existing code.

This fixes at least one bug with RPC: If you made an RPC call during blockConcurrentlyWhile, and passed it a callback, calls to the callback would be blocked until the end of blockConcurrencyWhile. This goes against the usual semantics of blockConcurrencyWhile, which says that interactions initiated from inside the block are allowed to complete inside the block.

This PR also fixes the warning Adding task to IoContext with no current IncomingRequest, but sort of by accident, and there are deeper underlying problems there which aren't actually fixed. I might work on that in a separate PR.

kentonv added 7 commits May 17, 2025 13:40
This is purely moving code into a new private method. No changes were made to the code.
This avoids the need to check the `weakIoContext` in `JsRpcTargetBase::call()` before calling the callback, which is nice because the callback already holds its own weak ref and does its own check.
This can be completely handled by the re-entry callback now.
Since we're no longer using the hack where `callImpl()` runs as part of a `ctx.addTask()`, it's now the case that the promise returned by `callImpl` will be canceled if the incoming RPC is canceled. The whole reason to hold onto `thisCap` is because if the RPC was canceled, then the RPC server could subsequently be dropped, but the addTask()ed callImpl() could still be running, so it needed to make sure `this` didn't disappear under it. But now the body of `callImpl()` will actually be canceled, so it doesn't matter if `this` goes away.

We still do need `ownCallContext`, but we can acquire it later than we did before, inside the body of `callImpl()` -- because if the call is canceled before that, we won't get to this point anyway.
`makeReentryCallback()`, as its name implies, is for *re-*entry, not original entry.

In particular, the callback itself holds a PendingEvent. This does not make sense at the top level, as it would mean there is always a PendingEvent, breaking the whole mechanism for RPC events. (I've added a test which was broken by this.)

It turns out, for the top-level call, we don't really need the complexity of `makeReentryCallback()` anyway. We can arrange to ensure that EntrypointJsRpcTarget cannot outlive the IoContext / IncomingRequest, then just use ctx.run() like normal.
Since the re-entry callback already holds one.
@kentonv kentonv requested a review from ObsidianMinor May 17, 2025 18:54
@kentonv kentonv requested review from a team as code owners May 17, 2025 18:54
kentonv added 2 commits May 17, 2025 14:00
The error messages aren't ideal, but I'm going to punt that for now. The behavior otherwise seems fine.
This forces a test failure in an internal test that triggers this behavior -- or at least it did, before the changes in this PR fixed the problem (since we no longer use addTask() for individual RPCs).

That said, the deeper bug isn't really fixed, which is that abort handling is extremely messy and does not actually cancel all outstanding tasks. That will require more work.
@kentonv kentonv merged commit 48e1e6f into main May 20, 2025
18 checks passed
@kentonv kentonv deleted the kenton/reentry-callback branch May 20, 2025 01:11
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.

3 participants