Commit 20d72b00ca81 ("netfs: Fix the request's work item to not require a ref") modified netfs_alloc_request() to initialize the reference counter to 2 instead of 1. The rationale was that the requet's "work" would release the second reference after completion (via netfs_{read,write}_collection_worker()). That works most of the time if all goes well. However, it leaks this additional reference if the request is released before the I/O operation has been submitted: the error code path only decrements the reference counter once and the work item will never be queued because there will never be a completion. This has caused outages of our whole server cluster today because tasks were blocked in netfs_wait_for_outstanding_io(), leading to deadlocks in Ceph (another bug that I will address soon in another patch). This was caused by a netfs_pgpriv2_begin_copy_to_cache() call which failed in fscache_begin_write_operation(). The leaked netfs_io_request was never completed, leaving `netfs_inode.io_count` with a positive value forever. All of this is super-fragile code. Finding out which code paths will lead to an eventual completion and which do not is hard to see: - Some functions like netfs_create_write_req() allocate a request, but will never submit any I/O. - netfs_unbuffered_read_iter_locked() calls netfs_unbuffered_read() and then netfs_put_request(); however, netfs_unbuffered_read() can also fail early before submitting the I/O request, therefore another netfs_put_request() call must be added there. A rule of thumb is that functions that return a `netfs_io_request` do not submit I/O, and all of their callers must be checked. For my taste, the whole netfs code needs an overhaul to make reference counting easier to understand and less fragile & obscure. But to fix this bug here and now and produce a patch that is adequate for a stable backport, I tried a minimal approach that quickly frees the request object upon early failure. I decided against adding a second netfs_put_request() each time because that would cause code duplication which obscures the code further. Instead, I added the function netfs_put_failed_request() which frees such a failed request synchronously under the assumption that the reference count is exactly 2 (as initially set by netfs_alloc_request() and never touched), verified by a WARN_ON_ONCE(). It then deinitializes the request object (without going through the "cleanup_work" indirection) and frees the allocation (with RCU protection to protect against concurrent access by netfs_requests_seq_start()). All code paths that fail early have been changed to call netfs_put_failed_request() instead of netfs_put_request(). Additionally, I have added a netfs_put_request() call to netfs_unbuffered_read() as explained above because the netfs_put_failed_request() approach does not work there.
Unified · Split
| @@ -369,7 +369,7 @@ void netfs_readahead(struct readahead_control *ractl) · 368 unmodified lines | |||
| 369 | return netfs_put_request(rreq, netfs_rreq_trace_put_return); | 369 | return netfs_put_request(rreq, netfs_rreq_trace_put_return); |
| 370 | 370 | ||
| 371 | cleanup_free: | 371 | cleanup_free: |
| 372 | return netfs_put_request(rreq, netfs_rreq_trace_put_failed); | 372 | return netfs_put_failed_request(rreq); |
| 373 | } | 373 | } |
| 374 | EXPORT_SYMBOL(netfs_readahead); | 374 | EXPORT_SYMBOL(netfs_readahead); |
| 375 | 375 | ||
| @@ -472,7 +472,7 @@ static int netfs_read_gaps(struct file *file, struct folio *folio) · 96 unmodified lines | |||
| 472 | return ret < 0 ? ret : 0; | 472 | return ret < 0 ? ret : 0; |
| 473 | 473 | ||
| 474 | discard: | 474 | discard: |
| 475 | netfs_put_request(rreq, netfs_rreq_trace_put_discard); | 475 | netfs_put_failed_request(rreq); |
| 476 | alloc_error: | 476 | alloc_error: |
| 477 | folio_unlock(folio); | 477 | folio_unlock(folio); |
| 478 | return ret; | 478 | return ret; |
| @@ -532,7 +532,7 @@ int netfs_read_folio(struct file *file, struct folio *folio) · 53 unmodified lines | |||
| 532 | return ret < 0 ? ret : 0; | 532 | return ret < 0 ? ret : 0; |
| 533 | 533 | ||
| 534 | discard: | 534 | discard: |
| 535 | netfs_put_request(rreq, netfs_rreq_trace_put_discard); | 535 | netfs_put_failed_request(rreq); |
| 536 | alloc_error: | 536 | alloc_error: |
| 537 | folio_unlock(folio); | 537 | folio_unlock(folio); |
| 538 | return ret; | 538 | return ret; |
| @@ -699,7 +699,7 @@ have_folio_no_wait: · 160 unmodified lines | |||
| 699 | return 0; | 699 | return 0; |
| 700 | 700 | ||
| 701 | error_put: | 701 | error_put: |
| 702 | netfs_put_request(rreq, netfs_rreq_trace_put_failed); | 702 | netfs_put_failed_request(rreq); |
| 703 | error: | 703 | error: |
| 704 | if (folio) { | 704 | if (folio) { |
| 705 | folio_unlock(folio); | 705 | folio_unlock(folio); |
| @@ -754,7 +754,7 @@ int netfs_prefetch_for_write(struct file *file, struct folio *folio, · 48 unmodified lines | |||
| 754 | return ret < 0 ? ret : 0; | 754 | return ret < 0 ? ret : 0; |
| 755 | 755 | ||
| 756 | error_put: | 756 | error_put: |
| 757 | netfs_put_request(rreq, netfs_rreq_trace_put_discard); | 757 | netfs_put_failed_request(rreq); |
| 758 | error: | 758 | error: |
| 759 | _leave(" = %d", ret); | 759 | _leave(" = %d", ret); |
| 760 | return ret; | 760 | return ret; |
| @@ -131,6 +131,7 @@ static ssize_t netfs_unbuffered_read(struct netfs_io_request *rreq, bool sync) · 130 unmodified lines | |||
| 131 | 131 | ||
| 132 | if (rreq->len == 0) { | 132 | if (rreq->len == 0) { |
| 133 | pr_err("Zero-sized read [R=%x]\n", rreq->debug_id); | 133 | pr_err("Zero-sized read [R=%x]\n", rreq->debug_id); |
| 134 | netfs_put_request(rreq, netfs_rreq_trace_put_discard); | ||
| 134 | return -EIO; | 135 | return -EIO; |
| 135 | } | 136 | } |
| 136 | 137 | ||
| @@ -205,7 +206,7 @@ ssize_t netfs_unbuffered_read_iter_locked(struct kiocb *iocb, struct iov_iter *i · 68 unmodified lines | |||
| 205 | if (user_backed_iter(iter)) { | 206 | if (user_backed_iter(iter)) { |
| 206 | ret = netfs_extract_user_iter(iter, rreq->len, &rreq->buffer.iter, 0); | 207 | ret = netfs_extract_user_iter(iter, rreq->len, &rreq->buffer.iter, 0); |
| 207 | if (ret < 0) | 208 | if (ret < 0) |
| 208 | goto out; | 209 | goto error_put; |
| 209 | rreq->direct_bv = (struct bio_vec *)rreq->buffer.iter.bvec; | 210 | rreq->direct_bv = (struct bio_vec *)rreq->buffer.iter.bvec; |
| 210 | rreq->direct_bv_count = ret; | 211 | rreq->direct_bv_count = ret; |
| 211 | rreq->direct_bv_unpin = iov_iter_extract_will_pin(iter); | 212 | rreq->direct_bv_unpin = iov_iter_extract_will_pin(iter); |
| @@ -238,6 +239,10 @@ out: · 26 unmodified lines | |||
| 238 | if (ret > 0) | 239 | if (ret > 0) |
| 239 | orig_count -= ret; | 240 | orig_count -= ret; |
| 240 | return ret; | 241 | return ret; |
| 242 | |||
| 243 | error_put: | ||
| 244 | netfs_put_failed_request(rreq); | ||
| 245 | return ret; | ||
| 241 | } | 246 | } |
| 242 | EXPORT_SYMBOL(netfs_unbuffered_read_iter_locked); | 247 | EXPORT_SYMBOL(netfs_unbuffered_read_iter_locked); |
| 243 | 248 | ||
| @@ -57,7 +57,7 @@ ssize_t netfs_unbuffered_write_iter_locked(struct kiocb *iocb, struct iov_iter * · 56 unmodified lines | |||
| 57 | n = netfs_extract_user_iter(iter, len, &wreq->buffer.iter, 0); | 57 | n = netfs_extract_user_iter(iter, len, &wreq->buffer.iter, 0); |
| 58 | if (n < 0) { | 58 | if (n < 0) { |
| 59 | ret = n; | 59 | ret = n; |
| 60 | goto out; | 60 | goto error_put; |
| 61 | } | 61 | } |
| 62 | wreq->direct_bv = (struct bio_vec *)wreq->buffer.iter.bvec; | 62 | wreq->direct_bv = (struct bio_vec *)wreq->buffer.iter.bvec; |
| 63 | wreq->direct_bv_count = n; | 63 | wreq->direct_bv_count = n; |
| @@ -101,6 +101,10 @@ ssize_t netfs_unbuffered_write_iter_locked(struct kiocb *iocb, struct iov_iter * · 37 unmodified lines | |||
| 101 | out: | 101 | out: |
| 102 | netfs_put_request(wreq, netfs_rreq_trace_put_return); | 102 | netfs_put_request(wreq, netfs_rreq_trace_put_return); |
| 103 | return ret; | 103 | return ret; |
| 104 | |||
| 105 | error_put: | ||
| 106 | netfs_put_failed_request(wreq); | ||
| 107 | return ret; | ||
| 104 | } | 108 | } |
| 105 | EXPORT_SYMBOL(netfs_unbuffered_write_iter_locked); | 109 | EXPORT_SYMBOL(netfs_unbuffered_write_iter_locked); |
| 106 | 110 | ||
| @@ -87,6 +87,7 @@ struct netfs_io_request *netfs_alloc_request(struct address_space *mapping, · 86 unmodified lines | |||
| 87 | void netfs_get_request(struct netfs_io_request *rreq, enum netfs_rreq_ref_trace what); | 87 | void netfs_get_request(struct netfs_io_request *rreq, enum netfs_rreq_ref_trace what); |
| 88 | void netfs_clear_subrequests(struct netfs_io_request *rreq); | 88 | void netfs_clear_subrequests(struct netfs_io_request *rreq); |
| 89 | void netfs_put_request(struct netfs_io_request *rreq, enum netfs_rreq_ref_trace what); | 89 | void netfs_put_request(struct netfs_io_request *rreq, enum netfs_rreq_ref_trace what); |
| 90 | void netfs_put_failed_request(struct netfs_io_request *rreq); | ||
| 90 | struct netfs_io_subrequest *netfs_alloc_subrequest(struct netfs_io_request *rreq); | 91 | struct netfs_io_subrequest *netfs_alloc_subrequest(struct netfs_io_request *rreq); |
| 91 | 92 | ||
| 92 | static inline void netfs_see_request(struct netfs_io_request *rreq, | 93 | static inline void netfs_see_request(struct netfs_io_request *rreq, |
| @@ -116,10 +116,8 @@ static void netfs_free_request_rcu(struct rcu_head *rcu) · 115 unmodified lines | |||
| 116 | netfs_stat_d(&netfs_n_rh_rreq); | 116 | netfs_stat_d(&netfs_n_rh_rreq); |
| 117 | } | 117 | } |
| 118 | 118 | ||
| 119 | static void netfs_free_request(struct work_struct *work) | 119 | static void netfs_deinit_request(struct netfs_io_request *rreq) |
| 120 | { | 120 | { |
| 121 | struct netfs_io_request *rreq = | ||
| 122 | container_of(work, struct netfs_io_request, cleanup_work); | ||
| 123 | struct netfs_inode *ictx = netfs_inode(rreq->inode); | 121 | struct netfs_inode *ictx = netfs_inode(rreq->inode); |
| 124 | unsigned int i; | 122 | unsigned int i; |
| 125 | 123 | ||
| @@ -149,6 +147,14 @@ static void netfs_free_request(struct work_struct *work) · 23 unmodified lines | |||
| 149 | 147 | ||
| 150 | if (atomic_dec_and_test(&ictx->io_count)) | 148 | if (atomic_dec_and_test(&ictx->io_count)) |
| 151 | wake_up_var(&ictx->io_count); | 149 | wake_up_var(&ictx->io_count); |
| 150 | } | ||
| 151 | |||
| 152 | static void netfs_free_request(struct work_struct *work) | ||
| 153 | { | ||
| 154 | struct netfs_io_request *rreq = | ||
| 155 | container_of(work, struct netfs_io_request, cleanup_work); | ||
| 156 | |||
| 157 | netfs_deinit_request(rreq); | ||
| 152 | call_rcu(&rreq->rcu, netfs_free_request_rcu); | 158 | call_rcu(&rreq->rcu, netfs_free_request_rcu); |
| 153 | } | 159 | } |
| 154 | 160 | ||
| @@ -167,6 +173,24 @@ void netfs_put_request(struct netfs_io_request *rreq, enum netfs_rreq_ref_trace · 12 unmodified lines | |||
| 167 | } | 173 | } |
| 168 | } | 174 | } |
| 169 | 175 | ||
| 176 | /* | ||
| 177 | * Free a request (synchronously) that was just allocated but has | ||
| 178 | * failed before it could be submitted. | ||
| 179 | */ | ||
| 180 | void netfs_put_failed_request(struct netfs_io_request *rreq) | ||
| 181 | { | ||
| 182 | int r = refcount_read(&rreq->ref); | ||
| 183 | |||
| 184 | /* new requests have two references (see | ||
| 185 | * netfs_alloc_request(), and this function is only allowed on | ||
| 186 | * new request objects | ||
| 187 | */ | ||
| 188 | WARN_ON_ONCE(r != 2); | ||
| 189 | |||
| 190 | trace_netfs_rreq_ref(rreq->debug_id, r, netfs_rreq_trace_put_failed); | ||
| 191 | netfs_free_request(&rreq->cleanup_work); | ||
| 192 | } | ||
| 193 | |||
| 170 | /* | 194 | /* |
| 171 | * Allocate and partially initialise an I/O request structure. | 195 | * Allocate and partially initialise an I/O request structure. |
| 172 | */ | 196 | */ |
| @@ -118,7 +118,7 @@ static struct netfs_io_request *netfs_pgpriv2_begin_copy_to_cache( · 117 unmodified lines | |||
| 118 | return creq; | 118 | return creq; |
| 119 | 119 | ||
| 120 | cancel_put: | 120 | cancel_put: |
| 121 | netfs_put_request(creq, netfs_rreq_trace_put_return); | 121 | netfs_put_failed_request(creq); |
| 122 | cancel: | 122 | cancel: |
| 123 | rreq->copy_to_cache = ERR_PTR(-ENOBUFS); | 123 | rreq->copy_to_cache = ERR_PTR(-ENOBUFS); |
| 124 | clear_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &rreq->flags); | 124 | clear_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &rreq->flags); |
| @@ -189,7 +189,7 @@ ssize_t netfs_read_single(struct inode *inode, struct file *file, struct iov_ite · 188 unmodified lines | |||
| 189 | return ret; | 189 | return ret; |
| 190 | 190 | ||
| 191 | cleanup_free: | 191 | cleanup_free: |
| 192 | netfs_put_request(rreq, netfs_rreq_trace_put_failed); | 192 | netfs_put_failed_request(rreq); |
| 193 | return ret; | 193 | return ret; |
| 194 | } | 194 | } |
| 195 | EXPORT_SYMBOL(netfs_read_single); | 195 | EXPORT_SYMBOL(netfs_read_single); |
| @@ -133,8 +133,7 @@ struct netfs_io_request *netfs_create_write_req(struct address_space *mapping, · 132 unmodified lines | |||
| 133 | 133 | ||
| 134 | return wreq; | 134 | return wreq; |
| 135 | nomem: | 135 | nomem: |
| 136 | wreq->error = -ENOMEM; | 136 | netfs_put_failed_request(wreq); |
| 137 | netfs_put_request(wreq, netfs_rreq_trace_put_failed); | ||
| 138 | return ERR_PTR(-ENOMEM); | 137 | return ERR_PTR(-ENOMEM); |
| 139 | } | 138 | } |
| 140 | 139 | ||