static void store_callback(lcb_t instance,
                           const void *cookie,
                           lcb_storage_t op,
                           lcb_error_t err,
                           const lcb_store_resp_t *resp)
{
    pycbc_ConnectionObject *conn;
    pycbc_OperationResultObject *res;
    pycbc_MultiResultObject *mres;
    int rv;

    rv = get_common_objects((PyObject*)cookie,
                            resp->v.v0.key,
                            resp->v.v0.nkey,
                            err,
                            &conn,
                            (pycbc_ResultBaseObject**)&res,
                            RESTYPE_OPERATION,
                            &mres);

    if (rv == -1) {
        CB_THR_BEGIN(conn);
        return;
    }

    res->rc = err;
    res->cas = resp->v.v0.cas;
    maybe_push_operr(mres, (pycbc_ResultBaseObject*)res, err, 0);
    CB_THR_BEGIN(conn);

    (void)instance;
    (void)op;
}
static void stat_callback(lcb_t instance,
                          const void *cookie,
                          lcb_error_t err,
                          const lcb_server_stat_resp_t *resp)
{
    pycbc_MultiResultObject *mres;
    PyObject *value;
    PyObject *skey, *knodes;


    mres = (pycbc_MultiResultObject*)cookie;
    CB_THR_END(mres->parent);

    if (err != LCB_SUCCESS) {
        if (mres->errop == NULL) {
            pycbc_ResultBaseObject *res =
                    (pycbc_ResultBaseObject*)pycbc_result_new(mres->parent);
            res->rc = err;
            res->key = Py_None; Py_INCREF(res->key);
            maybe_push_operr(mres, res, err, 0);
        }
        CB_THR_BEGIN(mres->parent);
        return;
    }

    if (!resp->v.v0.server_endpoint) {
        CB_THR_BEGIN(mres->parent);
        return;
    }

    skey = pycbc_SimpleStringN(resp->v.v0.key, resp->v.v0.nkey);
    value = pycbc_SimpleStringN(resp->v.v0.bytes, resp->v.v0.nbytes);
    {
        PyObject *intval = pycbc_maybe_convert_to_int(value);
        if (intval) {
            Py_DECREF(value);
            value = intval;

        } else {
            PyErr_Clear();
        }
    }

    knodes = PyDict_GetItem((PyObject*)mres, skey);
    if (!knodes) {
        knodes = PyDict_New();
        PyDict_SetItem((PyObject*)mres, skey, knodes);
        Py_DECREF(knodes);
    }

    PyDict_SetItemString(knodes, resp->v.v0.server_endpoint, value);

    Py_DECREF(skey);
    Py_DECREF(value);

    CB_THR_BEGIN(mres->parent);
    (void)instance;
}
static void
get_callback(lcb_t instance,
             const void *cookie,
             lcb_error_t err,
             const lcb_get_resp_t *resp)
{

    int rv;
    pycbc_Connection *conn = NULL;
    pycbc_ValueResult *res = NULL;
    pycbc_MultiResult *mres = NULL;
    lcb_uint32_t eflags;

    rv = get_common_objects((PyObject*)cookie,
                            resp->v.v0.key,
                            resp->v.v0.nkey,
                            err,
                            &conn,
                            (pycbc_Result**)&res,
                            RESTYPE_VALUE,
                            &mres);

    if (rv < 0) {
        CB_THR_BEGIN(conn);
        return;
    }

    res->flags = resp->v.v0.flags;
    res->cas = resp->v.v0.cas;

    maybe_push_operr(mres, (pycbc_Result*)res, err, 1);

    if (err != LCB_SUCCESS) {
        CB_THR_BEGIN(conn);
        return;
    }

    if (mres->mropts & PYCBC_MRES_F_FORCEBYTES) {
        eflags = PYCBC_FMT_BYTES;
    } else {
        eflags = resp->v.v0.flags;
    }

    rv = pycbc_tc_decode_value(mres->parent,
                               resp->v.v0.bytes,
                               resp->v.v0.nbytes,
                               eflags,
                               &res->value);
    if (rv < 0) {
        push_fatal_error(mres);
    }

    CB_THR_BEGIN(conn);
    (void)instance;
}
static void
keyop_simple_callback(lcb_t instance, int cbtype, const lcb_RESPBASE *resp)
{
    int rv;
    int optflags = RESTYPE_OPERATION;
    pycbc_Bucket *conn = NULL;
    pycbc_OperationResult *res = NULL;
    pycbc_MultiResult *mres = NULL;

    if (cbtype == LCB_CALLBACK_ENDURE) {
        optflags |= RESTYPE_EXISTS_OK;
    }

    rv = get_common_objects(resp, &conn, (pycbc_Result**)&res, optflags, &mres);

    if (rv == 0) {
        res->rc = resp->rc;
        maybe_push_operr(mres, (pycbc_Result*)res, resp->rc, 0);
    }
    if (resp->cas) {
        res->cas = resp->cas;
    }

    operation_completed(conn, mres);
    CB_THR_BEGIN(conn);
    (void)instance;

}
static void
subdoc_callback(lcb_t instance, int cbtype, const lcb_RESPBASE *rb)
{
    int rv;
    pycbc_Bucket *conn;
    pycbc__SDResult *res;
    pycbc_MultiResult *mres;
    lcb_SDENTRY cur;
    size_t vii = 0, oix = 0;
    const lcb_RESPSUBDOC *resp = (const lcb_RESPSUBDOC *)rb;

    rv = get_common_objects(rb, &conn,
        (pycbc_Result**)&res, RESTYPE_EXISTS_OK, &mres);
    if (rv < 0) {
        goto GT_ERROR;
    }

    if (rb->rc == LCB_SUCCESS || rb->rc == LCB_SUBDOC_MULTI_FAILURE) {
        res->cas = rb->cas;
    } else {
        maybe_push_operr(mres, (pycbc_Result*)res, rb->rc, 0);
        goto GT_ERROR;
    }

    while ((lcb_sdresult_next(resp, &cur, &vii))) {
        size_t cur_index;
        PyObject *cur_tuple = mk_sd_tuple(&cur);

        if (cbtype == LCB_CALLBACK_SDMUTATE) {
            cur_index = cur.index;
        } else {
            cur_index = oix++;
        }

        if (cur_tuple == NULL) {
            pycbc_multiresult_adderr(mres);
            goto GT_ERROR;
        }

        if (cur.status != LCB_SUCCESS) {
            if (cbtype == LCB_CALLBACK_SDMUTATE) {
                mk_sd_error(res, mres, cur.status, cur_index);
            } else if (cur.status != LCB_SUBDOC_PATH_ENOENT) {
                mk_sd_error(res, mres, cur.status, cur_index);
            }
        }

        pycbc_sdresult_addresult(res, cur_index, cur_tuple);
        Py_DECREF(cur_tuple);
    }
    if (rb->rc == LCB_SUCCESS) {
        dur_chain2(conn, mres, (pycbc_OperationResult*)res, cbtype, (const lcb_RESPBASE*)resp);
        return;
    }

    GT_ERROR:
    operation_completed(conn, mres);
    CB_THR_BEGIN(conn);
    (void)instance;
}
/**
 * This callback does things a bit differently.
 * Instead of using a MultiResult, we use a single HttpResult object.
 * We won't ever have "multiple" http objects.
 */
