Semantic of the Sync and Async Ports
With this issue we want to review the semantic of Async ports and clearly define the difference between Sync and Async cases.
Number of samples constraints
There are two types of constraints used to calculate the input and output number of samples to be processed:
- Port Constraints: Min/max number of samples set individually for each port.
- Resampling Constraints: Numerator, denominator, and stride set for each block.
-
Port constraints apply to both
SyncandAsyncports. -
Resampling constraints apply only to
Syncports.
Sync and Async Port Behaviors
Sync Ports:
- Same number of samples for all
Syncports. - Fixed
M:Nratio between input and output samples. TheprocessBulkis called withk*Minputs andk*Noutputs. - Both port constraints and resampling constraints are applied.
Async Ports:
- The
processBulkfunction is called independently of the available samples on otherSyncand/orAsyncports. - Only port constraints are applied; the input/output sample ratio is undefined.
- The user can change the sample ratio iteration-to-iteration using
input.consume()andoutput.publish().
Examples
-
4
Sync+ 1AsyncInputs -> 1SyncOutput:- All 4
Syncinputs have consistent input (same number of samples) andAsynchas some input samples (fulfilling port constraints) -> callprocessBulk. - All 4
Syncinputs have consistent input (fulfilling port + resampling constraints),Asynchas no input (or input does not fulfill port constraints) -> callprocessBulk(Asyncport has no input samples). - Samples do not fulfill
Syncinput constraints,Asynchas input (fulfilling port constraints) -> callprocessBulk(no samples for inputSyncports, no samples for outputSyncport, only samples for inputAsyncport). - No
Syncinput available and noAsyncdata -> do not callprocessBulk.
- All 4
-
Syncand/orAsyncInputs -> 4Sync+ 1AsyncOutputs:- If at least one
Asyncoutput port is present, always callprocessBulk(unlessAsyncoutput port constraints are not met). This is because the user can calloutput.publish()even if no input samples are present. Note: This can lead to performance issues due to busy polling.processBulkcan be invoked upto 4G/s. - Consider introducing a policy to prevent this and only call
processBulkforAsyncports if input samples are available. This can be a port policy.
- If at least one
consume() / publish() Only for Async Ports
- Users cannot override the
M:Nresampling ratio and stride forSyncports usingconsume()/publish(). This ensures the resampling ratio and the consistent number of input/output samples are maintained. - Can be enforced in
~ConsumablePortRangeand its counterpart or even statically disabled. - Disable
produce/consumeor usestatic_assertfor better error messages. - Users who need this functionality must use
Asyncports.
TODO
- Update the documentation for
Async/Syncstructs. - Check/update the Block implementation,
ConsumablePortRangelogic, andProducablePortRange.
The
processBulkfunction is called independently of the available samples on otherSyncand/orAsyncports.
Can you clarify when processBulk is called and when it isn't called? For example, for a block with Sync input and Async output, is processBulk called if there are zero items available in the input?
The user can change the sample ratio iteration-to-iteration using
input.consume()andoutput.publish()
Will this stop being true for Sync ports? The kind of use case that I have in mind for a Sync input and Sync output block that sometimes doesn't respect its M:N ratio is a block like the GR 3.10 Symbol Sync. Usually it decimates by a fixed ratio (the GR 3.10 block allows this ratio to be a float, but for the sake of the example let's suppose the ratio is a rational number). But the decimation is done in terms of a closed loop that depends on the values of the input, so sometimes the block "slips" one input sample forward or backwards by consuming one more or one less samples than the nominal ratio would indicate.
Reading the section on "consume() / publish() Only for Async Ports", it seems that this will be the case. In this case it might perhaps be necessary to have a way for blocks with Async ports to indicate a rough input_samples/ouput_samples relation.
Specially important in this case is to clarify what happens with busy polling a processBulk() function for a block with only Async ports. If consume() and publish() are removed for Sync ports, then every block that doesn't have a definite M:N input/output relation needs to be Async-only, but most of this blocks need some input to produce some output.
If at least one Async output port is present, processBulk is always called, even if no input samples are available (unless Async output port constraints are not met, namely min/max port constraints).
We consider introducing a policy to prevent busy polling and only call processBulk for Async ports if input samples are available.
Will this stop being true for Sync ports?
Yes, because users can easily ignoring N:M ratio using consume()/publish(). If the ratio is not fixed one should use Async ports.
Thanks for the clarifications. What I don't understand is, for a block with a single input port and a single output port, which are the differences between the cases:
- Async input, Sync output
- Sync input, Async output
- Async input, Async output
Looking at the list of differences between Sync and Async ports, I see that one of them is that if there is one Async port then processBulk() is called continuously (this isn't really a port-specific property, but a block-specific property), and that N:M Resampling is not taken into account (this isn't really a property of a single port, but a property that relates an input port and an output port). Maybe these all three cases behave the same? Maybe only the Async input, Async output makes sense? The Async annotation is applied at the level of ports, but to me it seems that its semantics affect properties that belong to a larger construct than just a single port.
In general, these three cases behave similarly, but there are some differences to note.
The main differences between these cases lie in how constraints are applied:
- Sync Ports: Both port constraints and resampling constraints are applied.
- Async Ports: Only port constraints are applied; the input/output sample ratio is undefined.
For the output port:
-
Output Sync:
processBulkis invoked only if input samples are available. -
Output Async:
processBulkis invoked even if no input samples are available, which can be controlled.
Additionally, for an Async port, a user can call consume() and publish().
For a block with a single Async input and a single Sync output, does processBulk() only get called if the input has some samples available? Wouldn't this kind of block ought to behave as a source block (so its processBulk() always gets called) which has an additional input that may or may not have samples at any point?
For the output Sync port, the processBulk function is only invoked when input samples are available. Otherwise, it's unclear how many samples to provide for the Sync output port, as Sync ports do not support a publish() call.
For a Source block with an additional Async input, it seems that both the input and output need to be Async.
This issue requires primarily updating the documentation in the core/Readme.md file regarding the Async tag and meaning, i.e. any block with an Async annotated port is executed when:
- there is at least one output port marked
Asyncand/or - there is at least one input port marked
Asyncthat has at least one sample and/or - the regular sync-in-to-out logic applies.