nfsd4: don't sleep in lease-break callback
[safe/jmp/linux-2.6] / fs / nfsd / nfs4callback.c
index 4bc22c7..e078c74 100644 (file)
@@ -32,6 +32,7 @@
  */
 
 #include <linux/sunrpc/clnt.h>
+#include <linux/sunrpc/svc_xprt.h>
 #include "nfsd.h"
 #include "state.h"
 
@@ -78,11 +79,6 @@ enum nfs_cb_opnum4 {
                                        cb_sequence_dec_sz +            \
                                        op_dec_sz)
 
-struct nfs4_rpc_args {
-       void                            *args_op;
-       struct nfsd4_cb_sequence        args_seq;
-};
-
 /*
 * Generic encode routines from fs/nfs/nfs4xdr.c
 */
@@ -455,7 +451,7 @@ static struct rpc_program cb_program = {
 
 static int max_cb_time(void)
 {
-       return max(NFSD_LEASE_TIME/10, (time_t)1) * HZ;
+       return max(nfsd4_lease/10, (time_t)1) * HZ;
 }
 
 /* Reference counting, callback cleanup, etc., all look racy as heck.
@@ -514,7 +510,6 @@ static void nfsd4_cb_probe_done(struct rpc_task *task, void *calldata)
                warn_no_callback_path(clp, task->tk_status);
        else
                atomic_set(&clp->cl_cb_conn.cb_set, 1);
-       put_nfs4_client(clp);
 }
 
 static const struct rpc_call_ops nfsd4_cb_probe_ops = {
@@ -547,10 +542,8 @@ void do_probe_callback(struct nfs4_client *clp)
        status = rpc_call_async(cb->cb_client, &msg,
                                RPC_TASK_SOFT | RPC_TASK_SOFTCONN,
                                &nfsd4_cb_probe_ops, (void *)clp);
-       if (status) {
+       if (status)
                warn_no_callback_path(clp, status);
-               put_nfs4_client(clp);
-       }
 }
 
 /*
@@ -568,10 +561,6 @@ nfsd4_probe_callback(struct nfs4_client *clp)
                warn_no_callback_path(clp, status);
                return;
        }
-
-       /* the task holds a reference to the nfs4_client struct */
-       atomic_inc(&clp->cl_count);
-
        do_probe_callback(clp);
 }
 
@@ -676,7 +665,7 @@ static void nfsd4_cb_recall_done(struct rpc_task *task, void *calldata)
                break;
        default:
                /* success, or error we can't handle */
-               goto done;
+               return;
        }
        if (dp->dl_retries--) {
                rpc_delay(task, 2*HZ);
@@ -687,8 +676,6 @@ static void nfsd4_cb_recall_done(struct rpc_task *task, void *calldata)
                atomic_set(&clp->cl_cb_conn.cb_set, 0);
                warn_no_callback_path(clp, task->tk_status);
        }
-done:
-       kfree(task->tk_msg.rpc_argp);
 }
 
 static void nfsd4_cb_recall_release(void *calldata)
@@ -706,33 +693,77 @@ static const struct rpc_call_ops nfsd4_cb_recall_ops = {
        .rpc_release = nfsd4_cb_recall_release,
 };
 
+static struct workqueue_struct *callback_wq;
+
+int nfsd4_create_callback_queue(void)
+{
+       callback_wq = create_singlethread_workqueue("nfsd4_callbacks");
+       if (!callback_wq)
+               return -ENOMEM;
+       return 0;
+}
+
+void nfsd4_destroy_callback_queue(void)
+{
+       destroy_workqueue(callback_wq);
+}
+
+void nfsd4_set_callback_client(struct nfs4_client *clp, struct rpc_clnt
+*new)
+{
+       struct rpc_clnt *old = clp->cl_cb_conn.cb_client;
+
+       clp->cl_cb_conn.cb_client = new;
+       /*
+        * After this, any work that saw the old value of cb_client will
+        * be gone:
+        */
+       flush_workqueue(callback_wq);
+       /* So we can safely shut it down: */
+       if (old)
+               rpc_shutdown_client(old);
+}
+
 /*
  * called with dp->dl_count inc'ed.
  */
-void
-nfsd4_cb_recall(struct nfs4_delegation *dp)
+static void _nfsd4_cb_recall(struct nfs4_delegation *dp)
 {
        struct nfs4_client *clp = dp->dl_client;
        struct rpc_clnt *clnt = clp->cl_cb_conn.cb_client;
-       struct nfs4_rpc_args *args;
+       struct nfs4_rpc_args *args = &dp->dl_recall.cb_args;
        struct rpc_message msg = {
                .rpc_proc = &nfs4_cb_procedures[NFSPROC4_CLNT_CB_RECALL],
                .rpc_cred = callback_cred
        };
-       int status = -ENOMEM;
+       int status;
+
+       if (clnt == NULL)
+               return; /* Client is shutting down; give up. */
 
-       args = kzalloc(sizeof(*args), GFP_KERNEL);
-       if (!args)
-               goto out;
        args->args_op = dp;
        msg.rpc_argp = args;
        dp->dl_retries = 1;
        status = rpc_call_async(clnt, &msg, RPC_TASK_SOFT,
                                &nfsd4_cb_recall_ops, dp);
-out:
        if (status) {
-               kfree(args);
                put_nfs4_client(clp);
                nfs4_put_delegation(dp);
        }
 }
+
+void nfsd4_do_callback_rpc(struct work_struct *w)
+{
+       /* XXX: for now, just send off delegation recall. */
+       /* In future, generalize to handle any sort of callback. */
+       struct nfsd4_callback *c = container_of(w, struct nfsd4_callback, cb_work);
+       struct nfs4_delegation *dp = container_of(c, struct nfs4_delegation, dl_recall);
+
+       _nfsd4_cb_recall(dp);
+}
+
+
+void nfsd4_cb_recall(struct nfs4_delegation *dp)
+{
+       queue_work(callback_wq, &dp->dl_recall.cb_work);
+}