static void http_complete_callback(lcb_http_request_t req,
                                   lcb_t instance,
                                   const void *cookie,
                                   lcb_error_t err,
                                   const lcb_http_resp_t *resp)
{
    pycbc_HttpResultObject *htres = (pycbc_HttpResultObject*)cookie;
    htres->rc = err;
    htres->htcode = resp->v.v0.status;

    CB_THR_END(htres->parent);

    if (resp->v.v0.nbytes) {
        pycbc_tc_simple_decode(&htres->http_data,
                               resp->v.v0.bytes,
                               resp->v.v0.nbytes,
                               htres->format);
        if (!htres->http_data) {
            PyErr_Clear();
            htres->http_data = PyBytes_FromStringAndSize(resp->v.v0.bytes,
                                                         resp->v.v0.nbytes);
        }

    } else {
        htres->http_data = Py_None;
        Py_INCREF(Py_None);
    }

    CB_THR_BEGIN(htres->parent);
    (void)instance;
    (void)req;
}
static void touch_callback(lcb_t instance, const void *cookie,
                           lcb_error_t err,
                           const lcb_touch_resp_t *resp)
{
    int rv;
    pycbc_ConnectionObject *conn = NULL;
    pycbc_OperationResultObject *res = NULL;
    pycbc_MultiResultObject *mres = NULL;

    rv = get_common_objects((PyObject*) cookie,
                            resp->v.v0.key,
                            resp->v.v0.nkey,
                            err,
                            &conn,
                            (pycbc_ResultBaseObject**)&res,
                            RESTYPE_OPERATION,
                            &mres);
    if (rv == 0) {
        res->cas = resp->v.v0.cas;
        res->rc = err;
        maybe_push_operr(mres, (pycbc_ResultBaseObject*)res, err, 1);
    }

    CB_THR_BEGIN(conn);
    (void)instance;
}
static void arithmetic_callback(lcb_t instance, const void *cookie,
                                lcb_error_t err,
                                const lcb_arithmetic_resp_t *resp)
{
    int rv;
    pycbc_ConnectionObject *conn = NULL;
    pycbc_ValueResultObject *res = NULL;
    pycbc_MultiResultObject *mres = NULL;

    rv = get_common_objects((PyObject*)cookie,
                            resp->v.v0.key,
                            resp->v.v0.nkey,
                            err,
                            &conn,
                            (pycbc_ResultBaseObject**)&res,
                            RESTYPE_VALUE,
                            &mres);
    if (rv == 0) {
        res->cas = resp->v.v0.cas;
        res->rc = err;
        if (err == LCB_SUCCESS) {
            res->value = pycbc_IntFromULL(resp->v.v0.value);
        }

        maybe_push_operr(mres, (pycbc_ResultBaseObject*)res, err, 0);
    }

    CB_THR_BEGIN(conn);
    (void)instance;
}
static void
end_global_callback(lcb_t instance, pycbc_Bucket *self)
{
    Py_DECREF((PyObject *)(self));

    self = (pycbc_Bucket *)lcb_get_cookie(instance);
    if (self) {
        CB_THR_BEGIN(self);
    }
}
static void
observe_callback(lcb_t instance,
                 const void *cookie,
                 lcb_error_t err,
                 const lcb_observe_resp_t *resp)
{
    int rv;
    pycbc_ObserveInfo *oi;
    pycbc_Connection *conn;
    pycbc_ValueResult *vres;
    pycbc_MultiResult *mres;

    if (!resp->v.v0.key) {
        mres = (pycbc_MultiResult*)cookie;;
        maybe_breakout(mres->parent);
        return;
    }

    rv = get_common_objects((PyObject*)cookie,
                            resp->v.v0.key,
                            resp->v.v0.nkey,
                            err,
                            &conn,
                            (pycbc_Result**)&vres,
                            RESTYPE_VALUE|RESTYPE_EXISTS_OK|RESTYPE_VARCOUNT,
                            &mres);
    if (rv < 0) {
        goto GT_DONE;
    }

    if (err != LCB_SUCCESS) {
        maybe_push_operr(mres, (pycbc_Result*)vres, err, 0);
        goto GT_DONE;
    }

    if (!vres->value) {
        vres->value = PyList_New(0);
    }

    oi = pycbc_observeinfo_new(conn);
    if (oi == NULL) {
        push_fatal_error(mres);
        goto GT_DONE;
    }

    oi->from_master = resp->v.v0.from_master;
    oi->flags = resp->v.v0.status;
    oi->cas = resp->v.v0.cas;
    PyList_Append(vres->value, (PyObject*)oi);
    Py_DECREF(oi);

    GT_DONE:
    CB_THR_BEGIN(conn);
    (void)instance;
}
static void
value_callback(lcb_t instance, int cbtype, const lcb_RESPBASE *resp)
{
    int rv;
    pycbc_Bucket *conn = NULL;
    pycbc_ValueResult *res = NULL;
    pycbc_MultiResult *mres = NULL;

    rv = get_common_objects(resp, &conn, (pycbc_Result**)&res, RESTYPE_VALUE,
        &mres);

    if (rv < 0) {
        goto GT_DONE;
    }

    if (resp->rc == LCB_SUCCESS) {
        res->cas = resp->cas;
    } else {
        maybe_push_operr(mres, (pycbc_Result*)res, resp->rc,
            cbtype != LCB_CALLBACK_COUNTER);
        goto GT_DONE;
    }

    if (cbtype == LCB_CALLBACK_GET || cbtype == LCB_CALLBACK_GETREPLICA) {
        const lcb_RESPGET *gresp = (const lcb_RESPGET *)resp;
        lcb_U32 eflags;

        res->flags = gresp->itmflags;
        if (mres->mropts & PYCBC_MRES_F_FORCEBYTES) {
            eflags = PYCBC_FMT_BYTES;
        } else {
            eflags = gresp->itmflags;
        }

        rv = pycbc_tc_decode_value(mres->parent, gresp->value, gresp->nvalue,
            eflags, &res->value);
        if (rv < 0) {
            pycbc_multiresult_adderr(mres);
        }
    } else if (cbtype == LCB_CALLBACK_COUNTER) {
        const lcb_RESPCOUNTER *cresp = (const lcb_RESPCOUNTER *)resp;
        res->value = pycbc_IntFromULL(cresp->value);
    }

    GT_DONE:
    operation_completed(conn, mres);
    CB_THR_BEGIN(conn);
    (void)instance;
}
static void
observe_callback(lcb_t instance, int cbtype, const lcb_RESPBASE *resp_base)
{
    int rv;
    pycbc_ObserveInfo *oi;
    pycbc_Bucket *conn;
    pycbc_ValueResult *vres;
    pycbc_MultiResult *mres;
    const lcb_RESPOBSERVE *oresp = (const lcb_RESPOBSERVE *)resp_base;

    if (resp_base->rflags & LCB_RESP_F_FINAL) {
        mres = (pycbc_MultiResult*)resp_base->cookie;
        operation_completed(mres->parent, mres);
        return;
    }

    rv = get_common_objects(resp_base, &conn, (pycbc_Result**)&vres,
        RESTYPE_VALUE|RESTYPE_EXISTS_OK|RESTYPE_VARCOUNT, &mres);
    if (rv < 0) {
        goto GT_DONE;
    }

    if (resp_base->rc != LCB_SUCCESS) {
        maybe_push_operr(mres, (pycbc_Result*)vres, resp_base->rc, 0);
        goto GT_DONE;
    }

    if (!vres->value) {
        vres->value = PyList_New(0);
    }

    oi = pycbc_observeinfo_new(conn);
    if (oi == NULL) {
        pycbc_multiresult_adderr(mres);
        goto GT_DONE;
    }

    oi->from_master = oresp->ismaster;
    oi->flags = oresp->status;
    oi->cas = oresp->cas;
    PyList_Append(vres->value, (PyObject*)oi);
    Py_DECREF(oi);

    GT_DONE:
    CB_THR_BEGIN(conn);
    (void)instance; (void)cbtype;
}
static void
error_callback(lcb_t instance, lcb_error_t err, const char *msg)
{
    PyObject *errtuple;
    PyObject *result;
    pycbc_Connection *self = (pycbc_Connection*) lcb_get_cookie(instance);

    CB_THR_END(self);

    pycbc_assert(self->errors);
    errtuple = Py_BuildValue("(i,s)", err, msg);
    pycbc_assert(errtuple);
    result = PyObject_CallMethod(self->errors, "append", "(O)", errtuple);
    pycbc_assert(result);
    Py_DECREF(errtuple);
    Py_DECREF(result);

    CB_THR_BEGIN(self);
}
/**
 * Common handler for durability
 */
