WebAudio / web-audio-api

The Web Audio API v1.0, developed by the W3C Audio WG
https://webaudio.github.io/web-audio-api/
Other
1.05k stars 167 forks source link

Invoking JS in the rendering loop needs a bunch of setup that doesn't seem to be in the spec #1967

Closed bzbarsky closed 3 years ago

bzbarsky commented 5 years ago

https://webaudio.github.io/web-audio-api/#rendering-loop step 4.4.2.1 invokes an ES Get. This requires a bunch of setup of the sort you see in https://html.spec.whatwg.org/multipage/webappapis.html#prepare-to-run-script and https://html.spec.whatwg.org/multipage/webappapis.html#prepare-to-run-a-callback and then cleanup afterwards. Similar for the function invocation at step 4.4.2.3. The lack of cleanup makes it unclear what happens with exceptions, if anything, for example. As a specific example, say the Get throws an exception. The way this algorithm is written right now, the result is somewhere between "undefined behavior" and "the entire rendering-a-graph algorithm invocation is aborted".

There's also additional confusion because some of the arguments to this function are defined as IDL values (e.g. outputs is defined as sequence<sequence<Float32Array>>) but there's nothing here that makes it clear that an IDL-to-ES conversion should happen.

I don't quite know what behavior this code is aiming is, especially in terms of microtask behavior; step 4.4.2.4 is not really clear to me in terms of what it's saying (e.g. is that a normative requirement, or an informative note? Normally microtask queuing normative bits are handled by the various existing ES infrastructure...). But it seems like what's really going on here is that processor is being treated as a Web IDL callback interface with a single method named process, and it might be worth actually defining it that way. Except the [[callable process]] bit has no corresponding equivalent in https://heycam.github.io/webidl/#call-a-user-objects-operation. Maybe we can refactor that algorithm in some way to make that behavior configurable?

Note that I am currently trying to review code that attempts to implement this stuff in Gecko, but I'm somewhat blocked by the above issues....

@karlt @domenic

padenot commented 5 years ago

[...] The way this algorithm is written right now, the result is somewhere between "undefined behavior" and "the entire rendering-a-graph algorithm invocation is aborted".

This should result in having onprocessorerror called on the control thread for the right AudioWorkletNode. Silence for this AudioWorkletProcessor should be output, and this AudioWorkletProcessor should be made inactive.

There's also additional confusion because some of the arguments to this function are defined as IDL values (e.g. outputs is defined as sequence<sequence>) but there's nothing here that makes it clear that an IDL-to-ES conversion should happen.

This we're having a look at in https://github.com/WebAudio/web-audio-api/issues/1933#issuecomment-505551737. I think what we really want is FrozenArray<FrozenArray<Float32Array>>. This will solve a number of things (such as having authors mutating the array while in the process method, and allowing to reuse the objects although I'm not quite sure about that). I used the notation Float32Array[][], but this is not valid WebIDL.

I don't quite know what behavior this code is aiming is, especially in terms of microtask behavior; step 4.4.2.4 is not really clear to me in terms of what it's saying (e.g. is that a normative requirement, or an informative note? Normally microtask queuing normative bits are handled by the various existing ES infrastructure...). But it seems like what's really going on here is that processor is being treated as a Web IDL callback interface with a single method named process, and it might be worth actually defining it that way. Except the [[callable process]] bit has no corresponding equivalent in https://heycam.github.io/webidl/#call-a-user-objects-operation. Maybe we can refactor that algorithm in some way to make that behavior configurable?

The behaviour this should have is to do a microtask checkpoint after all the worklet process methods have been executed.

Conceptually, each rendering quantum iteration (the big algorithm in 2.4) is an event loop task, and microtask checkpoint happens in between those iterations (step 4 of the algorithm). Multiple process methods can be called during in each rendering quantum iteration (exactly one per instance of AudioWorkletProcessor). Any microtask spawed as a result of calling any process method will be executed after all the process method calls for this iteration, and before any process method calls for the next iteration.

This was discussed at length in https://github.com/WebAudio/web-audio-api/issues/1511, and the behaviour was decided there, but wasn't expressed adequately in the text.

@hoch, did I capture the intent correctly?

hoch commented 5 years ago

Yes. That was our intention. Any suggestion to make it clear is welcome.

