microsoft / microsoft/garnet

Forward-slash path composition breaks under extended-length (\\?\) paths; long checkouts flake SeSaveRecoverMultipleObjectsTest

Open
#2,146 0 comments 0 reactions 1 assignee View on GitHub

@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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.