Closed bzbarsky closed 3 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?
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
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.
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.
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...
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.
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?
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.
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.
F2F comment: there are two parts of this issue, summarizing here:
onprocessorerror
textGet
operation, as explained in the initial comment.@hoch, would you mind taking this one? I'm quite busy with a few others already.
Where are we on this long-standing issue? Do we have consensus on what to add? Is it a V1 blocker?
I think it's important. @hoch, can you find someone that can take a few minutes to advise here?
I'll tackle it next week. Do you have someone that can advise on this issue?
Not anymore or readily available, unfortunately, sorry.
Not a problem and I'll ask around. The help from Boris was really valuable though.
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 theGet
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 namedprocess
, 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