Do we want to have process() as a proper IDL interface? About 2 years ago, we're told not to do that: https://github.com/WebAudio/web-audio-api/issues/950

bzbarsky commented 5 years ago

The behaviour this should have is to do a microtask checkpoint after all the worklet process methods have been executed.

OK, I suggest talking to @domenic about the right way to make that happen. That said, this is a pretty odd behavior imo; why do you want it?

and microtask checkpoint happens in between those iterations

Microtask checkpoints can, and do, happen multiple times per event loop task, in general. For example, if a browser fires an event (itself, not from script), there will be a microtask checkpoint after every event listener execution.

If you do want this behavior, the simplest way to accomplish it is to push something onto the JS execution context stack, then do all the process calls you want, then pop the stack and do a microtask checkpoint. Basically, do https://html.spec.whatwg.org/multipage/webappapis.html#prepare-to-run-script and https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script around your whole thing. Then you can use web idl callbacks or whatever inside there, and everything will work fine.

bzbarsky commented 5 years ago

Oh, and to be clear, if you do it that way there is no need to manually define behavior around microtasks; it will just follow from how https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script works.

bzbarsky commented 5 years ago

Oh, and I don't know why #950 suggested not using IDL. I mean, you can basically copy/paste https://heycam.github.io/webidl/#call-a-user-objects-operation since that's really what you are trying to do here, but then you should do that, with whatever modifications are relevant to this situation...

karlt commented 5 years ago

I assume the main motivation for https://github.com/WebAudio/web-audio-api/issues/950 was removal of client methods from AudioWorkletProcessor.

The "do not belong in the IDL" statement seems to be based on the assumption that IDL was not useful for describing invocation of client methods. https://github.com/WebAudio/web-audio-api/pull/869#issuecomment-238444178 I suspect it would be possible to use https://heycam.github.io/webidl/#call-a-user-objects-operation as is and make all [[callable process]] logic dependent on whether or not that returns an abrupt completion.

bzbarsky commented 5 years ago

Oh, I see, the JS-implemented methods used to be on AudioWorkletProcessor itself? Yeah, that wouldn't make sense.

and make all [[callable process]] logic dependent on whether or not that returns an abrupt completion.

So the difference would be that right now in the spec [[callable process] only gets set to false if getting .process off the object succeeds or returns a non-callable. If the Get throws or if the returned thing is a callable but calling it throws, then [[callable process]] does not get set to false.

Setting [[callable process]] to false if the Get throws makes a lot of sense to me.

I'm not sure what I think about setting it to false if the call to .process throws. It's certainly simple to spec and implement, but it means that any exception in an AudioWorkletProcessor's process method will forever silence the corresponding AudioWorkletNode, right?

karlt commented 5 years ago

I'm not sure what I think about setting it to false if the call to .process throws. It's certainly simple to spec and implement, but it means that any exception in an AudioWorkletProcessor's process method will forever silence the corresponding AudioWorkletNode, right?

I believe that is the intention, despite the status of the algorithm right now. https://webaudio.github.io/web-audio-api/#dom-audioworkletnode-onprocessorerror says

When an exception is thrown from the processor’s constructor, process method, or any user-defined class method, the processor will queue a task on the control thread to fire onprocessorerror event to the node. Note that once an exception is thrown, the processor will output silence throughout its lifetime.

bzbarsky commented 5 years ago

I believe that is the intention, despite the status of the algorithm right now.

Oh, in that case, great. :)

https://webaudio.github.io/web-audio-api/#dom-audioworkletnode-onprocessorerror says

Mmm, comefrom specs. ;) We should have the queuing of that task happen at those callsites, probably in a sub-algorithm called as needed that sets [[callable process]] to false and queues the error event task.

padenot commented 5 years ago

F2F comment: there are two parts of this issue, summarizing here:

padenot commented 4 years ago

@hoch, would you mind taking this one? I'm quite busy with a few others already.

svgeesus commented 3 years ago

Where are we on this long-standing issue? Do we have consensus on what to add? Is it a V1 blocker?

padenot commented 3 years ago

I think it's important. @hoch, can you find someone that can take a few minutes to advise here?

hoch commented 3 years ago

I'll tackle it next week. Do you have someone that can advise on this issue?

padenot commented 3 years ago

Not anymore or readily available, unfortunately, sorry.

hoch commented 3 years ago

Not a problem and I'll ask around. The help from Boris was really valuable though.