@@ -310,22 +310,45 @@ pub async fn persist_with_retry(
310310 // See: <https://github.com/astral-sh/uv/issues/1491> & <https://github.com/astral-sh/uv/issues/9531>
311311 let to = to. as_ref ( ) ;
312312
313- // the `NamedTempFile` `persist` method consumes `self`, and returns it back inside the Error in case of `PersistError`
313+ // Ok there's a lot of complex ownership stuff going on here.
314+ //
315+ // the `NamedTempFile` `persist` method consumes `self`, and returns it back inside
316+ // the Error in case of `PersistError`:
314317 // https://docs.rs/tempfile/latest/tempfile/struct.NamedTempFile.html#method.persist
315- // So we will update the `from` optional value in safe and borrow-checker friendly way every retry
316- // Allows us to use the NamedTempFile inside a FnMut closure used for backoff::retry
317- let mut from = Some ( from) ;
318- let persist = move || {
319- // Needed because we cannot move out of `from`, a captured variable in an `FnMut` closure, and then pass it to the async move block
320- let mut from: Option < NamedTempFile > = from. take ( ) ;
318+ // So every time we fail, we need to reset the `NamedTempFile` to try again.
319+ //
320+ // Every time we (re)try we call this outer closure (`let persist = ...`), so it needs to
321+ // be at least a `FnMut` (as opposed to `Fnonce`). However the closure needs to return a
322+ // totally owned `Future` (so effectively it returns a `FnOnce`).
323+ //
324+ // But if the `Future` is totally owned it *necessarily* can't write back the `NamedTempFile`
325+ // to somewhere the outer `FnMut` can see using references. So we need to use `Arc`s
326+ // with interior mutability (`Mutex`) to have the closure and all the Futures it creates share
327+ // a single memory location that the `NamedTempFile` can be shuttled in and out of.
328+ //
329+ // In spite of the Mutex all of this code will run logically serially, so there shouldn't be a
330+ // chance for a race where we try to get the `NamedTempFile` but it's actually None. The code
331+ // is just written pedantically/robustly.
332+ let from = std:: sync:: Arc :: new ( std:: sync:: Mutex :: new ( Some ( from) ) ) ;
333+ let persist = || {
334+ // Turn our by-ref-captured Arc into an owned Arc that the Future can capture by-value
335+ let from2 = from. clone ( ) ;
321336
322337 async move {
323- if let Some ( file) = from. take ( ) {
338+ let maybe_file: Option < NamedTempFile > = from2
339+ . lock ( )
340+ . map_err ( |_| PersistRetryError :: LostState ) ?
341+ . take ( ) ;
342+ if let Some ( file) = maybe_file {
324343 file. persist ( to) . map_err ( |err| {
325- let error_message = err. to_string ( ) ;
326- // Set back the NamedTempFile returned back by the Error
327- from = Some ( err. file ) ;
328- PersistRetryError :: Persist ( error_message)
344+ let error_message: String = err. to_string ( ) ;
345+ // Set back the `NamedTempFile` returned back by the Error
346+ if let Ok ( mut guard) = from2. lock ( ) {
347+ * guard = Some ( err. file ) ;
348+ PersistRetryError :: Persist ( error_message)
349+ } else {
350+ PersistRetryError :: LostState
351+ }
329352 } )
330353 } else {
331354 Err ( PersistRetryError :: LostState )
0 commit comments