Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

discussion of sync vs async API #10

Closed
fergald opened this issue Jul 6, 2022 · 4 comments
Closed

discussion of sync vs async API #10

fergald opened this issue Jul 6, 2022 · 4 comments
Labels
api Issue with API specs discussion

Comments

@fergald
Copy link
Collaborator

fergald commented Jul 6, 2022

Forking this from #9 (comment)

@yutakahirano

dealing with rejected calls to setData is tricky, especially if multiple calls are in flight, full example in the explainer.

The call to setData does not block and so there may be multiple outstanding calls to setData now their catch code has to be coordinated so that only one replacement beacon is created and the latest data is set on the beacon (and setting that latest data will be async and subject to the same problems).

I still don't understand this. Are you thinking about different parts of the application access the same beacon simultaneously? If so, that sounds like an application problem rather than an API problem. PendingBeacon is a relatively precious resource, and if there are much more (say, 1M) requests than beacons, the application needs to deal with the problem by queueing/filtering/merging requests for example.

This problem exists even with very simple use-cases. Imagine we want to count or summarise events in some way (e.g. intersection observer events that help us compute how long an ad was displayed or just keep logging LCP values). With every event, we update the summary and want to call setData on the beacon. We have no control over the timing of these events. With an async API, the next event could arrive before the first one's setData promise has resolved or rejected, forcing the application to write logic to handle this case. As you say, the application needs to deal with the problem by "queueing/filtering/merging requests" and that is con, especially as many devs will just write the naive version of the code and not even notice that a race exists.

Purely from an API user's perspective, with the sync API these problems just don't exist, every event just computes the latest data and calls setData if the beacon is pending or replaces the beacon with a new one if it is no longer pending. It also doesn't matter if the different parts of the application access the same beacon simultaneously (we're single thread, so I take "simultaneously" to mean in the same JS task). I see no reason to tell developers that they shouldn't do that. That problem comes from the async API, it is not a fundamentally problematic thing to do.

It seems to me that almost many uses of the API would need wrap the beacon in another layer to essentially present the sync API to the application.

We need a justification to push the burden of dealing with this onto JS and I don't think it's justified by implementation concerns. Even if the implementation was difficult, pushing the complexity to JS would not be good, however I believe the implementation is not difficult.

implementation concerns

I'll use chrome terminology, here renderer means the process running JS and browser means the process responsible for making the network request.

The concern that I am aware of is about the need to transfer the information from the renderer to the browser and getting that correct in all cases, including the crash case. There is no way to avoid transferring information from the renderer to the browser and I agree that minimising and making this atomic is good, e.g. #8 but the payload and also signals like sendNow and cancel will always come from the renderer. With the async API we also need to transfer information from browser to renderer to know whether it's possible to set a new payload or cancel. The async API forces us to have 2-way communication of state.

The sync API allows us to have 1-way communication of state. While the renderer is alive, the renderer is authoritative for the state of the beacon. This makes all API calls sync and return immediately, without blocking on events from other processes/network/etc. The sending step, at first, appears to be browser->renderer information flow but actually if you model it the right way, it's not. The browser requests clearance to send the beacon, the renderer updates the beacon's pending state to false and communicates the change in state to the browser. The browser cannot send until the pending state is false or the document has been discarded. There is no state transferred from browser to renderer in this model, all updates to the beacon state (payload, settings, pending, cancellation) are passed from the renderer to the browser.

If, in the future, we added an API to notify the renderer when sending completes or fails, that would introduce browser->renderer communication but at that point we are past the time where we have to defend against races and complexity in the JS.

@mingyc mingyc added api Issue with API specs discussion labels Jul 6, 2022
@yutakahirano
Copy link

Sorry for the late response.

My concern, is API extensibility. Your proposal may work well for a specific use case you described, but you may want to add a new feature to the API and the current sync API may prevent you from doing that (completely, or cleanly).

You may think that the above is abstract and both sync/async APIs may suffer the same kind of problem (with different situations), but I believe sync APIs in general are less flexible and you'll be more likely in trouble when you add more features.

Hence my first question is:

Do you foresee more usecases in the future in addition to those described in https://github.com/WICG/unload-beacon#readme?

If the answer is yes, I'd prefer the async model, or removing isPending in the first version.

If the answer is no, I'd like to ask if isPending is really needed. For example, we can give an constructor parameter, repeating, to specify if the beacon stays active once a request is done.

