diff options
Diffstat (limited to 'fs/nfs/direct.c')
| -rw-r--r-- | fs/nfs/direct.c | 134 | 
1 files changed, 93 insertions, 41 deletions
diff --git a/fs/nfs/direct.c b/fs/nfs/direct.c index 47d892a1d363..f6c74f424691 100644 --- a/fs/nfs/direct.c +++ b/fs/nfs/direct.c @@ -93,12 +93,10 @@ nfs_direct_handle_truncated(struct nfs_direct_req *dreq,  		dreq->max_count = dreq_len;  		if (dreq->count > dreq_len)  			dreq->count = dreq_len; - -		if (test_bit(NFS_IOHDR_ERROR, &hdr->flags)) -			dreq->error = hdr->error; -		else /* Clear outstanding error if this is EOF */ -			dreq->error = 0;  	} + +	if (test_bit(NFS_IOHDR_ERROR, &hdr->flags) && !dreq->error) +		dreq->error = hdr->error;  }  static void @@ -120,6 +118,18 @@ nfs_direct_count_bytes(struct nfs_direct_req *dreq,  		dreq->count = dreq_len;  } +static void nfs_direct_truncate_request(struct nfs_direct_req *dreq, +					struct nfs_page *req) +{ +	loff_t offs = req_offset(req); +	size_t req_start = (size_t)(offs - dreq->io_start); + +	if (req_start < dreq->max_count) +		dreq->max_count = req_start; +	if (req_start < dreq->count) +		dreq->count = req_start; +} +  /**   * nfs_swap_rw - NFS address space operation for swap I/O   * @iocb: target I/O control block @@ -488,7 +498,9 @@ static void nfs_direct_add_page_head(struct list_head *list,  	kref_get(&head->wb_kref);  } -static void nfs_direct_join_group(struct list_head *list, struct inode *inode) +static void nfs_direct_join_group(struct list_head *list, +				  struct nfs_commit_info *cinfo, +				  struct inode *inode)  {  	struct nfs_page *req, *subreq; @@ -510,7 +522,7 @@ static void nfs_direct_join_group(struct list_head *list, struct inode *inode)  				nfs_release_request(subreq);  			}  		} while ((subreq = subreq->wb_this_page) != req); -		nfs_join_page_group(req, inode); +		nfs_join_page_group(req, cinfo, inode);  	}  } @@ -528,20 +540,15 @@ nfs_direct_write_scan_commit_list(struct inode *inode,  static void nfs_direct_write_reschedule(struct nfs_direct_req *dreq)  {  	struct nfs_pageio_descriptor desc; -	struct nfs_page *req, *tmp; +	struct nfs_page *req;  	LIST_HEAD(reqs);  	struct nfs_commit_info cinfo; -	LIST_HEAD(failed);  	nfs_init_cinfo_from_dreq(&cinfo, dreq);  	nfs_direct_write_scan_commit_list(dreq->inode, &reqs, &cinfo); -	nfs_direct_join_group(&reqs, dreq->inode); +	nfs_direct_join_group(&reqs, &cinfo, dreq->inode); -	dreq->count = 0; -	dreq->max_count = 0; -	list_for_each_entry(req, &reqs, wb_list) -		dreq->max_count += req->wb_bytes;  	nfs_clear_pnfs_ds_commit_verifiers(&dreq->ds_cinfo);  	get_dreq(dreq); @@ -549,27 +556,40 @@ static void nfs_direct_write_reschedule(struct nfs_direct_req *dreq)  			      &nfs_direct_write_completion_ops);  	desc.pg_dreq = dreq; -	list_for_each_entry_safe(req, tmp, &reqs, wb_list) { +	while (!list_empty(&reqs)) { +		req = nfs_list_entry(reqs.next);  		/* Bump the transmission count */  		req->wb_nio++;  		if (!nfs_pageio_add_request(&desc, req)) { -			nfs_list_move_request(req, &failed); -			spin_lock(&cinfo.inode->i_lock); -			dreq->flags = 0; -			if (desc.pg_error < 0) +			spin_lock(&dreq->lock); +			if (dreq->error < 0) { +				desc.pg_error = dreq->error; +			} else if (desc.pg_error != -EAGAIN) { +				dreq->flags = 0; +				if (!desc.pg_error) +					desc.pg_error = -EIO;  				dreq->error = desc.pg_error; -			else -				dreq->error = -EIO; -			spin_unlock(&cinfo.inode->i_lock); +			} else +				dreq->flags = NFS_ODIRECT_RESCHED_WRITES; +			spin_unlock(&dreq->lock); +			break;  		}  		nfs_release_request(req);  	}  	nfs_pageio_complete(&desc); -	while (!list_empty(&failed)) { -		req = nfs_list_entry(failed.next); +	while (!list_empty(&reqs)) { +		req = nfs_list_entry(reqs.next);  		nfs_list_remove_request(req);  		nfs_unlock_and_release_request(req); +		if (desc.pg_error == -EAGAIN) { +			nfs_mark_request_commit(req, NULL, &cinfo, 0); +		} else { +			spin_lock(&dreq->lock); +			nfs_direct_truncate_request(dreq, req); +			spin_unlock(&dreq->lock); +			nfs_release_request(req); +		}  	}  	if (put_dreq(dreq)) @@ -589,8 +609,6 @@ static void nfs_direct_commit_complete(struct nfs_commit_data *data)  	if (status < 0) {  		/* Errors in commit are fatal */  		dreq->error = status; -		dreq->max_count = 0; -		dreq->count = 0;  		dreq->flags = NFS_ODIRECT_DONE;  	} else {  		status = dreq->error; @@ -601,7 +619,12 @@ static void nfs_direct_commit_complete(struct nfs_commit_data *data)  	while (!list_empty(&data->pages)) {  		req = nfs_list_entry(data->pages.next);  		nfs_list_remove_request(req); -		if (status >= 0 && !nfs_write_match_verf(verf, req)) { +		if (status < 0) { +			spin_lock(&dreq->lock); +			nfs_direct_truncate_request(dreq, req); +			spin_unlock(&dreq->lock); +			nfs_release_request(req); +		} else if (!nfs_write_match_verf(verf, req)) {  			dreq->flags = NFS_ODIRECT_RESCHED_WRITES;  			/*  			 * Despite the reboot, the write was successful, @@ -609,7 +632,7 @@ static void nfs_direct_commit_complete(struct nfs_commit_data *data)  			 */  			req->wb_nio = 0;  			nfs_mark_request_commit(req, NULL, &cinfo, 0); -		} else /* Error or match */ +		} else  			nfs_release_request(req);  		nfs_unlock_and_release_request(req);  	} @@ -662,6 +685,7 @@ static void nfs_direct_write_clear_reqs(struct nfs_direct_req *dreq)  	while (!list_empty(&reqs)) {  		req = nfs_list_entry(reqs.next);  		nfs_list_remove_request(req); +		nfs_direct_truncate_request(dreq, req);  		nfs_release_request(req);  		nfs_unlock_and_release_request(req);  	} @@ -711,7 +735,8 @@ static void nfs_direct_write_completion(struct nfs_pgio_header *hdr)  	}  	nfs_direct_count_bytes(dreq, hdr); -	if (test_bit(NFS_IOHDR_UNSTABLE_WRITES, &hdr->flags)) { +	if (test_bit(NFS_IOHDR_UNSTABLE_WRITES, &hdr->flags) && +	    !test_bit(NFS_IOHDR_ERROR, &hdr->flags)) {  		if (!dreq->flags)  			dreq->flags = NFS_ODIRECT_DO_COMMIT;  		flags = dreq->flags; @@ -755,18 +780,23 @@ static void nfs_write_sync_pgio_error(struct list_head *head, int error)  static void nfs_direct_write_reschedule_io(struct nfs_pgio_header *hdr)  {  	struct nfs_direct_req *dreq = hdr->dreq; +	struct nfs_page *req; +	struct nfs_commit_info cinfo;  	trace_nfs_direct_write_reschedule_io(dreq); +	nfs_init_cinfo_from_dreq(&cinfo, dreq);  	spin_lock(&dreq->lock); -	if (dreq->error == 0) { +	if (dreq->error == 0)  		dreq->flags = NFS_ODIRECT_RESCHED_WRITES; -		/* fake unstable write to let common nfs resend pages */ -		hdr->verf.committed = NFS_UNSTABLE; -		hdr->good_bytes = hdr->args.offset + hdr->args.count - -			hdr->io_start; -	} +	set_bit(NFS_IOHDR_REDO, &hdr->flags);  	spin_unlock(&dreq->lock); +	while (!list_empty(&hdr->pages)) { +		req = nfs_list_entry(hdr->pages.next); +		nfs_list_remove_request(req); +		nfs_unlock_request(req); +		nfs_mark_request_commit(req, NULL, &cinfo, 0); +	}  }  static const struct nfs_pgio_completion_ops nfs_direct_write_completion_ops = { @@ -794,9 +824,11 @@ static ssize_t nfs_direct_write_schedule_iovec(struct nfs_direct_req *dreq,  {  	struct nfs_pageio_descriptor desc;  	struct inode *inode = dreq->inode; +	struct nfs_commit_info cinfo;  	ssize_t result = 0;  	size_t requested_bytes = 0;  	size_t wsize = max_t(size_t, NFS_SERVER(inode)->wsize, PAGE_SIZE); +	bool defer = false;  	trace_nfs_direct_write_schedule_iovec(dreq); @@ -837,17 +869,37 @@ static ssize_t nfs_direct_write_schedule_iovec(struct nfs_direct_req *dreq,  				break;  			} -			nfs_lock_request(req); -			if (!nfs_pageio_add_request(&desc, req)) { -				result = desc.pg_error; -				nfs_unlock_and_release_request(req); -				break; -			}  			pgbase = 0;  			bytes -= req_len;  			requested_bytes += req_len;  			pos += req_len;  			dreq->bytes_left -= req_len; + +			if (defer) { +				nfs_mark_request_commit(req, NULL, &cinfo, 0); +				continue; +			} + +			nfs_lock_request(req); +			if (nfs_pageio_add_request(&desc, req)) +				continue; + +			/* Exit on hard errors */ +			if (desc.pg_error < 0 && desc.pg_error != -EAGAIN) { +				result = desc.pg_error; +				nfs_unlock_and_release_request(req); +				break; +			} + +			/* If the error is soft, defer remaining requests */ +			nfs_init_cinfo_from_dreq(&cinfo, dreq); +			spin_lock(&dreq->lock); +			dreq->flags = NFS_ODIRECT_RESCHED_WRITES; +			spin_unlock(&dreq->lock); +			nfs_unlock_request(req); +			nfs_mark_request_commit(req, NULL, &cinfo, 0); +			desc.pg_error = 0; +			defer = true;  		}  		nfs_direct_release_pages(pagevec, npages);  		kvfree(pagevec);  |