From 06742cad4fa24c15e4a8c129d274bf27203ab4b0 Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Fri, 17 Jul 2026 15:05:28 -0700 Subject: [PATCH] ci probe: tier-2 'pend-container' patch in isolation (do not merge) Attribution probe for the #2905 CodSpeed regressions (update1to1 -6.8%, merge -6.8%): each tier-2 patch measured alone against next. --- packages/solid-signals/src/affects.ts | 10 ++--- packages/solid-signals/src/core/async.ts | 44 ++++++-------------- packages/solid-signals/src/core/scheduler.ts | 2 +- packages/solid-signals/src/core/types.ts | 1 - packages/solid-signals/src/core/verdict.ts | 1 - 5 files changed, 16 insertions(+), 42 deletions(-) diff --git a/packages/solid-signals/src/affects.ts b/packages/solid-signals/src/affects.ts index 5c8bc3107..7c7c187ac 100644 --- a/packages/solid-signals/src/affects.ts +++ b/packages/solid-signals/src/affects.ts @@ -70,7 +70,7 @@ function propagateAffectsMark(node: MarkedNode): void { const sentinel = getAffectsSentinel(node); const error = new NotReadyError(sentinel); forEachDependent(node as Computed, sub => { - if (sub._pendingSource !== sentinel && !sub._pendingSources?.has(sentinel)) { + if (!sub._pendingSources?.has(sentinel)) { notifyStatus(sub, STATUS_PENDING, error); } }); @@ -171,7 +171,7 @@ function onlyMarkPending(el: Computed): boolean { for (const s of sources) if (!s._affectsFor) return false; return true; } - return !!el._pendingSource?._affectsFor; + return false; } /** @@ -186,11 +186,7 @@ function onlyMarkPending(el: Computed): boolean { * `GlobalQueue._collectMarkSources`, gated on `activeAffectsMarks`. */ function collectMarkSources(el: Computed, into: MarkedNode[]): void { - const single = el._pendingSource; - if (single) { - const marked = single._affectsFor; - if (marked && marked._affectsCount) into.push(marked); - } else if (el._pendingSources) { + if (el._pendingSources) { for (const s of el._pendingSources) { const marked = s._affectsFor; if (marked && marked._affectsCount) into.push(marked); diff --git a/packages/solid-signals/src/core/async.ts b/packages/solid-signals/src/core/async.ts index 2352c47a7..bd7ab05d3 100644 --- a/packages/solid-signals/src/core/async.ts +++ b/packages/solid-signals/src/core/async.ts @@ -32,40 +32,24 @@ import { } from "./scheduler.js"; import type { Computed, FirewallSignal, Link } from "./types.js"; +// The lazily-created Set is the ONE container for pending sources. Its +// predecessor — a singular slot promoted to a Set on the second source — +// created dual state whose migration invariant was easy to break: a third +// overlapping source landed beside the Set and removePendingSource refused +// to clear it, stranding the Set members' pending forever (#2893). export function addPendingSource(el: Computed, source: Computed): boolean { - if (el._pendingSource === source || el._pendingSources?.has(source)) return false; - // Once the Set exists it is THE container — the singular slot stays empty - // from migration until removePendingSource collapses back to one entry. - // Landing a third source in the singular slot instead created dual state - // that removePendingSource refused to clear, stranding the Set members' - // pending forever (#2893). - if (el._pendingSources) el._pendingSources.add(source); - else if (!el._pendingSource) el._pendingSource = source; - else { - el._pendingSources = new Set([el._pendingSource, source]); - el._pendingSource = undefined; - } + if (el._pendingSources?.has(source)) return false; + (el._pendingSources ??= new Set()).add(source); return true; } function removePendingSource(el: Computed, source: Computed): boolean { - if (el._pendingSource) { - if (el._pendingSource !== source) return false; - el._pendingSource = undefined; - return true; - } if (!el._pendingSources?.delete(source)) return false; - if (el._pendingSources.size === 1) { - el._pendingSource = el._pendingSources.values().next().value; - el._pendingSources = undefined; - } else if (el._pendingSources.size === 0) { - el._pendingSources = undefined; - } + if (el._pendingSources.size === 0) el._pendingSources = undefined; return true; } function clearPendingSources(el: Computed): void { - el._pendingSource = undefined; el._pendingSources?.clear(); el._pendingSources = undefined; } @@ -120,7 +104,7 @@ export function settlePendingSource( if (visited.has(node) || !removePendingSource(node, source)) return; visited.add(node); node._time = clock; - const remaining = node._pendingSource ?? node._pendingSources?.values().next().value; + const remaining = node._pendingSources?.values().next().value; if (remaining) { setPendingError(node, remaining); updateCompanions !== null && updateCompanions(node); @@ -393,7 +377,7 @@ export function handleAsync( } export function clearStatus(el: Computed, clearUninitialized: boolean = false): void { - if (el._pendingSource || el._pendingSources) clearPendingSources(el); + if (el._pendingSources) clearPendingSources(el); if (el._blocked) el._blocked = false; // The pending window is over; its quiet classification dies with it. // (Unconditional: _reask is baked into the node literals, so this is a @@ -482,12 +466,8 @@ export function notifyStatus( forEachDependent(el, (sub, link) => { sub._time = clock; if ( - (status === STATUS_PENDING && - pendingSource && - sub._pendingSource !== pendingSource && - !sub._pendingSources?.has(pendingSource)) || - (status !== STATUS_PENDING && - (sub._error !== error || sub._pendingSource || sub._pendingSources)) + (status === STATUS_PENDING && pendingSource && !sub._pendingSources?.has(pendingSource)) || + (status !== STATUS_PENDING && (sub._error !== error || sub._pendingSources)) ) { // A pending-observer link is the subscription an `isPending` read created. // It exists so the observer re-runs when the source settles, but it must diff --git a/packages/solid-signals/src/core/scheduler.ts b/packages/solid-signals/src/core/scheduler.ts index 62782245b..6c4bf73d6 100644 --- a/packages/solid-signals/src/core/scheduler.ts +++ b/packages/solid-signals/src/core/scheduler.ts @@ -819,7 +819,7 @@ function runQueue(queue: QueueCallback[], type: number): void { function reporterBlocksSource(reporter: Computed, source: Computed): boolean { if (reporter._flags & (REACTIVE_ZOMBIE | REACTIVE_DISPOSED)) return false; - if (reporter._pendingSource === source || reporter._pendingSources?.has(source)) return true; + if (reporter._pendingSources?.has(source)) return true; for (let dep = reporter._deps; dep; dep = dep._nextDep) { let current = dep._dep as Signal | Computed | undefined; while (current) { diff --git a/packages/solid-signals/src/core/types.ts b/packages/solid-signals/src/core/types.ts index 79a8aeb42..7bf43c1d4 100644 --- a/packages/solid-signals/src/core/types.ts +++ b/packages/solid-signals/src/core/types.ts @@ -103,7 +103,6 @@ export interface Computed extends RawSignal, Owner { _depGen: number; _flags: number; _blocked?: boolean; - _pendingSource?: Computed; _pendingSources?: Set>; _error?: unknown; _statusFlags: number; diff --git a/packages/solid-signals/src/core/verdict.ts b/packages/solid-signals/src/core/verdict.ts index 4fdedda7f..9f3d2237a 100644 --- a/packages/solid-signals/src/core/verdict.ts +++ b/packages/solid-signals/src/core/verdict.ts @@ -91,7 +91,6 @@ function quietPending(el: Computed): boolean { for (const source of el._pendingSources) if (!source._reask) return false; return true; } - if (el._pendingSource) return el._pendingSource._reask; return el._reask; }