Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

sunrpc: allow svc threads to fail initialisation cleanly

If an svc thread needs to perform some initialisation that might fail,
it has no good way to handle the failure.

Before the thread can exit it must call svc_exit_thread(), but that
requires the service mutex to be held. The thread cannot simply take
the mutex as that could deadlock if there is a concurrent attempt to
shut down all threads (which is unlikely, but not impossible).

nfsd currently call svc_exit_thread() unprotected in the unlikely event
that unshare_fs_struct() fails.

We can clean this up by introducing svc_thread_init_status() by which an
svc thread can report whether initialisation has succeeded. If it has,
it continues normally into the action loop. If it has not,
svc_thread_init_status() immediately aborts the thread.
svc_start_kthread() waits for either of these to happen, and calls
svc_exit_thread() (under the mutex) if the thread aborted.

Signed-off-by: NeilBrown <neilb@suse.de>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>

authored by

NeilBrown and committed by
Chuck Lever
3391fc92 59f3b138

+48 -6
+2
fs/lockd/svc.c
··· 124 124 struct net *net = &init_net; 125 125 struct lockd_net *ln = net_generic(net, lockd_net_id); 126 126 127 + svc_thread_init_status(rqstp, 0); 128 + 127 129 /* try_to_freeze() is called from svc_recv() */ 128 130 set_freezable(); 129 131
+2
fs/nfs/callback.c
··· 76 76 { 77 77 struct svc_rqst *rqstp = vrqstp; 78 78 79 + svc_thread_init_status(rqstp, 0); 80 + 79 81 set_freezable(); 80 82 81 83 while (!svc_thread_should_stop(rqstp))
+3 -6
fs/nfsd/nfssvc.c
··· 873 873 874 874 /* At this point, the thread shares current->fs 875 875 * with the init process. We need to create files with the 876 - * umask as defined by the client instead of init's umask. */ 877 - if (unshare_fs_struct() < 0) { 878 - printk("Unable to start nfsd thread: out of memory\n"); 879 - goto out; 880 - } 876 + * umask as defined by the client instead of init's umask. 877 + */ 878 + svc_thread_init_status(rqstp, unshare_fs_struct()); 881 879 882 880 current->fs->umask = 0; 883 881 ··· 897 899 898 900 atomic_dec(&nfsd_th_cnt); 899 901 900 - out: 901 902 /* Release the thread */ 902 903 svc_exit_thread(rqstp); 903 904 return 0;
+31
include/linux/sunrpc/svc.h
··· 21 21 #include <linux/wait.h> 22 22 #include <linux/mm.h> 23 23 #include <linux/pagevec.h> 24 + #include <linux/kthread.h> 24 25 25 26 /* 26 27 * ··· 233 232 struct net *rq_bc_net; /* pointer to backchannel's 234 233 * net namespace 235 234 */ 235 + 236 + int rq_err; /* Thread sets this to inidicate 237 + * initialisation success. 238 + */ 239 + 236 240 unsigned long bc_to_initval; 237 241 unsigned int bc_to_retries; 238 242 void ** rq_lease_breaker; /* The v4 client breaking a lease */ ··· 309 303 set_bit(RQ_VICTIM, &rqstp->rq_flags); 310 304 311 305 return test_bit(RQ_VICTIM, &rqstp->rq_flags); 306 + } 307 + 308 + /** 309 + * svc_thread_init_status - report whether thread has initialised successfully 310 + * @rqstp: the thread in question 311 + * @err: errno code 312 + * 313 + * After performing any initialisation that could fail, and before starting 314 + * normal work, each sunrpc svc_thread must call svc_thread_init_status() 315 + * with an appropriate error, or zero. 316 + * 317 + * If zero is passed, the thread is ready and must continue until 318 + * svc_thread_should_stop() returns true. If a non-zero error is passed 319 + * the call will not return - the thread will exit. 320 + */ 321 + static inline void svc_thread_init_status(struct svc_rqst *rqstp, int err) 322 + { 323 + rqstp->rq_err = err; 324 + /* memory barrier ensures assignment to error above is visible before 325 + * waitqueue_active() test below completes. 326 + */ 327 + smp_mb(); 328 + wake_up_var(&rqstp->rq_err); 329 + if (err) 330 + kthread_exit(1); 312 331 } 313 332 314 333 struct svc_deferred_req {
+10
net/sunrpc/svc.c
··· 706 706 if (!svc_init_buffer(rqstp, serv->sv_max_mesg, node)) 707 707 goto out_enomem; 708 708 709 + rqstp->rq_err = -EAGAIN; /* No error yet */ 710 + 709 711 serv->sv_nrthreads += 1; 710 712 pool->sp_nrthreads += 1; 711 713 ··· 794 792 struct svc_pool *chosen_pool; 795 793 unsigned int state = serv->sv_nrthreads-1; 796 794 int node; 795 + int err; 797 796 798 797 do { 799 798 nrservs--; ··· 817 814 818 815 svc_sock_update_bufs(serv); 819 816 wake_up_process(task); 817 + 818 + wait_var_event(&rqstp->rq_err, rqstp->rq_err != -EAGAIN); 819 + err = rqstp->rq_err; 820 + if (err) { 821 + svc_exit_thread(rqstp); 822 + return err; 823 + } 820 824 } while (nrservs > 0); 821 825 822 826 return 0;