EV-Etcd

 view release on metacpan or  search on metacpan

etcd_common.c  view on Meta::CPAN

        set_int_arg(&arg[args.num_args++], "grpc.http2.ping_timeout_ms", client->keepalive_timeout_ms);
    }
    /* grpc_channel_create arrived in gRPC 1.45 */
    grpc_channel *channel;
#ifdef HAVE_GRPC_NEW_CHANNEL_API
    grpc_channel_credentials *creds = client->creds
        ? client->creds : grpc_insecure_credentials_create();
    WITH_SIGNALS_BLOCKED(channel = grpc_channel_create(target, creds, &args));
    if (!client->creds) grpc_channel_credentials_release(creds);
#else
    WITH_SIGNALS_BLOCKED(channel = client->creds
        ? grpc_secure_channel_create(client->creds, target, &args, NULL)
        : grpc_insecure_channel_create(target, &args, NULL));
#endif
    if (!channel) return NULL;

    channel_ref_t *ref;
    Newx(ref, 1, channel_ref_t);
    ref->channel = channel;
    ref->refs = 1;
    return ref;
}

void etcd_channel_release(channel_ref_t *ref, int destroy) {
    if (ref && !--ref->refs) {
        /* A forked child discards only our wrapper, without touching gRPC. */
        if (destroy) grpc_channel_destroy(ref->channel);
        Safefree(ref);
    }
}

static void release_send_metadata(call_base_t *base) {
    if (base->owns_auth_value) {
        grpc_slice_unref(base->send_md[0].value);
        base->owns_auth_value = 0;
    }
}

void etcd_call_release(call_base_t *base) {
    etcd_channel_release(base->channel_ref, 1);
    base->channel_ref = NULL;
    release_send_metadata(base);
}

static void cancel_blocking_calls(ev_etcd_t *client, unsigned channel_gen, const char *message) {
    for (pending_call_t *pc = client->pending_calls; pc; pc = pc->next) {
        if (pc->call && pc->base.channel_gen == channel_gen
            && (pc->base.type == CALL_TYPE_LOCK || pc->base.type == CALL_TYPE_ELECTION_CAMPAIGN))
            grpc_call_cancel_with_status(pc->call, GRPC_STATUS_UNAVAILABLE, message, NULL);
    }
}

static void check_leader_failure(ev_etcd_t *client, unsigned channel_gen,
                                grpc_status_code status, grpc_slice status_details) {
    if (etcd_is_no_leader(status, status_details)) {
        /* Retired channels too: another failure may have moved the client already */
        cancel_blocking_calls(client, channel_gen, "etcdserver: no leader");
    }
}

void etcd_rotate_endpoint(ev_etcd_t *client) {
    int next = client->current_endpoint;
    if (client->endpoint_count > 1)
        next = (next + 1) % client->endpoint_count;

    channel_ref_t *ref = etcd_create_channel(client, client->endpoints[next]);
    if (!ref) return;

    channel_ref_t *old = client->channel_ref;
    unsigned old_gen = client->channel_gen;
    int was_ready = grpc_channel_check_connectivity_state(old->channel, 0) == GRPC_CHANNEL_READY;
    client->channel_ref = ref;
    client->channel = ref->channel;
    client->current_endpoint = next;
    client->channel_gen++;

    /* Calls without a deadline must not stay queued on a dead connection */
    if (!was_ready) {
        cancel_blocking_calls(client, old_gen, "Endpoint changed");
        for (watch_call_t *wc = client->watches; wc; wc = wc->next) {
            if (wc->active && wc->auto_reconnect && wc->call && wc->base.channel_gen == old_gen)
                grpc_call_cancel_with_status(wc->call, GRPC_STATUS_UNAVAILABLE, "Endpoint changed", NULL);
        }
        for (keepalive_call_t *kc = client->keepalives; kc; kc = kc->next) {
            if (kc->active && kc->auto_reconnect && kc->call && kc->base.channel_gen == old_gen)
                grpc_call_cancel_with_status(kc->call, GRPC_STATUS_UNAVAILABLE, "Endpoint changed", NULL);
        }
        for (observe_call_t *oc = client->observes; oc; oc = oc->next) {
            if (oc->active && oc->auto_reconnect && oc->call && oc->base.channel_gen == old_gen)
                grpc_call_cancel_with_status(oc->call, GRPC_STATUS_UNAVAILABLE, "Endpoint changed", NULL);
        }
    }
    etcd_channel_release(old, 1);
}

/* From a working member during a leader change: any other would answer the same */
static int cluster_wide_unavailable(grpc_status_code status, grpc_slice details) {
    static const char *const messages[] = {
        "etcdserver: leader changed",
        "etcdserver: request timed out, possibly due to previous leader failure",
        NULL
    };
    if (status != GRPC_STATUS_UNAVAILABLE)
        return 0;
    for (int i = 0; messages[i]; i++) {
        if (grpc_slice_eq(details, grpc_slice_from_static_string(messages[i])))
            return 1;
    }
    return 0;
}

static int failed_on_current(ev_etcd_t *client, unsigned channel_gen) {
    return client->endpoint_count > 1 && channel_gen == client->channel_gen;
}

static int connected(ev_etcd_t *client) {
    return grpc_channel_check_connectivity_state(client->channel, 0) == GRPC_CHANNEL_READY;
}

