Forward-slash path composition breaks under extended-length (\\?\) paths; long checkouts flake SeSaveRecoverMultipleObjectsTest
Describe the bug
Garnet composes several local-filesystem paths by concatenating a forward slash onto a base directory, e.g.:
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\<guid>\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):
libs/cluster/Server/ClusterManager.cs:68-69libs/cluster/Server/Replication/ReplicationManager.cs:153-154libs/server/PubSub/SubscribeBroker.cs:42libs/storage/Tsavorite/cs/src/core/Index/Common/KVSettings.cs:203libs/storage/Tsavorite/cs/src/core/Index/Common/KVSettings.cs:204libs/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
- 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).
- Run
dotnet test test\standalone\Garnet.test\Garnet.test.csproj -f net10.0 -c Debug --filter "FullyQualifiedName~SeSaveRecoverMultipleObjectsTest"repeatedly. - 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\<test>\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.
Source: microsoft/garnet