Forward-slash path composition breaks under extended-length (\\?\) paths; long checkouts flake SeSaveRecoverMultipleObjectsTest
@TedHartMS is already working on this.
Since Sep 17, 2026.
- Dominant language
- C#
- Stars
- 12k
- Forks
- 703
- Avg merge
- 2d 19h
- Merged PRs (30d)
- 36
Description
### Describe the bug
Garnet composes several local-filesystem paths by concatenating a forward slash onto a base directory, e.g.:
```csharp
var clusterFolder = "/cluster";
var clusterDataPath = serverOptions.CheckpointDir + clusterFolder;
```
Windows normalizes `/` to `\` in ordinary paths, so this works today. It does **not** normalize separators inside a Win32 extended-length (`\\?\`) path — `CreateFileW` fails with `ERROR_INVALID_NAME` (123).
This blocks the natural fix for a second, user-visible problem: `RespAdminCommandsTests.SeSaveRecoverMultipleObjectsTest` fails intermittently when the repository is checked out under a long path. `LocalStorageDevice` rejects non-extended paths longer than `Native32.WIN32_MAX_PATH - 11` (249), and the deepest checkpoint file (`...\Store\checkpoints\cpr-checkpoints\\snapshot.obj.dat`) overflows it. The failure is intermittent because `TestUtils.UnitTestWorkingDir()` embeds `HashCode.ToHashCode()`, which is randomized per process, so the generated directory name's length varies run to run and crosses 249 roughly half the time.
Tsavorite's `TestUtils` already solves this with `EnsureExtendedLengthPathIfNeeded`, but propagating that helper to Garnet's `TestUtils` fails until the separators are fixed: it makes test directories `\\?\`-prefixed, and the forward slashes above then become illegal.
Affected sites (all local-filesystem):
1. `libs/cluster/Server/ClusterManager.cs:68-69`
2. `libs/cluster/Server/Replication/ReplicationManager.cs:153-154`
3. `libs/server/PubSub/SubscribeBroker.cs:42`
4. `libs/storage/Tsavorite/cs/src/core/Index/Common/KVSettings.cs:203`
5. `libs/storage/Tsavorite/cs/src/core/Index/Common/KVSettings.cs:204`
6. `libs/storage/Tsavorite/cs/src/core/TsavoriteLog/TsavoriteLogSettings.cs:162`
Azure blob paths, `runtimes/{rid}/native/...` library paths, and Linux `/sys/...` paths correctly use forward slashes and are not affected.
### Steps to reproduce the bug
1. Check out the repository under a long path (the deepest checkpoint file must exceed 249 characters — a checkout root of roughly 100 characters is enough).
2. Run `dotnet test test\standalone\Garnet.test\Garnet.test.csproj -f net10.0 -c Debug --filter "FullyQualifiedName~SeSaveRecoverMultipleObjectsTest"` repeatedly.
3. Observe intermittent failures (measured 8 of 18 parameter combinations on an affected checkout). The underlying exception is swallowed, so the symptom surfaces as an empty store after recovery.
To see the separator half directly, apply `EnsureExtendedLengthPathIfNeeded` to `TestUtils.UnitTestWorkingDir()` and run the cluster suite:
```
System.IO.IOException : Error creating log file for
\\?\...\.tmp\\7000/cluster\nodes.conf.0, error: 123 0x(-2147024773)
at Tsavorite.core.LocalStorageDevice.CreateHandle
at Garnet.cluster.ClusterManager..ctor (ClusterManager.cs:79)
```
Note the `7000/cluster` — the forward slash from site 1. This reproduced as 24 failures across `Garnet.test.cluster`.
### Expected behavior
Local-filesystem paths should be composed with `Path.Combine` so they are valid under both ordinary and extended-length paths, and the test working directory should transparently switch to an extended-length path when it is close enough to `MAX_PATH` that the files the tests create beneath it would overflow.
### Release version
main (as of 2026-09)
### OS version
Windows (Windows-only; on Linux `/` is already correct and behavior is unchanged)
### Additional context
`Path.Combine` treats a **rooted** second argument as absolute and discards the first, so the leading slash must be dropped at each site — `Path.Combine(dir, "/cluster")` returns `"\cluster"`, which would silently relocate cluster data to the drive root.
Sites 4-6 are the `KVSettings(baseDir)` / `TsavoriteLogSettings(baseDir)` convenience constructors. No in-repo caller passes a non-null `baseDir`, so those are latent and affect external consumers of the public API only.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.