feat(siteruntime): WaitForAsync/WaitResult + quality-gated WaitAsync (spec §3, §4.2)
This commit is contained in:
@@ -422,31 +422,75 @@ public class ScriptRuntimeContext
|
||||
/// </param>
|
||||
/// <param name="predicate">Site-local predicate; null when the encoded target is used.</param>
|
||||
/// <param name="timeout">How long to wait before returning false.</param>
|
||||
/// <param name="requireGoodQuality">
|
||||
/// Quality-gated ("Good"-only) mode (spec §4.2): when <see langword="true"/>, a
|
||||
/// value reaching the target / satisfying the predicate at Bad/Uncertain quality
|
||||
/// is NOT a match — the wait holds until the value satisfies the test at Good
|
||||
/// quality (or times out). Defaults to <see langword="false"/> (quality-agnostic).
|
||||
/// </param>
|
||||
/// <returns><c>true</c> on match within the timeout; <c>false</c> on timeout.</returns>
|
||||
public async Task<bool> WaitAttribute(
|
||||
string name, string? targetValueEncoded, Func<object?, bool>? predicate, TimeSpan timeout)
|
||||
string name, string? targetValueEncoded, Func<object?, bool>? predicate, TimeSpan timeout,
|
||||
bool requireGoodQuality = false)
|
||||
=> (await WaitInternal(name, targetValueEncoded, predicate, timeout, requireGoodQuality)).Matched;
|
||||
|
||||
/// <summary>
|
||||
/// WaitForAttribute (spec §3): the richer overload backing <c>Attributes.WaitForAsync</c>
|
||||
/// — identical semantics to <see cref="WaitAttribute"/> but surfaces the full
|
||||
/// <see cref="WaitResult"/> (matched flag + matched value + quality + timed-out
|
||||
/// flag) instead of a bare bool. Never throws on timeout (see <see cref="WaitInternal"/>).
|
||||
/// </summary>
|
||||
/// <param name="name">The scope-resolved attribute name to wait on.</param>
|
||||
/// <param name="targetValueEncoded">The codec-encoded target value; null (with null predicate) means "any change".</param>
|
||||
/// <param name="predicate">Site-local predicate; null when the encoded target is used.</param>
|
||||
/// <param name="timeout">How long to wait before returning a timed-out result.</param>
|
||||
/// <param name="requireGoodQuality">Quality-gated ("Good"-only) mode (spec §4.2); defaults to <see langword="false"/>.</param>
|
||||
/// <returns>The full <see cref="WaitResult"/> — on timeout: <c>Matched:false, TimedOut:true</c>.</returns>
|
||||
public async Task<WaitResult> WaitAttributeFull(
|
||||
string name, string? targetValueEncoded, Func<object?, bool>? predicate, TimeSpan timeout,
|
||||
bool requireGoodQuality = false)
|
||||
{
|
||||
var r = await WaitInternal(name, targetValueEncoded, predicate, timeout, requireGoodQuality);
|
||||
return new WaitResult(r.Matched, r.Value, r.Quality, r.TimedOut);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shared core for <see cref="WaitAttribute"/> / <see cref="WaitAttributeFull"/>:
|
||||
/// builds the <see cref="WaitForAttributeRequest"/> (incl. the §4.2
|
||||
/// <paramref name="requireGoodQuality"/> flag), Asks the InstanceActor bounded by
|
||||
/// the script's execution-timeout token, and returns the full response. An
|
||||
/// <see cref="AskTimeoutException"/> (the pathological case where the actor's own
|
||||
/// authoritative timeout reply never arrives — actor stopped/restarted) is caught
|
||||
/// and surfaced as a synthetic non-matched/timed-out response, preserving the
|
||||
/// "never throw on timeout" contract. An <see cref="OperationCanceledException"/> /
|
||||
/// <see cref="TaskCanceledException"/> from the script-deadline token is NOT caught
|
||||
/// — it propagates to abort the script (§4.3).
|
||||
/// </summary>
|
||||
private async Task<WaitForAttributeResponse> WaitInternal(
|
||||
string name, string? targetValueEncoded, Func<object?, bool>? predicate, TimeSpan timeout,
|
||||
bool requireGoodQuality)
|
||||
{
|
||||
var cid = Guid.NewGuid().ToString();
|
||||
var req = new WaitForAttributeRequest(
|
||||
cid, _instanceName, name, targetValueEncoded, predicate, timeout, DateTimeOffset.UtcNow);
|
||||
cid, _instanceName, name, targetValueEncoded, predicate, timeout, DateTimeOffset.UtcNow,
|
||||
requireGoodQuality);
|
||||
|
||||
try
|
||||
{
|
||||
var resp = await _instanceActor.Ask<WaitForAttributeResponse>(
|
||||
return await _instanceActor.Ask<WaitForAttributeResponse>(
|
||||
req, timeout + _askTimeout, _scriptTimeoutToken);
|
||||
|
||||
return resp.Matched;
|
||||
}
|
||||
catch (AskTimeoutException)
|
||||
{
|
||||
// Pathological: the InstanceActor's own scheduled timeout reply never
|
||||
// arrived (e.g. the actor stopped/restarted under us). The helper's
|
||||
// contract is "false on timeout, never throw" — so swallow and return
|
||||
// false rather than leaking the Ask exception to the script.
|
||||
// contract is "false on timeout, never throw" — so synthesize a
|
||||
// non-matched/timed-out response rather than leaking the Ask exception.
|
||||
// OperationCanceledException / TaskCanceledException from the
|
||||
// script-deadline token are deliberately NOT caught here: they must
|
||||
// propagate to abort the script (§4.3).
|
||||
return false;
|
||||
return new WaitForAttributeResponse(
|
||||
cid, Matched: false, null, null, TimedOut: true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user