diff --git a/README.md b/README.md
index f8c6c4fbd..1e7535701 100644
--- a/README.md
+++ b/README.md
@@ -17,4 +17,4 @@ MyGet Pre-release feed: https://www.myget.org/gallery/stackoverflow
| ------- | ------------ | ----------------- | --------- | ----- |
| [StackExchange.Redis](https://www.nuget.org/packages/StackExchange.Redis/) | [](https://www.nuget.org/packages/StackExchange.Redis/) | [](https://www.nuget.org/packages/StackExchange.Redis/absoluteLatest) | [](https://www.nuget.org/packages/StackExchange.Redis/) | [](https://www.myget.org/feed/stackoverflow/package/nuget/StackExchange.Redis) |
-Release notes at: https://github.com/StackExchange/StackExchange.Redis/releases (3.0 onwards; earlier releases at https://seredis.dev/ReleaseNotes)
+Release notes at: [GitHub Releases](https://github.com/StackExchange/StackExchange.Redis/releases) (3.0 onwards; [earlier releases](https://seredis.dev/ReleaseNotes))
diff --git a/docs/404.html b/docs/404.html
new file mode 100644
index 000000000..cb4dd0ac4
--- /dev/null
+++ b/docs/404.html
@@ -0,0 +1,50 @@
+---
+layout: default
+title: Page not found
+sitemap: false
+---
+
+
There is nothing at this address.
+
+
+
+If you got here from a link in an exception message or a log line, the punctuation after the URL was
+probably picked up as part of it — most link detectors will happily swallow a closing bracket or a
+quote.
+
+
+
+
diff --git a/docs/CompareAndSwap.md b/docs/CompareAndSwap.md
index 7d79d42a0..c88347b12 100644
--- a/docs/CompareAndSwap.md
+++ b/docs/CompareAndSwap.md
@@ -316,6 +316,6 @@ var condition = ValueCondition.CalculateDigest(largeValueBytes);
## See Also
-- [Transactions](Transactions.md) - For multi-key atomic operations
-- [Keys and Values](KeysValues.md) - Understanding Redis data types
+- [Transactions](Transactions) - For multi-key atomic operations
+- [Keys and Values](KeysValues) - Understanding Redis data types
- [Redis CAS/CAD Documentation](https://redis.io/docs/latest/commands/set/) - Redis 8.4 SET command with IFEQ/IFNE/IFDEQ/IFDNE modifiers
diff --git a/docs/Profiling.md b/docs/Profiling.md
index cffdae25d..6b34f55e6 100644
--- a/docs/Profiling.md
+++ b/docs/Profiling.md
@@ -5,6 +5,6 @@ The profiling API has undergone breaking changes between 1.* and 2.*; in 1.*; in
the `object GetContext()` API was unintuitive for consumers and expensive for the library (due to book-keeping). The API in 2.* is much
simpler and more "obvious". This is a breaking change.
-[Profiling in 1.\*](Profiling_v1.md)
+[Profiling in 1.\*](Profiling_v1)
-[Profiling in 2.\*](Profiling_v2.md)
+[Profiling in 2.\*](Profiling_v2)
diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md
index 14231ff50..500a416dd 100644
--- a/docs/ReleaseNotes.md
+++ b/docs/ReleaseNotes.md
@@ -613,7 +613,7 @@ Changes:
- **Soft Break**: The `PreserveAsyncOrder` behaviour of the pub/sub API has been deprecated; a *new* API has been provided for scenarios that require in-order pub/sub handling -
the `Subscribe` method has a new overload *without* a handler parameter which returns a `ChannelMessageQueue`, which provides `async` ordered access to messages)
- Internal: The network architecture has moved to use `System.IO.Pipelines`; this has allowed us to simplify and unify a lot of the network code, and in particular fix a lot of problems relating to how the library worked with TLS and/or .NETStandard
-- Change: As a result of the `System.IO.Pipelines` change, the error-reporting on timeouts is now much simpler and clearer; the [timeouts documentation](Timeouts.md) has been updated
+- Change: As a result of the `System.IO.Pipelines` change, the error-reporting on timeouts is now much simpler and clearer; the [timeouts documentation](Timeouts) has been updated
- Removed: The `HighPriority` (queue-jumping) flag is now deprecated
- Internal: Most buffers internally now make use of pooled memory; `RedisValue` no longer preemptively allocates buffers
- Internal: Added new custom thread-pool for handling async continuations to avoid thread-pool starvation issues
diff --git a/src/StackExchange.Redis/ExceptionFactory.cs b/src/StackExchange.Redis/ExceptionFactory.cs
index d47d62c97..aa9bf7001 100644
--- a/src/StackExchange.Redis/ExceptionFactory.cs
+++ b/src/StackExchange.Redis/ExceptionFactory.cs
@@ -309,9 +309,12 @@ internal static Exception Timeout(ConnectionMultiplexer multiplexer, string? bas
}
AddCommonDetail(data, sb, message, multiplexer, server);
- sb.Append(" (Please take a look at this article for some common client-side issues that can cause timeouts: ")
+ // Keep the URL away from the end of the message and away from punctuation: linkifiers in log
+ // viewers, chat and issue trackers greedily swallow a trailing ')' or '"', and the docs site
+ // then sees a 404 for /Timeouts). The space after it is what makes them stop.
+ sb.Append(" (see ")
.Append(TimeoutHelpLink)
- .Append(')');
+ .Append(" for some common client-side issues that can cause timeouts)");
// If we're from a backlog timeout scenario, we log a more intuitive connection exception for the timeout...because the timeout was a symptom
// and we have a more direct cause: we had no connection to send it on.
diff --git a/tests/StackExchange.Redis.Tests/AbortOnConnectFailTests.cs b/tests/StackExchange.Redis.Tests/AbortOnConnectFailTests.cs
index 0c156300c..e2c34c9ad 100644
--- a/tests/StackExchange.Redis.Tests/AbortOnConnectFailTests.cs
+++ b/tests/StackExchange.Redis.Tests/AbortOnConnectFailTests.cs
@@ -49,7 +49,7 @@ public async Task DisconnectAndReconnectThrowsConnectionExceptionSync()
Assert.SkipUnless(server.CanSimulateConnectionFailure, "Skipping because server cannot simulate connection failure");
server.SimulateConnectionFailure(SimulatedFailureType.All);
- // Exception: The message timed out in the backlog attempting to send because no connection became available (400ms) - Last Connection Exception: SocketFailure (InputReaderCompleted, last-recv: 7) on 127.0.0.1:6379/Interactive, Idle/ReadAsync, last: PING, origin: SimulateConnectionFailure, outstanding: 0, last-read: 0s ago, last-write: 0s ago, keep-alive: 100s, state: ConnectedEstablished, mgr: 10 of 10 available, in: 0, in-pipe: 0, out-pipe: 0, last-heartbeat: never, last-mbeat: 0s ago, global: 0s ago, v: 2.6.120.51136, command=PING, timeout: 100, inst: 13, qu: 1, qs: 0, aw: False, bw: Inactive, last-in: 0, cur-in: 0, sync-ops: 2, async-ops: 0, serverEndpoint: 127.0.0.1:6379, conn-sec: n/a, aoc: 0, mc: 1/1/0, mgr: 10 of 10 available, clientName: CRAVERTOP7(SE.Redis-v2.6.120.51136), IOCP: (Busy=0,Free=1000,Min=16,Max=1000), WORKER: (Busy=2,Free=32765,Min=16,Max=32767), POOL: (Threads=33,QueuedItems=0,CompletedItems=6237,Timers=39), v: 2.6.120.51136 (Please take a look at this article for some common client-side issues that can cause timeouts: https://seredis.dev/Timeouts)
+ // Exception: The message timed out in the backlog attempting to send because no connection became available (400ms) - Last Connection Exception: SocketFailure (InputReaderCompleted, last-recv: 7) on 127.0.0.1:6379/Interactive, Idle/ReadAsync, last: PING, origin: SimulateConnectionFailure, outstanding: 0, last-read: 0s ago, last-write: 0s ago, keep-alive: 100s, state: ConnectedEstablished, mgr: 10 of 10 available, in: 0, in-pipe: 0, out-pipe: 0, last-heartbeat: never, last-mbeat: 0s ago, global: 0s ago, v: 2.6.120.51136, command=PING, timeout: 100, inst: 13, qu: 1, qs: 0, aw: False, bw: Inactive, last-in: 0, cur-in: 0, sync-ops: 2, async-ops: 0, serverEndpoint: 127.0.0.1:6379, conn-sec: n/a, aoc: 0, mc: 1/1/0, mgr: 10 of 10 available, clientName: CRAVERTOP7(SE.Redis-v2.6.120.51136), IOCP: (Busy=0,Free=1000,Min=16,Max=1000), WORKER: (Busy=2,Free=32765,Min=16,Max=32767), POOL: (Threads=33,QueuedItems=0,CompletedItems=6237,Timers=39), v: 2.6.120.51136 (see https://seredis.dev/Timeouts for some common client-side issues that can cause timeouts)
var ex = Assert.ThrowsAny(() => db.Ping());
Log("Exception: " + ex.Message);
Assert.True(ex is RedisConnectionException or RedisTimeoutException);
@@ -75,7 +75,7 @@ public async Task DisconnectAndNoReconnectThrowsConnectionExceptionAsync()
Assert.SkipUnless(server.CanSimulateConnectionFailure, "Skipping because server cannot simulate connection failure");
server.SimulateConnectionFailure(SimulatedFailureType.All);
- // Exception: The message timed out in the backlog attempting to send because no connection became available (400ms) - Last Connection Exception: SocketFailure (InputReaderCompleted, last-recv: 7) on 127.0.0.1:6379/Interactive, Idle/ReadAsync, last: PING, origin: SimulateConnectionFailure, outstanding: 0, last-read: 0s ago, last-write: 0s ago, keep-alive: 100s, state: ConnectedEstablished, mgr: 8 of 10 available, in: 0, in-pipe: 0, out-pipe: 0, last-heartbeat: never, last-mbeat: 0s ago, global: 0s ago, v: 2.6.120.51136, command=PING, timeout: 100, inst: 0, qu: 0, qs: 0, aw: False, bw: CheckingForTimeout, last-in: 0, cur-in: 0, sync-ops: 1, async-ops: 1, serverEndpoint: 127.0.0.1:6379, conn-sec: n/a, aoc: 0, mc: 1/1/0, mgr: 8 of 10 available, clientName: CRAVERTOP7(SE.Redis-v2.6.120.51136), IOCP: (Busy=0,Free=1000,Min=16,Max=1000), WORKER: (Busy=6,Free=32761,Min=16,Max=32767), POOL: (Threads=33,QueuedItems=0,CompletedItems=5547,Timers=60), v: 2.6.120.51136 (Please take a look at this article for some common client-side issues that can cause timeouts: https://seredis.dev/Timeouts)
+ // Exception: The message timed out in the backlog attempting to send because no connection became available (400ms) - Last Connection Exception: SocketFailure (InputReaderCompleted, last-recv: 7) on 127.0.0.1:6379/Interactive, Idle/ReadAsync, last: PING, origin: SimulateConnectionFailure, outstanding: 0, last-read: 0s ago, last-write: 0s ago, keep-alive: 100s, state: ConnectedEstablished, mgr: 8 of 10 available, in: 0, in-pipe: 0, out-pipe: 0, last-heartbeat: never, last-mbeat: 0s ago, global: 0s ago, v: 2.6.120.51136, command=PING, timeout: 100, inst: 0, qu: 0, qs: 0, aw: False, bw: CheckingForTimeout, last-in: 0, cur-in: 0, sync-ops: 1, async-ops: 1, serverEndpoint: 127.0.0.1:6379, conn-sec: n/a, aoc: 0, mc: 1/1/0, mgr: 8 of 10 available, clientName: CRAVERTOP7(SE.Redis-v2.6.120.51136), IOCP: (Busy=0,Free=1000,Min=16,Max=1000), WORKER: (Busy=6,Free=32761,Min=16,Max=32767), POOL: (Threads=33,QueuedItems=0,CompletedItems=5547,Timers=60), v: 2.6.120.51136 (see https://seredis.dev/Timeouts for some common client-side issues that can cause timeouts)
var ex = await Assert.ThrowsAsync(() => db.PingAsync());
Log("Exception: " + ex.Message);
Assert.StartsWith("The message timed out in the backlog attempting to send because no connection became available (1000ms) - Last Connection Exception: ", ex.Message);
diff --git a/tests/StackExchange.Redis.Tests/ExceptionFactoryTests.cs b/tests/StackExchange.Redis.Tests/ExceptionFactoryTests.cs
index ea868fb46..00d39b60d 100644
--- a/tests/StackExchange.Redis.Tests/ExceptionFactoryTests.cs
+++ b/tests/StackExchange.Redis.Tests/ExceptionFactoryTests.cs
@@ -116,7 +116,7 @@ public async Task TimeoutException()
var ex = Assert.IsType(rawEx);
Log("Exception: " + ex.Message);
- // Example format: "Test Timeout, command=PING, inst: 0, qu: 0, qs: 0, aw: False, in: 0, in-pipe: 0, out-pipe: 0, last-in: 0, cur-in: 0, serverEndpoint: 127.0.0.1:6379, mgr: 10 of 10 available, clientName: TimeoutException, IOCP: (Busy=0,Free=1000,Min=8,Max=1000), WORKER: (Busy=2,Free=2045,Min=8,Max=2047), v: 2.1.0 (Please take a look at this article for some common client-side issues that can cause timeouts: https://seredis.dev/Timeouts)";
+ // Example format: "Test Timeout, command=PING, inst: 0, qu: 0, qs: 0, aw: False, in: 0, in-pipe: 0, out-pipe: 0, last-in: 0, cur-in: 0, serverEndpoint: 127.0.0.1:6379, mgr: 10 of 10 available, clientName: TimeoutException, IOCP: (Busy=0,Free=1000,Min=8,Max=1000), WORKER: (Busy=2,Free=2045,Min=8,Max=2047), v: 2.1.0 (see https://seredis.dev/Timeouts for some common client-side issues that can cause timeouts)";
Assert.StartsWith("Test Timeout, command=PING", ex.Message);
Assert.Contains("clientName: " + nameof(TimeoutException), ex.Message);
// Ensure our pipe numbers are in place
@@ -138,7 +138,7 @@ public async Task TimeoutException()
Assert.Contains("Timers=", ex.Message);
#endif
Assert.DoesNotContain("Unspecified/", ex.Message);
- Assert.EndsWith(" (Please take a look at this article for some common client-side issues that can cause timeouts: https://seredis.dev/Timeouts)", ex.Message);
+ Assert.EndsWith(" (see https://seredis.dev/Timeouts for some common client-side issues that can cause timeouts)", ex.Message);
Assert.Null(ex.InnerException);
}
finally