static void
durability_chain_common(lcb_t instance, int cbtype, const lcb_RESPBASE *resp)
{
    pycbc_Bucket *conn;
    pycbc_OperationResult *res = NULL;
    pycbc_MultiResult *mres;
    int restype = RESTYPE_VARCOUNT;

    if (cbtype == LCB_CALLBACK_COUNTER) {
        restype |= RESTYPE_VALUE;
    } else {
        restype |= RESTYPE_OPERATION;
    }

    if (get_common_objects(resp, &conn, (pycbc_Result**)&res, restype, &mres) != 0) {
        operation_completed(conn, mres);
        CB_THR_BEGIN(conn);
        return;
    }

    dur_chain2(conn, mres, res, cbtype, resp);
}
static void
stats_callback(lcb_t instance, int cbtype, const lcb_RESPBASE *resp_base)
{
    pycbc_MultiResult *mres;
    PyObject *value;
    PyObject *skey, *knodes;
    PyObject *mrdict;
    pycbc_Bucket *parent;
    const lcb_RESPSTATS *resp = (const lcb_RESPSTATS *)resp_base;
    int do_return = 0;

    mres = (pycbc_MultiResult*)resp->cookie;
    parent = mres->parent;
    CB_THR_END(parent);

    if (resp->rc != LCB_SUCCESS) {
        do_return = 1;
        if (mres->errop == NULL) {
            pycbc_Result *res = (pycbc_Result*)pycbc_result_new(parent);
            res->rc = resp->rc;
            res->key = Py_None; Py_INCREF(res->key);
            maybe_push_operr(mres, res, resp->rc, 0);
        }
    }
    if (resp->rflags & LCB_RESP_F_FINAL) {
        /* Note this can happen in both success and error cases! */
        do_return = 1;
        operation_completed(parent, mres);
    }
    if (do_return) {
        CB_THR_BEGIN(parent);
        return;
    }

    skey = pycbc_SimpleStringN(resp->key, resp->nkey);
    value = pycbc_SimpleStringN(resp->value, resp->nvalue);
    {
        PyObject *intval = pycbc_maybe_convert_to_int(value);
        if (intval) {
            Py_DECREF(value);
            value = intval;

        } else {
            PyErr_Clear();
        }
    }

    mrdict = pycbc_multiresult_dict(mres);
    knodes = PyDict_GetItem(mrdict, skey);
    if (!knodes) {
        knodes = PyDict_New();
        PyDict_SetItem(mrdict, skey, knodes);
    }

    PyDict_SetItemString(knodes, resp->server, value);

    Py_DECREF(skey);
    Py_DECREF(value);

    CB_THR_BEGIN(parent);
    (void)instance;
}
static void
dur_chain2(pycbc_Bucket *conn,
    pycbc_MultiResult *mres,
    pycbc_OperationResult *res, int cbtype, const lcb_RESPBASE *resp)
{
    lcb_error_t err;
    lcb_durability_opts_t dopts = { 0 };
    lcb_CMDENDURE cmd = { 0 };
    lcb_MULTICMD_CTX *mctx = NULL;
    int is_delete = cbtype == LCB_CALLBACK_REMOVE;

    res->rc = resp->rc;
    if (resp->rc == LCB_SUCCESS) {
        const lcb_MUTATION_TOKEN *mutinfo = lcb_resp_get_mutation_token(cbtype, resp);
        Py_XDECREF(res->mutinfo);

        if (mutinfo && LCB_MUTATION_TOKEN_ISVALID(mutinfo)) {
            /* Create the mutation token tuple: (vb,uuid,seqno) */
            res->mutinfo = Py_BuildValue("HKKO",
                LCB_MUTATION_TOKEN_VB(mutinfo),
                LCB_MUTATION_TOKEN_ID(mutinfo),
                LCB_MUTATION_TOKEN_SEQ(mutinfo),
                conn->bucket);
        } else {
            Py_INCREF(Py_None);
            res->mutinfo = Py_None;
        }
        res->cas = resp->cas;
    }

    /** For remove, we check quiet */
    maybe_push_operr(mres, (pycbc_Result*)res, resp->rc, is_delete ? 1 : 0);

    if ((mres->mropts & PYCBC_MRES_F_DURABILITY) == 0 || resp->rc != LCB_SUCCESS) {
        operation_completed(conn, mres);
        CB_THR_BEGIN(conn);
        return;
    }

    if (conn->dur_testhook && conn->dur_testhook != Py_None) {
        invoke_endure_test_notification(conn, (pycbc_Result *)res);
    }

    /** Setup global options */
    dopts.v.v0.persist_to = mres->dur.persist_to;
    dopts.v.v0.replicate_to = mres->dur.replicate_to;
    dopts.v.v0.timeout = conn->dur_timeout;
    dopts.v.v0.check_delete = is_delete;
    if (mres->dur.persist_to < 0 || mres->dur.replicate_to < 0) {
        dopts.v.v0.cap_max = 1;
    }

    lcb_sched_enter(conn->instance);
    mctx = lcb_endure3_ctxnew(conn->instance, &dopts, &err);
    if (mctx == NULL) {
        goto GT_DONE;
    }

    cmd.cas = resp->cas;
    LCB_CMD_SET_KEY(&cmd, resp->key, resp->nkey);
    err = mctx->addcmd(mctx, (lcb_CMDBASE*)&cmd);
    if (err != LCB_SUCCESS) {
        goto GT_DONE;
    }

    err = mctx->done(mctx, mres);
    if (err == LCB_SUCCESS) {
        mctx = NULL;
        lcb_sched_leave(conn->instance);
    }

    GT_DONE:
    if (mctx) {
        mctx->fail(mctx);
    }
    if (err != LCB_SUCCESS) {
        res->rc = err;
        maybe_push_operr(mres, (pycbc_Result*)res, err, 0);
        operation_completed(conn, mres);

    }

    CB_THR_BEGIN(conn);
}