diff options
author | Christoph Hellwig <hch@lst.de> | 2014-09-10 17:36:30 -0700 |
---|---|---|
committer | Trond Myklebust <trond.myklebust@primarydata.com> | 2014-09-12 13:22:45 -0400 |
commit | 34dc93c2fc04da0d01acf8a1660b4ab276208af7 (patch) | |
tree | 89c6c0dbc294682703f7172e1285a8bf5245ce52 /fs/nfs/blocklayout/extent_tree.c | |
parent | d4b18c3e00b8d18fbd316abe9639b91ad416e1f3 (diff) |
pnfs/blocklayout: allocate separate pages for the layoutcommit payload
Instead of overflowing the XDR send buffer with our extent list allocate
pages and pre-encode the layoutupdate payload into them. We optimistically
allocate a single page use alloc_page and only switch to vmalloc when we
have more extents outstanding. Currently there is only a single testcase
(xfstests generic/113) which can reproduce large enough extent lists for
this to occur.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Trond Myklebust <trond.myklebust@primarydata.com>
Diffstat (limited to 'fs/nfs/blocklayout/extent_tree.c')
-rw-r--r-- | fs/nfs/blocklayout/extent_tree.c | 102 |
1 files changed, 81 insertions, 21 deletions
diff --git a/fs/nfs/blocklayout/extent_tree.c b/fs/nfs/blocklayout/extent_tree.c index 43e891b3e0b6..1b6009ee75ce 100644 --- a/fs/nfs/blocklayout/extent_tree.c +++ b/fs/nfs/blocklayout/extent_tree.c @@ -462,19 +462,25 @@ out: return err; } -int -ext_tree_encode_commit(struct pnfs_block_layout *bl, struct xdr_stream *xdr) +static void ext_tree_free_commitdata(struct nfs4_layoutcommit_args *arg, + size_t buffer_size) { - struct pnfs_block_extent *be; - unsigned int count = 0; - __be32 *p, *xdr_start; - int ret = 0; + if (arg->layoutupdate_pages != &arg->layoutupdate_page) { + int nr_pages = DIV_ROUND_UP(buffer_size, PAGE_SIZE), i; - dprintk("%s enter\n", __func__); + for (i = 0; i < nr_pages; i++) + put_page(arg->layoutupdate_pages[i]); + kfree(arg->layoutupdate_pages); + } else { + put_page(arg->layoutupdate_page); + } +} - xdr_start = xdr_reserve_space(xdr, 8); - if (!xdr_start) - return -ENOSPC; +static int ext_tree_encode_commit(struct pnfs_block_layout *bl, __be32 *p, + size_t buffer_size, size_t *count) +{ + struct pnfs_block_extent *be; + int ret = 0; spin_lock(&bl->bl_ext_lock); for (be = ext_tree_first(&bl->bl_ext_rw); be; be = ext_tree_next(be)) { @@ -482,12 +488,11 @@ ext_tree_encode_commit(struct pnfs_block_layout *bl, struct xdr_stream *xdr) be->be_tag != EXTENT_WRITTEN) continue; - p = xdr_reserve_space(xdr, 7 * sizeof(__be32) + - NFS4_DEVICEID4_SIZE); - if (!p) { - printk("%s: out of space for extent list\n", __func__); + (*count)++; + if (*count * BL_EXTENT_SIZE > buffer_size) { + /* keep counting.. */ ret = -ENOSPC; - break; + continue; } p = xdr_encode_opaque_fixed(p, be->be_device->deviceid.data, @@ -498,25 +503,80 @@ ext_tree_encode_commit(struct pnfs_block_layout *bl, struct xdr_stream *xdr) *p++ = cpu_to_be32(PNFS_BLOCK_READWRITE_DATA); be->be_tag = EXTENT_COMMITTING; - count++; } spin_unlock(&bl->bl_ext_lock); - xdr_start[0] = cpu_to_be32((xdr->p - xdr_start - 1) * 4); - xdr_start[1] = cpu_to_be32(count); - - dprintk("%s found %i ranges\n", __func__, count); return ret; } +int +ext_tree_prepare_commit(struct nfs4_layoutcommit_args *arg) +{ + struct pnfs_block_layout *bl = BLK_LO2EXT(NFS_I(arg->inode)->layout); + size_t count = 0, buffer_size = PAGE_SIZE; + __be32 *start_p; + int ret; + + dprintk("%s enter\n", __func__); + + arg->layoutupdate_page = alloc_page(GFP_NOFS); + if (!arg->layoutupdate_page) + return -ENOMEM; + start_p = page_address(arg->layoutupdate_page); + arg->layoutupdate_pages = &arg->layoutupdate_page; + +retry: + ret = ext_tree_encode_commit(bl, start_p + 1, buffer_size, &count); + if (unlikely(ret)) { + ext_tree_free_commitdata(arg, buffer_size); + + buffer_size = sizeof(__be32) + BL_EXTENT_SIZE * count; + count = 0; + + arg->layoutupdate_pages = + kcalloc(DIV_ROUND_UP(buffer_size, PAGE_SIZE), + sizeof(struct page *), GFP_NOFS); + if (!arg->layoutupdate_pages) + return -ENOMEM; + + start_p = __vmalloc(buffer_size, GFP_NOFS, PAGE_KERNEL); + if (!start_p) { + kfree(arg->layoutupdate_pages); + return -ENOMEM; + } + + goto retry; + } + + *start_p = cpu_to_be32(count); + arg->layoutupdate_len = sizeof(__be32) + BL_EXTENT_SIZE * count; + + if (unlikely(arg->layoutupdate_pages != &arg->layoutupdate_page)) { + __be32 *p = start_p; + int i = 0; + + for (p = start_p; + p < start_p + arg->layoutupdate_len; + p += PAGE_SIZE) { + arg->layoutupdate_pages[i++] = vmalloc_to_page(p); + } + } + + dprintk("%s found %zu ranges\n", __func__, count); + return 0; +} + void -ext_tree_mark_committed(struct pnfs_block_layout *bl, int status) +ext_tree_mark_committed(struct nfs4_layoutcommit_args *arg, int status) { + struct pnfs_block_layout *bl = BLK_LO2EXT(NFS_I(arg->inode)->layout); struct rb_root *root = &bl->bl_ext_rw; struct pnfs_block_extent *be; dprintk("%s status %d\n", __func__, status); + ext_tree_free_commitdata(arg, arg->layoutupdate_len); + spin_lock(&bl->bl_ext_lock); for (be = ext_tree_first(root); be; be = ext_tree_next(be)) { if (be->be_state != PNFS_BLOCK_INVALID_DATA || |