OK, let's follow the request and see what is performing a file mutex lock. [ 5845.725268] lockd: request from 192.168.1.210, port=860 This is from the main lockd kernel thread function. static void lockd (...) in svc.c. It invokes svc_process(). [ 5845.725272] lockd: LOCK called Via some xdr magic, preprocessor, and function lookup table, our main handler function nlmsvc_proc_lock (...) from svcproc.c is called. [ 5845.725274] lockd: nlm_lookup_host(192.168.1.210, p=6, v=4, my role=server, name=romita) [ 5845.725504] lockd: get host romita [ 5845.725506] lockd: found host in cache nlmsvc_proc_lock (...) invokes nlmsvc_retrieve_args (...) also in svcproc.c to get/parse some args, including the host. In this case, the host is found in the cache. [ 5845.725507] lockd: nsm_monitor(romita) nlmsvc_retrieve_args (...) also monitors the host in some way that isn't clear to me yet. It doesn't appear to be related to our problem, so that can be put aside for now. [ 5845.725509] lockd: nlm_file_lookup (01070001 00288001 00000000 926e57da d142d9c6 dabb48bd c2a30bcf 0028c75d) nlmsvc_retrieve_args (...) also does a file lookup by calling nlm_lookup_file(...) This debug is from nlm_lookup_file (even though it says nlm_file_lookup). We take out the file table mutex here, but not the file-specific mutex. We initialise the file mutex here, so from this point onwards we need to be looking out for file specific locks. [ 5845.725789] lockd: found file f7aa2840 (count 0) [ 5845.725792] lockd: nlmsvc_lock(sda1/2672477, ty=0, pi=80357, 1073741826-1073742335, bl=0) Now nlmsvc_proc_lock (...) calls nlmsvc_lock (...) from svclock.c to do the actual locking. Very first thing, right after the debug, this takes out the mutex on the file... /* Lock file against concurrent access */ mutex_lock(&file->f_mutex); The corresponding... mutex_unlock(&file->f_mutex); ...is right down the bottom of nlmsvc_lock (...)... out: mutex_unlock(&file->f_mutex); nlmsvc_release_block(block); dprintk("lockd: nlmsvc_lock returned %u\n", ret); ...but we don't get that far. I think we've found it then. But let's carry on... [ 5845.725806] lockd: nlmsvc_lookup_block f=f7aa2840 pd=80357 1073741826-1073742335 ty=0 The call from nlmsvc_lock (...) to nlmsvc_lookup_block (...) is right after the file-specific mutex lock is taken out. We don't find an existing block, so nlmsvc_lock (...) creates a new one by calling nlmsvc_create_block (...). [ 5845.725809] lockd: nlm_lookup_host(192.168.1.210, p=6, v=4, my role=server, name=romita) nlmsvc_create_block (...) calls nlmsvc_lookup_host (...) ... [ 5845.726186] lockd: host garbage collection ...which decides it's time to take out the trash. [ 5845.726188] lockd: nlmsvc_mark_resources [ 5845.726189] lockd: nlm_traverse_files [ 5845.726255] lockd: mutex acquired, checking 128 file hash entries [ 5845.726257] lockd: got entry in list 29 [ 5845.726259] lockd: inspecting file f=f7afd480 [ 5845.726260] lockd: traverse blocks [ 5845.726262] lockd: locking file mutex ...which goes through all the files fine, until it comes to the specific file for which we are currently serving the request... and hey presto. Two mutexes are not better than one.