Skip to content

Scan: observe the faults of abandoned page tasks after a sync timeout or dispose (#3260) - #3267

Merged
mgravell merged 1 commit into
mainfrom
marc/issue-3260-scan-unobserved
Oct 9, 2026
Merged

mgravell merged 1 commit into
mainfrom
marc/issue-3260-scan-unobserved

Conversation

@mgravell

@mgravell mgravell commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator

Fixes #3260.

A synchronous scan (HashScan, SetScan, SortedSetScan, Keys) that hits SyncTimeout throws RedisTimeoutException and walks away from the task wrapping the in-flight page. When that page later faults (backlog timeout, socket closed), the fault has no observer and surfaces as TaskScheduler.UnobservedTaskException, exactly as reported.

Changes in CursorEnumerable:

  • The sync wait observes the orphaned task before throwing the timeout.
  • Disposing or resetting an enumerator with a prefetched page in flight now observes that page too (the common break out of a foreach), via a single AbandonPending.
  • A disposed enumerator no longer issues one more SCAN when the late page finally arrives.
  • Faulted pages surface as their own exception rather than AggregateException, on both the sync and async paths; this matches the rest of the sync surface. Previously the type depended on timing: a fault during the await rethrew the cause, a fault that had already happened (typical with prefetching) came wrapped. Behaviour change, called out deliberately.
  • A sync timeout on a scan with no pattern (a single HGETALL etc.) threw NullReferenceException because the enumerator has no Message to report; ExceptionFactory.Timeout now accepts null.

Tests: CursorEnumerableFaultTests, six server-free tests driven by a scripted RedisDatabase. Five of six fail on main for the reasons above; the dispose test was verified to fail with the observation line removed.

Not addressed: calling MoveNext() again after a sync timeout while the earlier page is still in flight double-processes the reply. Pre-existing, separate issue.

No 2.x backport is planned; the fix ships in the current release, which is a non-breaking upgrade from 2.13.x.

Checklist

  • I fully and freely contribute this code in accordance with the project license (and am legally able to do so)
  • I take responsibility for this contribution's quality and correctness, including any portions produced with AI assistance (see CONTRIBUTING.md).

@mgravell mgravell left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@mgravell
mgravell merged commit 61867e6 into main Oct 9, 2026
6 checks passed
@mgravell
mgravell deleted the marc/issue-3260-scan-unobserved branch October 9, 2026 11:37
mgravell added a commit that referenced this pull request Oct 9, 2026
Absorbs the drift up to the 3.4.0 cut. Most of main's changes landed in v3's
PhysicalBridge/PhysicalConnection/CursorEnumerable, which v4 has deleted, so
several are re-expressed in v4 terms rather than merged textually:

- #3266 duplicate LoggerMessage event ids: taken as-is. 124/125 (maintenance),
  126 (TLS established) and 127 (ConnectionFailed) shipped in 3.4.0, so the
  v4-only events that had claimed those ids move to 128-131; the guard test
  (LoggerEventIdTests) covers both.
- #3268 create-implementation-plan skill: taken as-is.
- #3260/#3267 scan faults after a sync timeout or dispose: the fix targets
  CursorEnumerable's page prefetch, which v4 does not have (no prefetch; the
  sync face uses the synchronous Send). The intent is kept as tests on v4's
  scans: a faulted page surfaces as its own exception, sync and async, and
  nothing is fetched once the enumeration is let go.
