Repository: valkey-io/valkey
File: src/cluster_legacy.c (C)

Code before:
 * This function scans the radix tree of failure reports (keyed by bucketed
 * timestamps plus sender pointer) for the given node and removes any entries that
 * have expired beyond the cutoff window
 *
 * By expiring entries older than the global node timeout window,
 * we guarantee only fresh votes remain. */
void clusterNodeCleanupFailureReports(clusterNode *node) {
    mstime_t now = mstime();
    mstime_t timeout = server.cluster_node_timeout * CLUSTER_FAIL_REPORT_VALIDITY_MULT;
    mstime_t cutoff = now - timeout;

    raxIterator ri;
    raxStart(&ri, node->fail_reports);
    raxSeek(&ri, "^", NULL, 0);

    while (raxNext(&ri)) {
        mstime_t reported_time;
        clusterNode *reported_node;
        decodeFailureReportKey(ri.key, &reported_time, &reported_node);
        if (reported_time > cutoff) break;

        int retval = raxRemove(node->fail_reports, ri.key, ri.key_len, NULL);
        serverAssert(retval == 1);
        /* Move directly to the next element after the tree mutation due to removal */
        raxSeek(&ri, ">=", ri.key, ri.key_len);
    }

    raxStop(&ri);
}

/* Remove the failing report for 'node' if it was previously considered
 * failing by 'sender'. This function is called when a node informs us via
 * gossip that a node is OK from its point of view (no FAIL or PFAIL flags).
 *
 * Note that this function is called relatively often as it gets called even
 * when there are no nodes failing, and is O(N), however when the cluster is
 * fine the failure reports list is empty so the function runs in constant
 * time.
 *
 * The function returns 1 if the failure report was found and removed.
 * Otherwise 0 is returned. */
int clusterNodeDelFailureReport(clusterNode *node, clusterNode *sender) {
    raxIterator ri;
    raxStart(&ri, node->fail_reports);
    raxSeek(&ri, "^", NULL, 0);

    while (raxNext(&ri)) {
        mstime_t reported_time;
        clusterNode *reported_node;
        decodeFailureReportKey(ri.key, &reported_time, &reported_node);
        if (reported_node == sender) {

Review comment (bug):
isn't it better to branch out from [clusterProcessGossipSection](https://github.com/valkey-io/valkey/blob/unstable/src/cluster_legacy.c#L2560)? `markNodeAsFailingIfNeeded` also has a similar condition where we just return 0 if the node is failed.

Code after:
    *report_time = (mstime_t)ntohu64(big_endian_time);
    memcpy(node_ptr, buf + sizeof(uint64_t), sizeof(*node_ptr));
}

/* This function is called every time we get a failure report from a node.
 * We maintain fail_reports as a radix tree keyed by bucketed timestamp
 * (ceiled to the next second) and the sender pointer, which naturally orders
 * entries by time.
 *
 * 'failing' is the node that is in failure state according to the
 * 'sender' node.
 *
 * Returns 0:
 *   - The node is already in FAIL state
 *   - The same sender has already reported within the same time bucket
 *   - An existing report from 'sender' was refreshed (timestamp updated)
 * Returns 1 if a brand new failure report entry is created. */
int clusterNodeAddFailureReport(clusterNode *failing, clusterNode *sender) {
    unsigned char buf[FAILURE_REPORT_KEYLEN];
    mstime_t now = mstime();
    const mstime_t bucketed_time = (now / SEC_IN_MS) * SEC_IN_MS + SEC_IN_MS;
    int is_new = 1;

    /* This avoids unnecessary iteration and memory ops, improving performance
     * when handling repeated reports for already failed nodes. */
    if (nodeFailed(failing)) return 0;

    /* Look for any existing entry from this sender and remove it */
    raxIterator ri;
    raxStart(&ri, failing->fail_reports);
    raxSeek(&ri, "^", NULL, 0);
    while (raxNext(&ri)) {
        mstime_t reported_time;
        clusterNode *reported_node;
        decodeFailureReportKey(ri.key, &reported_time, &reported_node);
        if (reported_node == sender) {
            if (reported_time == bucketed_time) {
                /* Early exit if it is the exact same sender and bucket */
                raxStop(&ri);
                return 0;
            }

            raxRemove(failing->fail_reports,
                      ri.key, ri.key_len, NULL);
            is_new = 0;
            break;
        }
    }
    raxStop(&ri);

    /* Encode new key (now + sender) and store the fresh timestamp */