const beacon = new PendingBeacon(url, {repeating: true});
beacon.setData(data);

...
// If the beacon has sent the previous data or is sending the previous data, then
// set `data` for the next request body. Otherwise, replace the current beacon data.
beacon.setData(data);

I believe this is what the example wants to do.

With this, we don't need to choose whether setData needs to be synchronous now.

@yoavweiss
Copy link
Collaborator

Hey @yutakahirano! :)

In #9 we heard from both @philipwalton and @nicjansma that their main concern with an async API that's lacking a isPending signal is that of over-sending data and lack of control over what data is being sent. If I understand your proposal correctly, it doesn't really address that concern.

Regarding async APIs being more flexible, that's true (e.g. they enable cross-process communication, etc), but that flexibility comes with a cost, in terms of developer ergonomics and control.
It seems to me that it'd be a shame to ship an API that doesn't address the use cases we do know about, even if it's flexible to address potential future cases.

@yutakahirano
Copy link

Thank you. If you're certain that the synchronous isPending is important, I'm OK with closing this.

@fergald
Copy link
Collaborator Author

fergald commented Jul 20, 2022

@yutakahirano Thanks. I'm going to leave some a closing argument here for why I think we are not creating a future problem.

The real choice we're making here is in the implementation about what process is authoritative for the state of the beacon. If the renderer is authoritative (while alive) then the API never involves IPCs under the hood and anything that is just updating state (up to the point of sending) can be synchronous now and in the future.

So I think the question of whether we will regret this API is really a question of whether we will ever regret making the renderer authoritative. It's hard to imagine that, since all state changes either come from

  • the API (the renderer)
  • a timer (can also be the renderer)
  • death of the renderer

I don't know what other source of state changes there could be but if there was one, it could also just send IPCs to the renderer to request state changes. That other process might have to deal with the asynchronicity but as Yoav points out, it still seems better to put this theoretical future burden on the browser-implementer instead of putting it on the API callers right now.

@fergald fergald closed this as completed Jul 20, 2022
aarongable pushed a commit to chromium/chromium that referenced this issue Aug 16, 2022
This CL does not change any PendingBeacon's behavior yet. It is to prepare for subsequent renderer-side timeout implementation.

- Replaced `PendingBeaconHostRemote` with `PendingBeaconDispatcher`
  and exported the latter as an independent class.
- Removed `timeout` from the mojom message to browser process. All
  timeout behavior will be implemented on the renderer-side, according
  to [1].
- Browser-side will still be responsible for making network requests. And trigger sending all beacons on page destroyed.

Design Doc: https://docs.google.com/document/d/1QIFUu6Ne8x0W62RKJSoTtZjSd_bIM2yXZSELxdeuTFo/edit?pli=1#heading=h.nxtqzwddjsbk

[1]: WICG/pending-beacon#10 (comment)

Bug: 1293679
Change-Id: I04534a95d8540dd4344cc2b916a028a96ed3fbf4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3809613
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Reviewed-by: Rakina Zata Amni <rakina@chromium.org>
Commit-Queue: Ming-Ying Chung <mych@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1035441}
mjfroman pushed a commit to mjfroman/moz-libwebrtc-third-party that referenced this issue Oct 14, 2022
This CL does not change any PendingBeacon's behavior yet. It is to prepare for subsequent renderer-side timeout implementation.

- Replaced `PendingBeaconHostRemote` with `PendingBeaconDispatcher`
  and exported the latter as an independent class.
- Removed `timeout` from the mojom message to browser process. All
  timeout behavior will be implemented on the renderer-side, according
  to [1].
- Browser-side will still be responsible for making network requests. And trigger sending all beacons on page destroyed.

Design Doc: https://docs.google.com/document/d/1QIFUu6Ne8x0W62RKJSoTtZjSd_bIM2yXZSELxdeuTFo/edit?pli=1#heading=h.nxtqzwddjsbk

[1]: WICG/pending-beacon#10 (comment)

Bug: 1293679
Change-Id: I04534a95d8540dd4344cc2b916a028a96ed3fbf4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3809613
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Reviewed-by: Rakina Zata Amni <rakina@chromium.org>
Commit-Queue: Ming-Ying Chung <mych@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1035441}
NOKEYCHECK=True
GitOrigin-RevId: 845c545f2edbec473cb270eaf97a82440338669e
This was referenced Mar 20, 2023
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api Issue with API specs discussion
Projects
None yet
Development

No branches or pull requests

4 participants