diff options
author | Jeff Layton <jlayton@redhat.com> | 2009-09-21 06:47:50 -0400 |
---|---|---|
committer | Steve French <sfrench@us.ibm.com> | 2009-09-24 14:33:18 -0400 |
commit | 3bc303c254335dbd7c7012cc1760b12f1d5514d3 (patch) | |
tree | 7da17fbfd697216d9ed0ccd64ea9c03aaf3d52c1 /fs/cifs/file.c | |
parent | 48541bd3dd4739b4d574b44ea47660c88d833677 (diff) |
cifs: convert oplock breaks to use slow_work facility (try #4)
This is the fourth respin of the patch to convert oplock breaks to
use the slow_work facility.
A customer of ours was testing a backport of one of the earlier
patchsets, and hit a "Busy inodes after umount..." problem. An oplock
break job had raced with a umount, and the superblock got torn down and
its memory reused. When the oplock break job tried to dereference the
inode->i_sb, the kernel oopsed.
This patchset has the oplock break job hold an inode and vfsmount
reference until the oplock break completes. With this, there should be
no need to take a tcon reference (the vfsmount implicitly holds one
already).
Currently, when an oplock break comes in there's a chance that the
oplock break job won't occur if the allocation of the oplock_q_entry
fails. There are also some rather nasty races in the allocation and
handling these structs.
Rather than allocating oplock queue entries when an oplock break comes
in, add a few extra fields to the cifsFileInfo struct. Get rid of the
dedicated cifs_oplock_thread as well and queue the oplock break job to
the slow_work thread pool.
This approach also has the advantage that the oplock break jobs can
potentially run in parallel rather than be serialized like they are
today.
Signed-off-by: Jeff Layton <jlayton@redhat.com>
Signed-off-by: Steve French <sfrench@us.ibm.com>
Diffstat (limited to 'fs/cifs/file.c')
-rw-r--r-- | fs/cifs/file.c | 74 |
1 files changed, 72 insertions, 2 deletions
diff --git a/fs/cifs/file.c b/fs/cifs/file.c index b976cea24102..90f61786f516 100644 --- a/fs/cifs/file.c +++ b/fs/cifs/file.c | |||
@@ -30,6 +30,7 @@ | |||
30 | #include <linux/writeback.h> | 30 | #include <linux/writeback.h> |
31 | #include <linux/task_io_accounting_ops.h> | 31 | #include <linux/task_io_accounting_ops.h> |
32 | #include <linux/delay.h> | 32 | #include <linux/delay.h> |
33 | #include <linux/mount.h> | ||
33 | #include <asm/div64.h> | 34 | #include <asm/div64.h> |
34 | #include "cifsfs.h" | 35 | #include "cifsfs.h" |
35 | #include "cifspdu.h" | 36 | #include "cifspdu.h" |
@@ -51,11 +52,13 @@ static inline struct cifsFileInfo *cifs_init_private( | |||
51 | INIT_LIST_HEAD(&private_data->llist); | 52 | INIT_LIST_HEAD(&private_data->llist); |
52 | private_data->pfile = file; /* needed for writepage */ | 53 | private_data->pfile = file; /* needed for writepage */ |
53 | private_data->pInode = igrab(inode); | 54 | private_data->pInode = igrab(inode); |
55 | private_data->mnt = file->f_path.mnt; | ||
54 | private_data->invalidHandle = false; | 56 | private_data->invalidHandle = false; |
55 | private_data->closePend = false; | 57 | private_data->closePend = false; |
56 | /* Initialize reference count to one. The private data is | 58 | /* Initialize reference count to one. The private data is |
57 | freed on the release of the last reference */ | 59 | freed on the release of the last reference */ |
58 | atomic_set(&private_data->count, 1); | 60 | atomic_set(&private_data->count, 1); |
61 | slow_work_init(&private_data->oplock_break, &cifs_oplock_break_ops); | ||
59 | 62 | ||
60 | return private_data; | 63 | return private_data; |
61 | } | 64 | } |
@@ -327,7 +330,7 @@ int cifs_open(struct inode *inode, struct file *file) | |||
327 | le64_to_cpu(tcon->fsUnixInfo.Capability))) { | 330 | le64_to_cpu(tcon->fsUnixInfo.Capability))) { |
328 | int oflags = (int) cifs_posix_convert_flags(file->f_flags); | 331 | int oflags = (int) cifs_posix_convert_flags(file->f_flags); |
329 | /* can not refresh inode info since size could be stale */ | 332 | /* can not refresh inode info since size could be stale */ |
330 | rc = cifs_posix_open(full_path, &inode, inode->i_sb, | 333 | rc = cifs_posix_open(full_path, &inode, file->f_path.mnt, |
331 | cifs_sb->mnt_file_mode /* ignored */, | 334 | cifs_sb->mnt_file_mode /* ignored */, |
332 | oflags, &oplock, &netfid, xid); | 335 | oflags, &oplock, &netfid, xid); |
333 | if (rc == 0) { | 336 | if (rc == 0) { |
@@ -547,7 +550,7 @@ reopen_error_exit: | |||
547 | le64_to_cpu(tcon->fsUnixInfo.Capability))) { | 550 | le64_to_cpu(tcon->fsUnixInfo.Capability))) { |
548 | int oflags = (int) cifs_posix_convert_flags(file->f_flags); | 551 | int oflags = (int) cifs_posix_convert_flags(file->f_flags); |
549 | /* can not refresh inode info since size could be stale */ | 552 | /* can not refresh inode info since size could be stale */ |
550 | rc = cifs_posix_open(full_path, NULL, inode->i_sb, | 553 | rc = cifs_posix_open(full_path, NULL, file->f_path.mnt, |
551 | cifs_sb->mnt_file_mode /* ignored */, | 554 | cifs_sb->mnt_file_mode /* ignored */, |
552 | oflags, &oplock, &netfid, xid); | 555 | oflags, &oplock, &netfid, xid); |
553 | if (rc == 0) { | 556 | if (rc == 0) { |
@@ -2312,6 +2315,73 @@ out: | |||
2312 | return rc; | 2315 | return rc; |
2313 | } | 2316 | } |
2314 | 2317 | ||
2318 | static void | ||
2319 | cifs_oplock_break(struct slow_work *work) | ||
2320 | { | ||
2321 | struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo, | ||
2322 | oplock_break); | ||
2323 | struct inode *inode = cfile->pInode; | ||
2324 | struct cifsInodeInfo *cinode = CIFS_I(inode); | ||
2325 | struct cifs_sb_info *cifs_sb = CIFS_SB(cfile->mnt->mnt_sb); | ||
2326 | int rc, waitrc = 0; | ||
2327 | |||
2328 | if (inode && S_ISREG(inode->i_mode)) { | ||
2329 | #ifdef CONFIG_CIFS_EXPERIMENTAL | ||
2330 | if (cinode->clientCanCacheAll == 0) | ||
2331 | break_lease(inode, FMODE_READ); | ||
2332 | else if (cinode->clientCanCacheRead == 0) | ||
2333 | break_lease(inode, FMODE_WRITE); | ||
2334 | #endif | ||
2335 | rc = filemap_fdatawrite(inode->i_mapping); | ||
2336 | if (cinode->clientCanCacheRead == 0) { | ||
2337 | waitrc = filemap_fdatawait(inode->i_mapping); | ||
2338 | invalidate_remote_inode(inode); | ||
2339 | } | ||
2340 | if (!rc) | ||
2341 | rc = waitrc; | ||
2342 | if (rc) | ||
2343 | cinode->write_behind_rc = rc; | ||
2344 | cFYI(1, ("Oplock flush inode %p rc %d", inode, rc)); | ||
2345 | } | ||
2346 | |||
2347 | /* | ||
2348 | * releasing stale oplock after recent reconnect of smb session using | ||
2349 | * a now incorrect file handle is not a data integrity issue but do | ||
2350 | * not bother sending an oplock release if session to server still is | ||
2351 | * disconnected since oplock already released by the server | ||
2352 | */ | ||
2353 | if (!cfile->closePend && !cfile->oplock_break_cancelled) { | ||
2354 | rc = CIFSSMBLock(0, cifs_sb->tcon, cfile->netfid, 0, 0, 0, 0, | ||
2355 | LOCKING_ANDX_OPLOCK_RELEASE, false); | ||
2356 | cFYI(1, ("Oplock release rc = %d", rc)); | ||
2357 | } | ||
2358 | } | ||
2359 | |||
2360 | static int | ||
2361 | cifs_oplock_break_get(struct slow_work *work) | ||
2362 | { | ||
2363 | struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo, | ||
2364 | oplock_break); | ||
2365 | mntget(cfile->mnt); | ||
2366 | cifsFileInfo_get(cfile); | ||
2367 | return 0; | ||
2368 | } | ||
2369 | |||
2370 | static void | ||
2371 | cifs_oplock_break_put(struct slow_work *work) | ||
2372 | { | ||
2373 | struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo, | ||
2374 | oplock_break); | ||
2375 | mntput(cfile->mnt); | ||
2376 | cifsFileInfo_put(cfile); | ||
2377 | } | ||
2378 | |||
2379 | const struct slow_work_ops cifs_oplock_break_ops = { | ||
2380 | .get_ref = cifs_oplock_break_get, | ||
2381 | .put_ref = cifs_oplock_break_put, | ||
2382 | .execute = cifs_oplock_break, | ||
2383 | }; | ||
2384 | |||
2315 | const struct address_space_operations cifs_addr_ops = { | 2385 | const struct address_space_operations cifs_addr_ops = { |
2316 | .readpage = cifs_readpage, | 2386 | .readpage = cifs_readpage, |
2317 | .readpages = cifs_readpages, | 2387 | .readpages = cifs_readpages, |