/* etcd's own request timeout on a member without a leader, not the client's deadline */
static int member_timed_out(grpc_status_code status, grpc_slice details) {
    if ((status == GRPC_STATUS_DEADLINE_EXCEEDED || status == GRPC_STATUS_UNKNOWN)
        && grpc_slice_eq(details, grpc_slice_from_static_string("context deadline exceeded")))
        return 1;
    return status == GRPC_STATUS_UNKNOWN
        && (grpc_slice_eq(details, grpc_slice_from_static_string("etcdserver: request timed out"))
            || grpc_slice_eq(details, grpc_slice_from_static_string(
                "etcdserver: request timed out, possibly due to connection lost")));
}

/* Only failures on the current channel rotate, so a burst moves the client once */
void etcd_endpoint_failed(ev_etcd_t *client, const call_base_t *base,
                          grpc_status_code status, grpc_slice status_details) {
    /* A lock or campaign ended by the sweep carries the same message */
    if (base->type != CALL_TYPE_LOCK && base->type != CALL_TYPE_ELECTION_CAMPAIGN)
        check_leader_failure(client, base->channel_gen, status, status_details);
    if (etcd_is_no_leader(status, status_details))
        client->no_leader_epoch++;
    if (!failed_on_current(client, base->channel_gen))
        return;
    if ((status == GRPC_STATUS_UNAVAILABLE && !cluster_wide_unavailable(status, status_details))
        || (status == GRPC_STATUS_DEADLINE_EXCEEDED && !connected(client))
        || member_timed_out(status, status_details))
        etcd_rotate_endpoint(client);
}

/* etcd refuses new streams during any election, but ends an established one
 * only after several election timeouts without a leader */
void etcd_stream_failed(ev_etcd_t *client, unsigned channel_gen, int established,
                        grpc_status_code status, grpc_slice status_details) {
    if (established)
        check_leader_failure(client, channel_gen, status, status_details);
    if (etcd_is_no_leader(status, status_details))
        client->no_leader_epoch++;
    if (failed_on_current(client, channel_gen)
        && ((status == GRPC_STATUS_UNAVAILABLE && !cluster_wide_unavailable(status, status_details))
            || !connected(client)))
        etcd_rotate_endpoint(client);
}

SV* create_error_hv(pTHX_ grpc_status_code code, const char *message, size_t message_len, const char *source) {
    HV *err = newHV();
    hv_store(err, "code", 4, newSViv(code), 0);
    hv_store(err, "status", 6, newSVpv(grpc_status_name(code), 0), 0);
    if (message && message_len > 0) {
        hv_store(err, "message", 7, newSVpvn(message, message_len), 0);
    } else {
        hv_store(err, "message", 7, newSVpv("", 0), 0);
    }
    hv_store(err, "source", 6, newSVpv(source, 0), 0);
    /* The one RESOURCE_EXHAUSTED that passes by itself */
    int retryable = is_retryable_status(code)
        && (code != GRPC_STATUS_RESOURCE_EXHAUSTED
            || (message_len == sizeof("etcdserver: too many requests") - 1
                && memEQ(message, "etcdserver: too many requests", message_len)));
    hv_store(err, "retryable", 9, newSViv(retryable), 0);
    return newRV_noinc((SV *)err);
}

SV* create_pending_error_hv(pTHX_ pending_call_t *pc, grpc_status_code code, const char *source) {
    SV *err = create_error_hv(aTHX_ code,
        (const char *)GRPC_SLICE_START_PTR(pc->status_details),
        GRPC_SLICE_LENGTH(pc->status_details), source);
    /* Late server cleanup of the lease-derived key would delete a same-lease retry's */
    if (pc->base.type == CALL_TYPE_LOCK || pc->base.type == CALL_TYPE_ELECTION_CAMPAIGN)
        hv_store((HV *)SvRV(err), "retryable", 9, newSViv(0), 0);
    return err;
}

SV* kv_to_hashref(pTHX_ Mvccpb__KeyValue *kv) {
    HV *hv = newHV();

    /* protobuf-c leaves .data NULL for empty bytes, and newSVpvn(NULL, 0) is undef */
    hv_store(hv, "key", 3,
             kv->key.data ? newSVpvn((char *)kv->key.data, kv->key.len) : newSVpvn("", 0), 0);
    hv_store(hv, "value", 5,
             kv->value.data ? newSVpvn((char *)kv->value.data, kv->value.len) : newSVpvn("", 0), 0);
    hv_store(hv, "create_revision", 15, newSVi64(kv->create_revision), 0);
    hv_store(hv, "mod_revision", 12, newSVi64(kv->mod_revision), 0);
    hv_store(hv, "version", 7, newSVi64(kv->version), 0);
    hv_store(hv, "lease", 5, newSVi64(kv->lease), 0);

    return newRV_noinc((SV *)hv);
}

SV* event_to_hashref(pTHX_ Mvccpb__Event *event) {
    HV *hv = newHV();

    const char *type_str = (event->type == MVCCPB__EVENT__EVENT_TYPE__PUT) ? "PUT" : "DELETE";
    hv_store(hv, "type", 4, newSVpv(type_str, 0), 0);

    if (event->kv) {
        hv_store(hv, "kv", 2, kv_to_hashref(aTHX_ event->kv), 0);
    }

    if (event->prev_kv) {
        hv_store(hv, "prev_kv", 7, kv_to_hashref(aTHX_ event->prev_kv), 0);
    }



( run in 0.752 second using v1.01-cache-2.11-cpan-16aabcbfbcb )