- #3257 ConnectionAttemptCompleted: v4 has no PhysicalConnection until an
  attempt succeeds, so ConnectionAttempt carries the record through the
  transport factory and the handshake instead - stages Connect, Tunnel, Tls,
  Handshake, Established; client/server certificate observation and the TLS
  host; one outcome per attempt. Created only when someone subscribes.
  Abandoned (executor disposal) reports ConnectionDisposed; outliving the
  connect timeout reports UnableToConnect with the timeout. A refused AUTH is
  reported as Handshake + AuthenticationFailure although v4 keeps the
  connection (parity with v3's fire-and-forget AUTH).
- #3264 dedicated pub/sub connection under RESP3 by default: v4's "RESP3, so
  share" becomes SharesSubscriptions (RESP3 *and* SharedSubscriptionConnection).
  v4 keeps pay-per-play dialling: the subscription socket is dialled up front
  only for the configuration channel, as under RESP2. The dedicated
  subscription connection now raises ConnectionRestored (Subscription) when it
  (re)connects, as v3's subscription bridge did - previously only reachable
  under RESP2, and silent there too (Issue922_ReconnectRaised). It does not
  trigger a reconfigure: topology is read over the interactive leg, and the
  subscription leg connects just after the initial connect.
- 3.4 bump: version.json stays 4.0; the 3.4.0 API surface is reconciled into
  PublicAPI.Shipped.txt.

Also fixed while here: the auth-refusal callback added for #3257 was a lambda
capturing `this`, which landed in the closure scope OnPush chains to and let
the socket root the multiplexer (GarbageCollectionTests.MuxerIsCollected).

Closes ledger rows D6 and D7.
FloatingMilkshake pushed a commit to FloatingMilkshake/MechanicalMilkshake that referenced this pull request Oct 9, 2026
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [StackExchange.Redis](https://seredis.dev/) ([source](https://github.com/StackExchange/StackExchange.Redis)) | nuget | minor | `3.3.1` → `3.4.0` |

---

### Release Notes

<details>
<summary>StackExchange/StackExchange.Redis (StackExchange.Redis)</summary>

### [`v3.4.0`](https://github.com/StackExchange/StackExchange.Redis/releases/tag/3.4.0)

[Compare Source](StackExchange/StackExchange.Redis@3.3.1...3.4.0)

#### Major Changes

- In RESP3, by default the library now uses **separate** connections for pub/sub; see [#&#8203;3264](StackExchange/StackExchange.Redis#3264)
- Smart-client-handoffs is now enabled by default on Redis Enterprise and AMR; see [#&#8203;3248](StackExchange/StackExchange.Redis#3248)
- Some logging events incorrectly used duplicated ids - resolved; see [#&#8203;3266](StackExchange/StackExchange.Redis#3266)

#### All Changes

- Recover promptly from a cluster failover; fix the configuration channel under RESP3 and with a channel prefix ([#&#8203;3254](StackExchange/StackExchange.Redis#3254)) by [@&#8203;mgravell](https://github.com/mgravell) in [#&#8203;3255](StackExchange/StackExchange.Redis#3255)
- Preserve binary members as VectorSetRange bounds by [@&#8203;Laurianti](https://github.com/Laurianti) in [#&#8203;3256](StackExchange/StackExchange.Redis#3256)
- Smart client handoffs: reinstate auto-enlistment for the hosted providers and remove \[Experimental] by [@&#8203;mgravell](https://github.com/mgravell) in [#&#8203;3248](StackExchange/StackExchange.Redis#3248)
- docs: fix retry-category test in the custom RetryPolicy sample by [@&#8203;mgravell](https://github.com/mgravell) in [#&#8203;3265](StackExchange/StackExchange.Redis#3265)
- Fix duplicate LoggerMessage event ids; add a guard test by [@&#8203;mgravell](https://github.com/mgravell) in [#&#8203;3266](StackExchange/StackExchange.Redis#3266)
- Scan: observe the faults of abandoned page tasks after a sync timeout or dispose ([#&#8203;3260](StackExchange/StackExchange.Redis#3260)) by [@&#8203;mgravell](https://github.com/mgravell) in [#&#8203;3267](StackExchange/StackExchange.Redis#3267)
- Report the outcome of every physical connection attempt, identifying the client certificate used by [@&#8203;mgravell](https://github.com/mgravell) in [#&#8203;3257](StackExchange/StackExchange.Redis#3257)
- Use a dedicated pub/sub connection under RESP3 by default by [@&#8203;mgravell](https://github.com/mgravell) in [#&#8203;3264](StackExchange/StackExchange.Redis#3264)

#### Tooling

- docs(agents): add create-implementation-plan-for-redis-api-change skill by [@&#8203;uglide](https://github.com/uglide) in [#&#8203;3268](StackExchange/StackExchange.Redis#3268)

#### New Contributors

- [@&#8203;Laurianti](https://github.com/Laurianti) made their first contribution in [#&#8203;3256](StackExchange/StackExchange.Redis#3256)

**Full Changelog**: <StackExchange/StackExchange.Redis@3.3.1...3.4.0>

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xNDkuMCIsInVwZGF0ZWRJblZlciI6IjQ0LjE0OS4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: https://git.milkshake.services/FloatingMilkshake/MechanicalMilkshake/pulls/9
Reviewed-by: Milkshake <floatingmilkshake@noreply.milkshake.services>
FloatingMilkshake pushed a commit to FloatingMilkshake/Azusa that referenced this pull request Oct 9, 2026
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [StackExchange.Redis](https://seredis.dev/) ([source](https://github.com/StackExchange/StackExchange.Redis)) | nuget | minor | `3.3.1` → `3.4.0` |

---

### Release Notes

<details>
<summary>StackExchange/StackExchange.Redis (StackExchange.Redis)</summary>

### [`v3.4.0`](https://github.com/StackExchange/StackExchange.Redis/releases/tag/3.4.0)

[Compare Source](StackExchange/StackExchange.Redis@3.3.1...3.4.0)

#### Major Changes

- In RESP3, by default the library now uses **separate** connections for pub/sub; see [#&#8203;3264](StackExchange/StackExchange.Redis#3264)
- Smart-client-handoffs is now enabled by default on Redis Enterprise and AMR; see [#&#8203;3248](StackExchange/StackExchange.Redis#3248)
- Some logging events incorrectly used duplicated ids - resolved; see [#&#8203;3266](StackExchange/StackExchange.Redis#3266)

#### All Changes

- Recover promptly from a cluster failover; fix the configuration channel under RESP3 and with a channel prefix ([#&#8203;3254](StackExchange/StackExchange.Redis#3254)) by [@&#8203;mgravell](https://github.com/mgravell) in [#&#8203;3255](StackExchange/StackExchange.Redis#3255)
- Preserve binary members as VectorSetRange bounds by [@&#8203;Laurianti](https://github.com/Laurianti) in [#&#8203;3256](StackExchange/StackExchange.Redis#3256)
- Smart client handoffs: reinstate auto-enlistment for the hosted providers and remove \[Experimental] by [@&#8203;mgravell](https://github.com/mgravell) in [#&#8203;3248](StackExchange/StackExchange.Redis#3248)
- docs: fix retry-category test in the custom RetryPolicy sample by [@&#8203;mgravell](https://github.com/mgravell) in [#&#8203;3265](StackExchange/StackExchange.Redis#3265)
- Fix duplicate LoggerMessage event ids; add a guard test by [@&#8203;mgravell](https://github.com/mgravell) in [#&#8203;3266](StackExchange/StackExchange.Redis#3266)
- Scan: observe the faults of abandoned page tasks after a sync timeout or dispose ([#&#8203;3260](StackExchange/StackExchange.Redis#3260)) by [@&#8203;mgravell](https://github.com/mgravell) in [#&#8203;3267](StackExchange/StackExchange.Redis#3267)
- Report the outcome of every physical connection attempt, identifying the client certificate used by [@&#8203;mgravell](https://github.com/mgravell) in [#&#8203;3257](StackExchange/StackExchange.Redis#3257)
- Use a dedicated pub/sub connection under RESP3 by default by [@&#8203;mgravell](https://github.com/mgravell) in [#&#8203;3264](StackExchange/StackExchange.Redis#3264)

#### Tooling

- docs(agents): add create-implementation-plan-for-redis-api-change skill by [@&#8203;uglide](https://github.com/uglide) in [#&#8203;3268](StackExchange/StackExchange.Redis#3268)

#### New Contributors

- [@&#8203;Laurianti](https://github.com/Laurianti) made their first contribution in [#&#8203;3256](StackExchange/StackExchange.Redis#3256)

**Full Changelog**: <StackExchange/StackExchange.Redis@3.3.1...3.4.0>

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xNDkuMCIsInVwZGF0ZWRJblZlciI6IjQ0LjE0OS4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: https://git.milkshake.services/FloatingMilkshake/Azusa/pulls/9
Reviewed-by: Milkshake <floatingmilkshake@noreply.milkshake.services>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Synchronous scan timeout can leave an internal task exception unobserved in 2.13.17

1 participant