fix(siteruntime): harden WaitAsync — no spurious match on quality republish, guard throwing predicate, Ask-timeout returns false
This commit is contained in:
@@ -81,9 +81,24 @@ public class AttributeAccessor
|
||||
/// <c>false</c> on timeout (no throw). Honors the script's execution-timeout token.
|
||||
/// Scope/composition path resolution (<see cref="Resolve"/>) is applied just like
|
||||
/// <see cref="GetAsync"/> / <see cref="SetAsync"/>.
|
||||
///
|
||||
/// <para>
|
||||
/// <b>Quality-agnostic by default (spec §4.2):</b> matching tests the VALUE, not
|
||||
/// the quality — a value arriving at Bad quality still satisfies the wait. A
|
||||
/// quality-gated ("Good"-only) mode is a planned enhancement, deferred per spec §4.2.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Passing a <b>null</b> <paramref name="targetValue"/> means "match on any change":
|
||||
/// the wait then matches the next value the attribute receives — and matches
|
||||
/// IMMEDIATELY (fast-path) if the attribute already holds any value at registration.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="key">The attribute key (scope-resolved before the wait is registered).</param>
|
||||
/// <param name="targetValue">The value to wait for (codec-encoded for comparison).</param>
|
||||
/// <param name="targetValue">
|
||||
/// The value to wait for (codec-encoded for comparison); <c>null</c> means
|
||||
/// "match on any change" (matches immediately if the attribute already has a value).
|
||||
/// </param>
|
||||
/// <param name="timeout">How long to wait before returning false.</param>
|
||||
/// <returns><c>true</c> on match within the timeout; <c>false</c> on timeout.</returns>
|
||||
public Task<bool> WaitAsync(string key, object? targetValue, TimeSpan timeout)
|
||||
@@ -95,6 +110,13 @@ public class AttributeAccessor
|
||||
/// value, bounded by <paramref name="timeout"/>. Site-local only (the predicate
|
||||
/// is an in-process delegate). Returns <c>true</c> if matched within the timeout,
|
||||
/// <c>false</c> on timeout (no throw). Scope/composition path resolution applies.
|
||||
///
|
||||
/// <para>
|
||||
/// <b>Quality-agnostic by default (spec §4.2):</b> the predicate is tested against
|
||||
/// the VALUE, regardless of quality — a value arriving at Bad quality still
|
||||
/// satisfies the wait if the predicate passes. A quality-gated ("Good"-only) mode
|
||||
/// is a planned enhancement, deferred per spec §4.2.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="key">The attribute key (scope-resolved before the wait is registered).</param>
|
||||
/// <param name="predicate">The site-local predicate tested against the current value.</param>
|
||||
|
||||
@@ -399,6 +399,21 @@ public class ScriptRuntimeContext
|
||||
/// so the InstanceActor's own scheduled timeout reply is the authoritative path
|
||||
/// for the false/timed-out outcome, not the Ask deadline.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// <b>Quality-agnostic by default (spec §4.2):</b> a value arriving at Bad
|
||||
/// quality still satisfies the wait — the match tests the value, not the quality.
|
||||
/// A quality-gated ("Good"-only) mode is a planned enhancement, deferred per spec §4.2.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// <b>Never throws on timeout.</b> An <see cref="Akka.Actor.AskTimeoutException"/>
|
||||
/// (the pathological case where the InstanceActor's authoritative timeout reply
|
||||
/// never arrives — actor stopped/restarted) is caught and surfaced as <c>false</c>,
|
||||
/// matching the timeout contract. An <see cref="OperationCanceledException"/> /
|
||||
/// <see cref="TaskCanceledException"/> from the script-deadline token is NOT caught
|
||||
/// — it propagates to abort the script (intended §4.3 behaviour).
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="name">The scope-resolved attribute name to wait on.</param>
|
||||
/// <param name="targetValueEncoded">
|
||||
@@ -415,10 +430,24 @@ public class ScriptRuntimeContext
|
||||
var req = new WaitForAttributeRequest(
|
||||
cid, _instanceName, name, targetValueEncoded, predicate, timeout, DateTimeOffset.UtcNow);
|
||||
|
||||
var resp = await _instanceActor.Ask<WaitForAttributeResponse>(
|
||||
req, timeout + _askTimeout, _scriptTimeoutToken);
|
||||
try
|
||||
{
|
||||
var resp = await _instanceActor.Ask<WaitForAttributeResponse>(
|
||||
req, timeout + _askTimeout, _scriptTimeoutToken);
|
||||
|
||||
return resp.Matched;
|
||||
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.
|
||||
// OperationCanceledException / TaskCanceledException from the
|
||||
// script-deadline token are deliberately NOT caught here: they must
|
||||
// propagate to abort the script (§4.3).
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user