The create_nodes_conf_folder test helper deletes nodes.conf and then creates a directory in its place using two separate exec calls, and in best-effort mode the server legitimately recreates nodes.conf via rename() on a BIO thread whenever gossip changes the topology. When a save lands in that window the following mkdir -p fails EEXIST, because mkdir -p tolerates an existing directory but not an existing regular file. This is a [exception], so it kills the test client and fails the whole job rather than one test. The server is behaving correctly; the helper is the bug.
Details
Job: test-fedorarawhide-jemalloc, run 35039408935
https://github.com/valkey-io/valkey/actions/runs/35039408935/job/104615694004
Seen once across seven consecutive failing Daily runs on unstable (2026-09-10 to 2026-09-16). Source read at d6415e766.
[exception]: Executing test client: mkdir: cannot create directory '/__w/valkey/valkey/tests/tmp/server.7554.137/nodes.conf': File exists.
mkdir: cannot create directory '/__w/valkey/valkey/tests/tmp/server.7554.137/nodes.conf': File exists
while executing
"exec mkdir -p $cluster_conf_path"
(procedure "create_nodes_conf_folder" line 4)
invoked from within
"create_nodes_conf_folder 1"
("uplevel" body line 81)
start_cluster opens at tests/unit/cluster/misc.tcl:225, so body line 81 is tests/unit/cluster/misc.tcl:306, inside cluster-config-save-behavior best-effort mode - node continues running when config save fails.
The helper
tests/unit/cluster/misc.tcl:192
proc create_nodes_conf_folder {srv_idx} {
set cluster_conf_path [get_nodes_conf_path $srv_idx]
if {[file exists $cluster_conf_path]} { exec rm -rf $cluster_conf_path }
exec mkdir -p $cluster_conf_path
}
Two forks, tens of milliseconds apart, against a file the server owns.
Why the server refills the hole
clusterSaveConfigImpl renames a temp file into place at src/cluster_legacy.c:1163. In best-effort mode the save is queued, not inline: src/cluster_legacy.c:7013 calls clusterSaveConfigBackground (src/cluster_legacy.c:1243) which calls clusterSaveConfig(true, ...) (src/cluster_legacy.c:1207), handing the content to bioCreateClusterConfigSaveJob; the worker lands in clusterSaveConfigFromBio (src/cluster_legacy.c:1230). CLUSTER_TODO_SAVE_CONFIG is set from many gossip paths (src/cluster_legacy.c:3379, :3407, :4276, :4407, :4436, :4459), so a save can fire at any moment. A rename() onto a path the test just deleted succeeds and logs nothing, so the recreation is invisible in the server log.
Sequence that guarantees the window
tests/unit/cluster/misc.tcl:284-306
- 284-285
remove_nodes_conf_folder 0 / 1, nodes.conf deleted.
- 287
R 1 cluster failover takeover, save succeeds, nodes.conf recreated as a regular file.
- 292-303 wait for
cluster_config_save_status ok on both, i.e. the test explicitly waits for the file to be back.
- 305
create_nodes_conf_folder 0, ok.
- 306
create_nodes_conf_folder 1, rm -rf then a background save recreates the file, mkdir -p fails.
From the run (pid 37708 is R1 / srv -1, pid 37808 is R0 / srv 0):
00:22:16.774 37708 Taking over the primary <- misc.tcl:240
00:22:16.777 37708 Could not rename tmp cluster config file: Is a directory
00:22:16.799 37808 Taking over the primary <- misc.tcl:262
00:22:16.916 37708 Could not rename tmp cluster config file: Is a directory
00:22:16.940 37708 Taking over the primary <- misc.tcl:287, after the removes
00:22:17.092 37808 Could not rename tmp cluster config file: Is a directory <- after misc.tcl:305
00:22:17.116 37708 Client closed connection ... cmd=config|get <- get_nodes_conf_path for :306
00:22:17.17 mkdir fails
R1 logs no rename failure between 16.940 and shutdown, confirming its saves were succeeding right up to line 306.
Introduced by
f1addd8f3 "Use BIO thread for cluster config saving in cluster-config-save-behavior best-effort mode (#2555)", 2026-09-01. It added the remove-save-recreate cycle at 284-306 and moved the best-effort save onto a BIO thread, so the main thread returns from CLUSTER FAILOVER TAKEOVER and CLUSTER INFO while the write is still pending. The helper predates it (82d7b7e40, 2e28af6fd), but before #2555 it was only called once per file on a quiescent cluster, so the window was never reached.
Suggested fix
Retry until the directory sticks.
proc create_nodes_conf_folder {srv_idx} {
set p [get_nodes_conf_path $srv_idx]
wait_for_condition 50 100 {
[catch {file delete -force $p; file mkdir $p}] == 0 && [file isdirectory $p]
} else {
fail "could not turn $p into a directory"
}
}
Related
#33 is also fallout from #2555 (valgrind definite loss on the BIO cluster-config save content in a fork child) but is a different failure. No valkey-io/valkey issue tracks this one.
This was generated by AI but verified, with love, by a human.
The
create_nodes_conf_foldertest helper deletesnodes.confand then creates a directory in its place using two separateexeccalls, and in best-effort mode the server legitimately recreatesnodes.confviarename()on a BIO thread whenever gossip changes the topology. When a save lands in that window the followingmkdir -pfails EEXIST, becausemkdir -ptolerates an existing directory but not an existing regular file. This is a[exception], so it kills the test client and fails the whole job rather than one test. The server is behaving correctly; the helper is the bug.Details
Job:
test-fedorarawhide-jemalloc, run 35039408935https://github.com/valkey-io/valkey/actions/runs/35039408935/job/104615694004
Seen once across seven consecutive failing Daily runs on
unstable(2026-09-10 to 2026-09-16). Source read atd6415e766.start_clusteropens attests/unit/cluster/misc.tcl:225, so body line 81 istests/unit/cluster/misc.tcl:306, insidecluster-config-save-behavior best-effort mode - node continues running when config save fails.The helper
tests/unit/cluster/misc.tcl:192Two forks, tens of milliseconds apart, against a file the server owns.
Why the server refills the hole
clusterSaveConfigImplrenames a temp file into place atsrc/cluster_legacy.c:1163. In best-effort mode the save is queued, not inline:src/cluster_legacy.c:7013callsclusterSaveConfigBackground(src/cluster_legacy.c:1243) which callsclusterSaveConfig(true, ...)(src/cluster_legacy.c:1207), handing the content tobioCreateClusterConfigSaveJob; the worker lands inclusterSaveConfigFromBio(src/cluster_legacy.c:1230).CLUSTER_TODO_SAVE_CONFIGis set from many gossip paths (src/cluster_legacy.c:3379,:3407,:4276,:4407,:4436,:4459), so a save can fire at any moment. Arename()onto a path the test just deleted succeeds and logs nothing, so the recreation is invisible in the server log.Sequence that guarantees the window
tests/unit/cluster/misc.tcl:284-306remove_nodes_conf_folder 0/1,nodes.confdeleted.R 1 cluster failover takeover, save succeeds,nodes.confrecreated as a regular file.cluster_config_save_statusokon both, i.e. the test explicitly waits for the file to be back.create_nodes_conf_folder 0, ok.create_nodes_conf_folder 1,rm -rfthen a background save recreates the file,mkdir -pfails.From the run (pid 37708 is R1 /
srv -1, pid 37808 is R0 /srv 0):R1 logs no rename failure between 16.940 and shutdown, confirming its saves were succeeding right up to line 306.
Introduced by
f1addd8f3"Use BIO thread for cluster config saving in cluster-config-save-behavior best-effort mode (#2555)", 2026-09-01. It added the remove-save-recreate cycle at 284-306 and moved the best-effort save onto a BIO thread, so the main thread returns fromCLUSTER FAILOVER TAKEOVERandCLUSTER INFOwhile the write is still pending. The helper predates it (82d7b7e40,2e28af6fd), but before #2555 it was only called once per file on a quiescent cluster, so the window was never reached.Suggested fix
Retry until the directory sticks.
Related
#33 is also fallout from #2555 (valgrind definite loss on the BIO cluster-config save content in a fork child) but is a different failure. No
valkey-io/valkeyissue tracks this one.This was generated by AI but verified, with love, by a human.