nfs41: Implement NFSv4.1 callback service process.
authorRicardo Labiaga <Ricardo.Labiaga@netapp.com>
Wed, 1 Apr 2009 13:23:08 +0000 (09:23 -0400)
committerBenny Halevy <bhalevy@panasas.com>
Wed, 17 Jun 2009 21:11:29 +0000 (14:11 -0700)
nfs41_callback_up() initializes the necessary queues and creates the new
nfs41_callback_svc thread.  This thread executes the callback service which
waits for requests to arrive on the svc_serv->sv_cb_list.

NFS41_BC_MIN_CALLBACKS is set to 1 because we expect callbacks to not
cause substantial latency.

The actual processing of the callback will be implemented as a separate patch.

There is only one NFSv4.1 callback service.  The first caller of
nfs4_callback_up() creates the service, subsequent callers increment a
reference count on the service.  The service is destroyed when the last
caller invokes nfs_callback_down().

The transport needs to hold a reference to the callback service in order
to invoke it during callback processing.  Currently this reference is only
obtained when the service is first created.  This is incorrect, since
subsequent registrations for other transports will leave the xprt->serv
pointer uninitialized, leading to an oops when a callback arrives on
the "unreferenced" transport.

This patch fixes the problem by ensuring that a reference to the service
is saved in xprt->serv, either because the service is created by this
invocation to nfs4_callback_up() or by a prior invocation.

