/** * @brief Decrement the call path refcnt on a cache entry. * * We assert req->rq_u1 now points to the corresonding duplicate request * cache entry (dv). * * In the common case, a refcnt of 0 indicates that dv is cached. If * also dv->state == DUPREQ_DELETED, the request entry has been discarded * and should be destroyed here. * * @param[in] req The svc_req structure. * @param[in] func The function descriptor for this request type */ void nfs_dupreq_rele(struct svc_req *req, const nfs_function_desc_t *func) { dupreq_entry_t *dv = (dupreq_entry_t *) req->rq_u1; /* no-cache cleanup */ if (dv == (void *)DUPREQ_NOCACHE) { LogFullDebug(COMPONENT_DUPREQ, "releasing no-cache res %p", req->rq_u2); func->free_function(req->rq_u2); free_nfs_res(req->rq_u2); goto out; } pthread_mutex_lock(&dv->mtx); LogFullDebug(COMPONENT_DUPREQ, "releasing dv=%p xid=%u on DRC=%p state=%s, " "refcnt=%d", dv, dv->hin.tcp.rq_xid, dv->hin.drc, dupreq_state_table[dv->state], dv->refcnt); (dv->refcnt)--; if (dv->refcnt == 0) { if (dv->state == DUPREQ_DELETED) { pthread_mutex_unlock(&dv->mtx); /* deep free */ nfs_dupreq_free_dupreq(dv); return; } } pthread_mutex_unlock(&dv->mtx); out: /* dispose RPC header */ if (req->rq_auth) SVCAUTH_RELEASE(req->rq_auth, req); /* XXX */ if (req->rq_rtaddr.len) mem_free(req->rq_rtaddr.buf, req->rq_rtaddr.len); (void)free_rpc_msg(req->rq_msg); return; }
/** * @brief Start a duplicate request transaction * * Finds any matching request entry in the cache, if one exists, else * creates one in the START state. On any non-error return, the refcnt * of the corresponding entry is incremented. * * @param[in] reqnfs The NFS request data * @param[in] req The request to be cached * * @retval DUPREQ_SUCCESS if successful. * @retval DUPREQ_INSERT_MALLOC_ERROR if an error occured during insertion. */ dupreq_status_t nfs_dupreq_start(nfs_request_t *reqnfs, struct svc_req *req) { dupreq_status_t status = DUPREQ_SUCCESS; dupreq_entry_t *dv, *dk = NULL; bool release_dk = true; nfs_res_t *res = NULL; drc_t *drc; /* Disabled? */ if (nfs_param.core_param.drc.disabled) { req->rq_u1 = (void *)DUPREQ_NOCACHE; res = alloc_nfs_res(); goto out; } req->rq_u1 = (void *)DUPREQ_BAD_ADDR1; req->rq_u2 = (void *)DUPREQ_BAD_ADDR1; drc = nfs_dupreq_get_drc(req); if (!drc) { status = DUPREQ_INSERT_MALLOC_ERROR; goto out; } switch (drc->type) { case DRC_TCP_V4: if (reqnfs->funcdesc->service_function == nfs4_Compound) { if (!nfs_dupreq_v4_cacheable(reqnfs)) { /* for such requests, we merely thread * the request through for later * cleanup--all v41 caching is handled * by the v41 slot reply cache */ req->rq_u1 = (void *)DUPREQ_NOCACHE; res = alloc_nfs_res(); goto out; } } break; default: /* likewise for other protocol requests we may not or choose not * to cache */ if (!(reqnfs->funcdesc->dispatch_behaviour & CAN_BE_DUP)) { req->rq_u1 = (void *)DUPREQ_NOCACHE; res = alloc_nfs_res(); goto out; } break; } dk = alloc_dupreq(); if (dk == NULL) { release_dk = false; status = DUPREQ_ERROR; goto release_dk; } dk->hin.drc = drc; /* trans. call path ref to dv */ switch (drc->type) { case DRC_TCP_V4: case DRC_TCP_V3: dk->hin.tcp.rq_xid = req->rq_xid; /* XXX needed? */ dk->hin.rq_prog = req->rq_prog; dk->hin.rq_vers = req->rq_vers; dk->hin.rq_proc = req->rq_proc; break; case DRC_UDP_V234: dk->hin.tcp.rq_xid = req->rq_xid; if (unlikely(!copy_xprt_addr(&dk->hin.addr, req->rq_xprt))) { status = DUPREQ_INSERT_MALLOC_ERROR; goto release_dk; } dk->hin.rq_prog = req->rq_prog; dk->hin.rq_vers = req->rq_vers; dk->hin.rq_proc = req->rq_proc; break; default: /* error */ status = DUPREQ_ERROR; goto release_dk; } /* TI-RPC computed checksum */ dk->hk = req->rq_cksum; dk->state = DUPREQ_START; dk->timestamp = time(NULL); { struct opr_rbtree_node *nv; struct rbtree_x_part *t = rbtx_partition_of_scalar(&drc->xt, dk->hk); PTHREAD_MUTEX_lock(&t->mtx); /* partition lock */ nv = rbtree_x_cached_lookup(&drc->xt, t, &dk->rbt_k, dk->hk); if (nv) { /* cached request */ dv = opr_containerof(nv, dupreq_entry_t, rbt_k); PTHREAD_MUTEX_lock(&dv->mtx); if (unlikely(dv->state == DUPREQ_START)) { status = DUPREQ_BEING_PROCESSED; } else { /* satisfy req from the DRC, incref, extend window */ res = dv->res; PTHREAD_MUTEX_lock(&drc->mtx); drc_inc_retwnd(drc); PTHREAD_MUTEX_unlock(&drc->mtx); status = DUPREQ_EXISTS; (dv->refcnt)++; } LogDebug(COMPONENT_DUPREQ, "dupreq hit dk=%p, dk xid=%u cksum %" PRIu64 " state=%s", dk, dk->hin.tcp.rq_xid, dk->hk, dupreq_state_table[dk->state]); req->rq_u1 = dv; PTHREAD_MUTEX_unlock(&dv->mtx); } else { /* new request */ res = req->rq_u2 = dk->res = alloc_nfs_res(); (void)rbtree_x_cached_insert(&drc->xt, t, &dk->rbt_k, dk->hk); (dk->refcnt)++; /* add to q tail */ PTHREAD_MUTEX_lock(&drc->mtx); TAILQ_INSERT_TAIL(&drc->dupreq_q, dk, fifo_q); ++(drc->size); PTHREAD_MUTEX_unlock(&drc->mtx); req->rq_u1 = dk; release_dk = false; dv = dk; } PTHREAD_MUTEX_unlock(&t->mtx); } LogFullDebug(COMPONENT_DUPREQ, "starting dv=%p xid=%u on DRC=%p state=%s, status=%s, refcnt=%d", dv, dk->hin.tcp.rq_xid, drc, dupreq_state_table[dv->state], dupreq_status_table[status], dv->refcnt); release_dk: if (release_dk) nfs_dupreq_free_dupreq(dk); nfs_dupreq_put_drc(req->rq_xprt, drc, DRC_FLAG_NONE); /* dk ref */ out: if (res) reqnfs->res_nfs = req->rq_u2 = res; return status; }
/** * @brief Completes a request in the cache * * Completes a cache insertion operation begun in nfs_dupreq_start. * The refcnt of the corresponding duplicate request entry is unchanged * (ie, the caller must still call nfs_dupreq_rele). * * In contrast with the prior DRC implementation, completing a request * in the current implementation may under normal conditions cause one * or more cached requests to be retired. Requests are retired in the * order they were inserted. The primary retire algorithm is a high * water mark, and a windowing heuristic. One or more requests will be * retired if the water mark/timeout is exceeded, and if a no duplicate * requests have been found in the cache in a configurable window of * immediately preceding requests. A timeout may supplement the water mark, * in future. * * req->rq_u1 has either a magic value, or points to a duplicate request * cache entry allocated in nfs_dupreq_start. * * @param[in] req The request * @param[in] res_nfs The response * * @return DUPREQ_SUCCESS if successful. * @return DUPREQ_INSERT_MALLOC_ERROR if an error occured. */ dupreq_status_t nfs_dupreq_finish(struct svc_req *req, nfs_res_t *res_nfs) { dupreq_entry_t *ov = NULL, *dv = (dupreq_entry_t *)req->rq_u1; dupreq_status_t status = DUPREQ_SUCCESS; struct rbtree_x_part *t; drc_t *drc = NULL; /* do nothing if req is marked no-cache */ if (dv == (void *)DUPREQ_NOCACHE) goto out; /* do nothing if nfs_dupreq_start failed completely */ if (dv == (void *)DUPREQ_BAD_ADDR1) goto out; PTHREAD_MUTEX_lock(&dv->mtx); dv->res = res_nfs; dv->timestamp = time(NULL); dv->state = DUPREQ_COMPLETE; drc = dv->hin.drc; PTHREAD_MUTEX_unlock(&dv->mtx); /* cond. remove from q head */ PTHREAD_MUTEX_lock(&drc->mtx); LogFullDebug(COMPONENT_DUPREQ, "completing dv=%p xid=%u on DRC=%p state=%s, status=%s, refcnt=%d", dv, dv->hin.tcp.rq_xid, drc, dupreq_state_table[dv->state], dupreq_status_table[status], dv->refcnt); /* ok, do the new retwnd calculation here. then, put drc only if * we retire an entry */ if (drc_should_retire(drc)) { /* again: */ ov = TAILQ_FIRST(&drc->dupreq_q); if (likely(ov)) { /* finished request count against retwnd */ drc_dec_retwnd(drc); /* check refcnt */ if (ov->refcnt > 0) { /* ov still in use, apparently */ goto unlock; } /* remove q entry */ TAILQ_REMOVE(&drc->dupreq_q, ov, fifo_q); --(drc->size); /* remove dict entry */ t = rbtx_partition_of_scalar(&drc->xt, ov->hk); /* interlock */ PTHREAD_MUTEX_unlock(&drc->mtx); PTHREAD_MUTEX_lock(&t->mtx); /* partition lock */ rbtree_x_cached_remove(&drc->xt, t, &ov->rbt_k, ov->hk); PTHREAD_MUTEX_unlock(&t->mtx); LogDebug(COMPONENT_DUPREQ, "retiring ov=%p xid=%u on DRC=%p state=%s, status=%s, refcnt=%d", ov, ov->hin.tcp.rq_xid, ov->hin.drc, dupreq_state_table[dv->state], dupreq_status_table[status], ov->refcnt); /* deep free ov */ nfs_dupreq_free_dupreq(ov); goto out; } } unlock: PTHREAD_MUTEX_unlock(&drc->mtx); out: return status; }