Skip to content

Commit 2bbfed9

Browse files
trondmyJ. Bruce Fields
authored andcommitted
nfsd: Fix races between nfsd4_cb_release() and nfsd4_shutdown_callback()
When we're destroying the client lease, and we call nfsd4_shutdown_callback(), we must ensure that we do not return before all outstanding callbacks have terminated and have released their payloads. Signed-off-by: Trond Myklebust <[email protected]> Signed-off-by: J. Bruce Fields <[email protected]>
1 parent 12357f1 commit 2bbfed9

File tree

2 files changed

+59
-9
lines changed

2 files changed

+59
-9
lines changed

fs/nfsd/nfs4callback.c

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,31 @@ static int max_cb_time(struct net *net)
826826
return max(nn->nfsd4_lease/10, (time_t)1) * HZ;
827827
}
828828

829+
static struct workqueue_struct *callback_wq;
830+
831+
static bool nfsd4_queue_cb(struct nfsd4_callback *cb)
832+
{
833+
return queue_work(callback_wq, &cb->cb_work);
834+
}
835+
836+
static void nfsd41_cb_inflight_begin(struct nfs4_client *clp)
837+
{
838+
atomic_inc(&clp->cl_cb_inflight);
839+
}
840+
841+
static void nfsd41_cb_inflight_end(struct nfs4_client *clp)
842+
{
843+
844+
if (atomic_dec_and_test(&clp->cl_cb_inflight))
845+
wake_up_var(&clp->cl_cb_inflight);
846+
}
847+
848+
static void nfsd41_cb_inflight_wait_complete(struct nfs4_client *clp)
849+
{
850+
wait_var_event(&clp->cl_cb_inflight,
851+
!atomic_read(&clp->cl_cb_inflight));
852+
}
853+
829854
static const struct cred *get_backchannel_cred(struct nfs4_client *clp, struct rpc_clnt *client, struct nfsd4_session *ses)
830855
{
831856
if (clp->cl_minorversion == 0) {
@@ -937,14 +962,21 @@ static void nfsd4_cb_probe_done(struct rpc_task *task, void *calldata)
937962
clp->cl_cb_state = NFSD4_CB_UP;
938963
}
939964

965+
static void nfsd4_cb_probe_release(void *calldata)
966+
{
967+
struct nfs4_client *clp = container_of(calldata, struct nfs4_client, cl_cb_null);
968+
969+
nfsd41_cb_inflight_end(clp);
970+
971+
}
972+
940973
static const struct rpc_call_ops nfsd4_cb_probe_ops = {
941974
/* XXX: release method to ensure we set the cb channel down if
942975
* necessary on early failure? */
943976
.rpc_call_done = nfsd4_cb_probe_done,
977+
.rpc_release = nfsd4_cb_probe_release,
944978
};
945979

946-
static struct workqueue_struct *callback_wq;
947-
948980
/*
949981
* Poke the callback thread to process any updates to the callback
950982
* parameters, and send a null probe.
@@ -1004,6 +1036,16 @@ static void nfsd41_cb_release_slot(struct nfsd4_callback *cb)
10041036
}
10051037
}
10061038

1039+
static void nfsd41_destroy_cb(struct nfsd4_callback *cb)
1040+
{
1041+
struct nfs4_client *clp = cb->cb_clp;
1042+
1043+
nfsd41_cb_release_slot(cb);
1044+
if (cb->cb_ops && cb->cb_ops->release)
1045+
cb->cb_ops->release(cb);
1046+
nfsd41_cb_inflight_end(clp);
1047+
}
1048+
10071049
/*
10081050
* TODO: cb_sequence should support referring call lists, cachethis, multiple
10091051
* slots, and mark callback channel down on communication errors.
@@ -1101,8 +1143,10 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback
11011143
ret = false;
11021144
goto out;
11031145
need_restart:
1104-
task->tk_status = 0;
1105-
cb->cb_need_restart = true;
1146+
if (!test_bit(NFSD4_CLIENT_CB_KILL, &clp->cl_flags)) {
1147+
task->tk_status = 0;
1148+
cb->cb_need_restart = true;
1149+
}
11061150
return false;
11071151
}
11081152

@@ -1144,9 +1188,9 @@ static void nfsd4_cb_release(void *calldata)
11441188
struct nfsd4_callback *cb = calldata;
11451189

11461190
if (cb->cb_need_restart)
1147-
nfsd4_run_cb(cb);
1191+
nfsd4_queue_cb(cb);
11481192
else
1149-
cb->cb_ops->release(cb);
1193+
nfsd41_destroy_cb(cb);
11501194

11511195
}
11521196

@@ -1180,6 +1224,7 @@ void nfsd4_shutdown_callback(struct nfs4_client *clp)
11801224
*/
11811225
nfsd4_run_cb(&clp->cl_cb_null);
11821226
flush_workqueue(callback_wq);
1227+
nfsd41_cb_inflight_wait_complete(clp);
11831228
}
11841229

11851230
/* requires cl_lock: */
@@ -1265,8 +1310,7 @@ nfsd4_run_cb_work(struct work_struct *work)
12651310
clnt = clp->cl_cb_client;
12661311
if (!clnt) {
12671312
/* Callback channel broken, or client killed; give up: */
1268-
if (cb->cb_ops && cb->cb_ops->release)
1269-
cb->cb_ops->release(cb);
1313+
nfsd41_destroy_cb(cb);
12701314
return;
12711315
}
12721316

@@ -1275,6 +1319,7 @@ nfsd4_run_cb_work(struct work_struct *work)
12751319
*/
12761320
if (!cb->cb_ops && clp->cl_minorversion) {
12771321
clp->cl_cb_state = NFSD4_CB_UP;
1322+
nfsd41_destroy_cb(cb);
12781323
return;
12791324
}
12801325

@@ -1300,5 +1345,9 @@ void nfsd4_init_cb(struct nfsd4_callback *cb, struct nfs4_client *clp,
13001345

13011346
void nfsd4_run_cb(struct nfsd4_callback *cb)
13021347
{
1303-
queue_work(callback_wq, &cb->cb_work);
1348+
struct nfs4_client *clp = cb->cb_clp;
1349+
1350+
nfsd41_cb_inflight_begin(clp);
1351+
if (!nfsd4_queue_cb(cb))
1352+
nfsd41_cb_inflight_end(clp);
13041353
}

fs/nfsd/state.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ struct nfs4_client {
367367
struct net *net;
368368
struct list_head async_copies; /* list of async copies */
369369
spinlock_t async_lock; /* lock for async copies */
370+
atomic_t cl_cb_inflight; /* Outstanding callbacks */
370371
};
371372

372373
/* struct nfs4_client_reset

0 commit comments

Comments
 (0)