Signed-off-by: Ricardo Labiaga <Ricardo.Labiaga@netapp.com>
Signed-off-by: Benny Halevy <bhalevy@panasas.com>
[nfs41: Add a reference to svc_serv during callback service bring up]
Signed-off-by: Ricardo Labiaga <Ricardo.Labiaga@netapp.com>
Signed-off-by: Benny Halevy <bhalevy@panasas.com>
[Type check arguments of nfs_callback_up]
Signed-off-by: Ricardo Labiaga <Ricardo.Labiaga@netapp.com>
Signed-off-by: Benny Halevy <bhalevy@panasas.com>
[nfs41: save svc_serv in nfs_callback_info]
Signed-off-by: Benny Halevy <bhalevy@panasas.com>
[Removal of ugly #ifdefs]
[nfs41: Update to removal of ugly #ifdefs]
Signed-off-by: Ricardo Labiaga <Ricardo.Labiaga@netapp.com>
Signed-off-by: Benny Halevy <bhalevy@panasas.com>
fs/nfs/callback.c
fs/nfs/callback.h

index 4b1313eda6f5c1dcc08cd782fa236a14e807d84c..470928898063448097535221bf915610b5edcfd0 100644 (file)
@@ -17,6 +17,9 @@
 #include <linux/freezer.h>
 #include <linux/kthread.h>
 #include <linux/sunrpc/svcauth_gss.h>
+#if defined(CONFIG_NFS_V4_1)
+#include <linux/sunrpc/bc_xprt.h>
+#endif
 
 #include <net/inet_sock.h>
 
@@ -28,6 +31,7 @@
 
 struct nfs_callback_data {
        unsigned int users;
+       struct svc_serv *serv;
        struct svc_rqst *rqst;
        struct task_struct *task;
 };
@@ -131,6 +135,99 @@ out_err:
        return ERR_PTR(ret);
 }
 
+#if defined(CONFIG_NFS_V4_1)
+/*
+ * The callback service for NFSv4.1 callbacks
+ */
+static int
+nfs41_callback_svc(void *vrqstp)
+{
+       struct svc_rqst *rqstp = vrqstp;
+       struct svc_serv *serv = rqstp->rq_server;
+       struct rpc_rqst *req;
+       int error;
+       DEFINE_WAIT(wq);
+
+       set_freezable();
+
+       /*
+        * FIXME: do we really need to run this under the BKL? If so, please
+        * add a comment about what it's intended to protect.
+        */
+       lock_kernel();
+       while (!kthread_should_stop()) {
+               prepare_to_wait(&serv->sv_cb_waitq, &wq, TASK_INTERRUPTIBLE);
+               spin_lock_bh(&serv->sv_cb_lock);
+               if (!list_empty(&serv->sv_cb_list)) {
+                       req = list_first_entry(&serv->sv_cb_list,
+                                       struct rpc_rqst, rq_bc_list);
+                       list_del(&req->rq_bc_list);
+                       spin_unlock_bh(&serv->sv_cb_lock);
+                       dprintk("Invoking bc_svc_process()\n");
+                       error = bc_svc_process(serv, req, rqstp);
+                       dprintk("bc_svc_process() returned w/ error code= %d\n",
+                               error);
+               } else {
+                       spin_unlock_bh(&serv->sv_cb_lock);
+                       schedule();
+               }
+               finish_wait(&serv->sv_cb_waitq, &wq);
+       }
+       unlock_kernel();
+       nfs_callback_info.task = NULL;
+       svc_exit_thread(rqstp);
+       return 0;
+}
+
+/*
+ * Bring up the NFSv4.1 callback service
+ */
+struct svc_rqst *
+nfs41_callback_up(struct svc_serv *serv, struct rpc_xprt *xprt)
+{
+       /*
+        * Save the svc_serv in the transport so that it can
+        * be referenced when the session backchannel is initialized
+        */
+       xprt->bc_serv = serv;
+
+       INIT_LIST_HEAD(&serv->sv_cb_list);
+       spin_lock_init(&serv->sv_cb_lock);
+       init_waitqueue_head(&serv->sv_cb_waitq);
+       return svc_prepare_thread(serv, &serv->sv_pools[0]);
+}
+
+static inline int nfs_minorversion_callback_svc_setup(u32 minorversion,
+               struct svc_serv *serv, struct rpc_xprt *xprt,
+               struct svc_rqst **rqstpp, int (**callback_svc)(void *vrqstp))
+{
+       if (minorversion) {
+               *rqstpp = nfs41_callback_up(serv, xprt);
+               *callback_svc = nfs41_callback_svc;
+       }
+       return minorversion;
+}
+
+static inline void nfs_callback_bc_serv(u32 minorversion, struct rpc_xprt *xprt,
+               struct nfs_callback_data *cb_info)
+{
+       if (minorversion)
+               xprt->bc_serv = cb_info->serv;
+}
+#else
+static inline int nfs_minorversion_callback_svc_setup(u32 minorversion,
+               struct svc_serv *serv, struct rpc_xprt *xprt,
+               struct svc_rqst **rqstpp, int (**callback_svc)(void *vrqstp))
+{
+       return 0;
+}
+
+static inline void nfs_callback_bc_serv(u32 minorversion, struct rpc_xprt *xprt,
+               struct nfs_callback_data *cb_info)
+{
+}
+#endif /* CONFIG_NFS_V4_1 */
+
 /*
  * Bring up the callback thread if it is not already up.
  */
@@ -141,21 +238,25 @@ int nfs_callback_up(u32 minorversion, struct rpc_xprt *xprt)
        int (*callback_svc)(void *vrqstp);
        char svc_name[12];
        int ret = 0;
+       int minorversion_setup;
 
        mutex_lock(&nfs_callback_mutex);
-       if (nfs_callback_info.users++ || nfs_callback_info.task != NULL)
+       if (nfs_callback_info.users++ || nfs_callback_info.task != NULL) {
+               nfs_callback_bc_serv(minorversion, xprt, &nfs_callback_info);
                goto out;
+       }
        serv = svc_create(&nfs4_callback_program, NFS4_CALLBACK_BUFSIZE, NULL);
        if (!serv) {
                ret = -ENOMEM;
                goto out_err;
        }
 
-       if (!minorversion) {
+       minorversion_setup =  nfs_minorversion_callback_svc_setup(minorversion,
+                                       serv, xprt, &rqstp, &callback_svc);
+       if (!minorversion_setup) {
+               /* v4.0 callback setup */
                rqstp = nfs4_callback_up(serv);
                callback_svc = nfs4_callback_svc;
-       } else {
-               BUG();  /* for now */
        }
 
        if (IS_ERR(rqstp)) {
@@ -166,6 +267,7 @@ int nfs_callback_up(u32 minorversion, struct rpc_xprt *xprt)
        svc_sock_update_bufs(serv);
 
        sprintf(svc_name, "nfsv4.%u-svc", minorversion);
+       nfs_callback_info.serv = serv;
        nfs_callback_info.rqst = rqstp;
        nfs_callback_info.task = kthread_run(callback_svc,
                                             nfs_callback_info.rqst,
@@ -173,6 +275,7 @@ int nfs_callback_up(u32 minorversion, struct rpc_xprt *xprt)
        if (IS_ERR(nfs_callback_info.task)) {
                ret = PTR_ERR(nfs_callback_info.task);
                svc_exit_thread(nfs_callback_info.rqst);
+               nfs_callback_info.serv = NULL;
                nfs_callback_info.rqst = NULL;
                nfs_callback_info.task = NULL;
                goto out_err;
@@ -205,6 +308,7 @@ void nfs_callback_down(void)
        if (nfs_callback_info.users == 0 && nfs_callback_info.task != NULL) {
                kthread_stop(nfs_callback_info.task);
                svc_exit_thread(nfs_callback_info.rqst);
+               nfs_callback_info.serv = NULL;
                nfs_callback_info.rqst = NULL;
                nfs_callback_info.task = NULL;
        }
index 9b907f408b8dbf7e3f9914c9b679b99b209bf448..29123b5604f22ed0cd6d438d1d448d470dadc360 100644 (file)
@@ -70,6 +70,13 @@ extern void nfs_callback_down(void);
 #define nfs_callback_down()    do {} while(0)
 #endif
 
+/*
+ * nfs41: Callbacks are expected to not cause substantial latency,
+ * so we limit their concurrency to 1 by setting up the maximum number
+ * of slots for the backchannel.
+ */
+#define NFS41_BC_MIN_CALLBACKS 1
+
 extern unsigned int nfs_callback_set_tcpport;
 extern unsigned short nfs_callback_tcpport;
 extern unsigned short nfs_callback_tcpport6;