/** * Schedules us to be notified with the given state within a particular amount * of time. This is used both for the timeout and for the interval */ void lcbdur_switch_state(lcb_DURSET *dset, unsigned int state) { lcb_U32 delay = 0; lcbio_TABLE* io = dset->instance->iotable; hrtime_t now = gethrtime(); if (state == LCBDUR_STATE_TIMEOUT) { if (dset->ns_timeout && now < dset->ns_timeout) { delay = LCB_NS2US(dset->ns_timeout - now); } else { delay = 0; } } else if (state == LCBDUR_STATE_OBSPOLL) { if (now + LCB_US2NS(DSET_OPTFLD(dset, interval)) < dset->ns_timeout) { delay = DSET_OPTFLD(dset, interval); } else { delay = 0; state = LCBDUR_STATE_TIMEOUT; } } else if (state == LCBDUR_STATE_INIT) { delay = 0; } dset->next_state = state; io->timer.cancel(io->p, dset->timer); io->timer.schedule(io->p, dset->timer, delay, dset, timer_callback); }
void lcbdur_prepare_item(lcb_DURITEM *ent, lcb_U16 *ixarray, size_t *nitems) { size_t ii, oix = 0, maxix = 0; lcb_DURSET *dset = ent->parent; lcb_t instance = dset->instance; lcbvb_CONFIG *vbc = LCBT_VBCONFIG(instance); RESFLD(ent, persisted_master) = 0; RESFLD(ent, exists_master) = 0; RESFLD(ent, npersisted) = 0; RESFLD(ent, nreplicated) = 0; RESFLD(ent, cas) = 0; RESFLD(ent, rc) = LCB_SUCCESS; if (DSET_OPTFLD(dset, persist_to) == 1 && DSET_OPTFLD(dset, replicate_to) == 0) { maxix = 1; /* Only master! */ } else { maxix = LCBT_NREPLICAS(instance) + 1; } for (ii = 0; ii < maxix; ii++) { int cur_ix; lcbdur_SERVINFO *info = &ent->sinfo[ii]; const mc_SERVER *s_exp; cur_ix = lcbvb_vbserver(vbc, ent->vbid, ii); if (cur_ix < 0) { memset(info, 0, sizeof(*info)); continue; } s_exp = LCBT_GET_SERVER(instance, cur_ix); if (s_exp != info->server) { memset(info, 0, sizeof(*info)); } else if (server_criteria_satisfied(ent, info, ii==0)) { /* Update counters as required */ if (ii == 0) { RESFLD(ent, exists_master) = 1; } else { RESFLD(ent, nreplicated)++; } if (info->persisted) { RESFLD(ent, npersisted)++; if (ii == 0) { RESFLD(ent, persisted_master) = 1; } } continue; } /* Otherwise, write the expected server out */ ixarray[oix++] = s_exp->pipeline.index; } *nitems = oix; }
LIBCOUCHBASE_API lcb_MULTICMD_CTX * lcb_endure3_ctxnew(lcb_t instance, const lcb_durability_opts_t *options, lcb_error_t *errp) { lcb_DURSET *dset; lcb_error_t err_s; hrtime_t now; lcbio_pTABLE io = instance->iotable; if (!errp) { errp = &err_s; } if (!LCBT_VBCONFIG(instance)) { *errp = LCB_CLIENT_ETMPFAIL; return NULL; } now = gethrtime(); dset = calloc(1, sizeof(*dset)); if (!dset) { *errp = LCB_CLIENT_ENOMEM; return NULL; } dset->opts = *options; dset->instance = instance; dset->mctx.addcmd = dset_ctx_add; dset->mctx.done = dset_ctx_schedule; dset->mctx.fail = dset_ctx_fail; if (!DSET_OPTFLD(dset, timeout)) { DSET_OPTFLD(dset, timeout) = LCBT_SETTING(instance, durability_timeout); } if (!DSET_OPTFLD(dset, interval)) { DSET_OPTFLD(dset, interval) = LCB_DEFAULT_DURABILITY_INTERVAL; } if (-1 == verify_critera(instance, dset)) { free(dset); *errp = LCB_DURABILITY_ETOOMANY; return NULL; } dset->us_timeout = (lcb_U32)(now / 1000) + DSET_OPTFLD(dset, timeout); dset->timer = io->timer.create(io->p); lcb_string_init(&dset->kvbufs); return &dset->mctx; }
static int server_criteria_satisfied(const lcb_DURITEM *item, const lcbdur_SERVINFO *info, int is_master) { const lcb_DURSET *dset = item->parent; if (!info->exists) { return 0; } if (info->persisted) { return 1; } if (DSET_OPTFLD(dset, persist_to) == 0) { return 1; } if (DSET_OPTFLD(dset, persist_to) == 1 && !is_master) { return 1; } return 0; }
/** * Called when the last (primitive) OBSERVE response is received for the entry. */ static void dset_done_waiting(lcb_durability_set_t *dset) { lcb_assert(dset->waiting || ("Got NULL callback twice!" && 0)); dset->waiting = 0; if (dset->nremaining > 0) { timer_schedule(dset, DSET_OPTFLD(dset, interval), STATE_OBSPOLL); } dset_unref(dset); }
static lcb_error_t dset_ctx_schedule(lcb_MULTICMD_CTX *mctx, const void *cookie) { size_t ii; lcb_error_t err; lcb_DURSET *dset = CTX_FROM_MULTI(mctx); char *kptr = dset->kvbufs.base; if (!DSET_COUNT(dset)) { lcbdur_destroy(dset); return LCB_EINVAL; } for (ii = 0; ii < DSET_COUNT(dset); ii++) { lcb_DURITEM *ent = DSET_ENTRIES(dset) + ii; RESFLD(ent, key) = kptr; kptr += RESFLD(ent, nkey); } if (DSET_PROCS(dset)->schedule) { err = DSET_PROCS(dset)->schedule(dset); if (err != LCB_SUCCESS) { lcbdur_destroy(dset); return err; } } lcbdur_ref(dset); dset->cookie = cookie; dset->nremaining = DSET_COUNT(dset); dset->ns_timeout = gethrtime() + LCB_US2NS(DSET_OPTFLD(dset, timeout)); lcb_aspend_add(&dset->instance->pendops, LCB_PENDTYPE_DURABILITY, dset); lcbdur_switch_state(dset, LCBDUR_STATE_INIT); return LCB_SUCCESS; }
LIBCOUCHBASE_API lcb_MULTICMD_CTX * lcb_endure3_ctxnew(lcb_t instance, const lcb_durability_opts_t *options, lcb_error_t *errp) { lcb_DURSET *dset; lcb_error_t err_s; lcbio_pTABLE io = instance->iotable; const lcb_DURABILITYOPTSv0 *opts_in = &options->v.v0; if (!errp) { errp = &err_s; } *errp = LCB_SUCCESS; if (!LCBT_VBCONFIG(instance)) { *errp = LCB_CLIENT_ETMPFAIL; return NULL; } dset = calloc(1, sizeof(*dset)); if (!dset) { *errp = LCB_CLIENT_ENOMEM; return NULL; } /* Ensure we don't clobber options from older versions */ dset->opts.cap_max = opts_in->cap_max; dset->opts.check_delete = opts_in->check_delete; dset->opts.interval = opts_in->interval; dset->opts.persist_to = opts_in->persist_to; dset->opts.replicate_to = opts_in->replicate_to; dset->opts.timeout = opts_in->timeout; if (options->version > 0) { dset->opts.pollopts = opts_in->pollopts; } dset->opts.pollopts = get_poll_meth(instance, &dset->opts); dset->instance = instance; dset->mctx.addcmd = dset_ctx_add; dset->mctx.done = dset_ctx_schedule; dset->mctx.fail = dset_ctx_fail; if (!DSET_OPTFLD(dset, timeout)) { DSET_OPTFLD(dset, timeout) = LCBT_SETTING(instance, durability_timeout); } if (!DSET_OPTFLD(dset, interval)) { DSET_OPTFLD(dset, interval) = LCBT_SETTING(instance, durability_interval); } *errp = lcb_durability_validate(instance, &dset->opts.persist_to, &dset->opts.replicate_to, dset->opts.cap_max ? LCB_DURABILITY_VALIDATE_CAPMAX : 0); if (*errp != LCB_SUCCESS) { free(dset); return NULL; } dset->timer = io->timer.create(io->p); lcb_string_init(&dset->kvbufs); return &dset->mctx; }
LIBCOUCHBASE_API lcb_error_t lcb_durability_poll(lcb_t instance, const void *cookie, const lcb_durability_opts_t *options, lcb_size_t ncmds, const lcb_durability_cmd_t *const *cmds) { hrtime_t now = gethrtime(); lcb_durability_set_t *dset; lcb_size_t ii; lcb_io_opt_t io = instance->settings.io; if (!ncmds) { return LCB_EINVAL; } dset = calloc(1, sizeof(*dset)); if (!dset) { return LCB_CLIENT_ENOMEM; } dset->opts = *options; dset->instance = instance; if (!DSET_OPTFLD(dset, timeout)) { DSET_OPTFLD(dset, timeout) = instance->settings.durability_timeout; } if (-1 == verify_critera(instance, dset)) { free(dset); return LCB_DURABILITY_ETOOMANY; } /* set our timeouts now */ dset->us_timeout = (lcb_uint32_t)(now / 1000) + DSET_OPTFLD(dset, timeout); dset->timer = io->v.v0.create_timer(io); dset->cookie = cookie; dset->nentries = ncmds; dset->nremaining = ncmds; /** Get the timings */ if (!DSET_OPTFLD(dset, interval)) { DSET_OPTFLD(dset, interval) = LCB_DEFAULT_DURABILITY_INTERVAL; } /* list of observe commands to schedule */ if (dset->nentries == 1) { dset->entries = &dset->single.ent; dset->valid_entries = &dset->single.entp; } else { dset->ht = lcb_hashtable_nc_new(dset->nentries); dset->entries = calloc(dset->nentries, sizeof(*dset->entries)); dset->valid_entries = malloc(dset->nentries * sizeof(*dset->valid_entries)); if (dset->entries == NULL || dset->valid_entries == NULL) { lcb_durability_dset_destroy(dset); return LCB_CLIENT_ENOMEM; } } /* set up the observe commands */ for (ii = 0; ii < dset->nentries; ii++) { lcb_durability_entry_t *ent = dset->entries + ii; ent_init(cmds[ii], ent); ent->parent = dset; if (dset->nentries > 1) { int mt = genhash_update(dset->ht, REQFLD(ent, key), REQFLD(ent, nkey), ent, 0); if (mt != NEW) { lcb_durability_dset_destroy(dset); return LCB_DUPLICATE_COMMANDS; } } } /** * Increase the refcount by one. This will be decremented * when the remaining_total count hits 0 */ dset_ref(dset); hashset_add(instance->durability_polls, dset); timer_schedule(dset, 0, STATE_OBSPOLL); return lcb_synchandler_return(instance, LCB_SUCCESS); }
/** * Observe callback. Called internally by libcouchbase's observe handlers */ void lcb_durability_dset_update(lcb_t instance, lcb_durability_set_t *dset, lcb_error_t err, const lcb_observe_resp_t *resp) { lcb_durability_entry_t *ent; /** * So we have two counters to decrement. One is the global 'done' counter * and the other is the iteration counter. * * The iteration counter is only decremented when we receive a NULL signal * in the callback, whereas the global counter is decremented once, whenever * the entry's criteria have been satisfied */ if (resp->v.v0.key == NULL) { dset_done_waiting(dset); return; } if (dset->nentries == 1) { ent = &dset->single.ent; } else { ent = genhash_find(dset->ht, resp->v.v0.key, resp->v.v0.nkey); } if (ent->done) { /* ignore subsequent errors */ return; } if (err != LCB_SUCCESS) { RESFLD(ent, err) = err; return; } RESFLD(ent, nresponses)++; if (resp->v.v0.cas && resp->v.v0.from_master) { RESFLD(ent, cas) = resp->v.v0.cas; if (REQFLD(ent, cas) && REQFLD(ent, cas) != resp->v.v0.cas) { RESFLD(ent, err) = LCB_KEY_EEXISTS; ent_set_resdone(ent); return; } } if (DSET_OPTFLD(ent->parent, check_delete)) { check_negative_durability(ent, resp); } else { check_positive_durability(ent, resp); } if (ent_is_complete(ent)) { /* clear any transient errors */ RESFLD(ent, err) = LCB_SUCCESS; ent_set_resdone(ent); } (void)instance; }
/** * Observe callback. Called internally by libcouchbase's observe handlers */ void lcb_durability_dset_update(lcb_t instance, lcb_DURSET *dset, lcb_error_t err, const lcb_RESPOBSERVE *resp) { lcb_DURITEM *ent; /** * So we have two counters to decrement. One is the global 'done' counter * and the other is the iteration counter. * * The iteration counter is only decremented when we receive a NULL signal * in the callback, whereas the global counter is decremented once, whenever * the entry's criteria have been satisfied */ if (resp->key == NULL) { dset_done_waiting(dset); return; } if (dset->nentries == 1) { ent = &dset->single.ent; } else { ent = genhash_find(dset->ht, resp->key, resp->nkey); } if (ent->done) { /* ignore subsequent errors */ return; } if (err != LCB_SUCCESS) { RESFLD(ent, rc) = err; /* If it's a non-scheduling error then the item will be retried in the * next iteration */ if (err == LCB_SCHEDFAIL_INTERNAL) { ent_set_resdone(ent); } return; } RESFLD(ent, nresponses)++; if (resp->cas && resp->ismaster) { RESFLD(ent, cas) = resp->cas; if (ent->reqcas && ent->reqcas != resp->cas) { RESFLD(ent, rc) = LCB_KEY_EEXISTS; ent_set_resdone(ent); return; } } if (DSET_OPTFLD(ent->parent, check_delete)) { check_negative_durability(ent, resp); } else { check_positive_durability(ent, resp); } if (ent_is_complete(ent)) { /* clear any transient errors */ RESFLD(ent, rc) = LCB_SUCCESS; ent_set_resdone(ent); } (void)instance; }