Skip to content

[daily-ci] FLAKY-TEST: create_nodes_conf_folder races the BIO cluster-config save that recreates nodes.conf #36

Description

@madolson

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

  1. 284-285 remove_nodes_conf_folder 0 / 1, nodes.conf deleted.
  2. 287 R 1 cluster failover takeover, save succeeds, nodes.conf recreated as a regular file.
  3. 292-303 wait for cluster_config_save_status ok on both, i.e. the test explicitly waits for the file to be back.
  4. 305 create_nodes_conf_folder 0, ok.
  5. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions