@@ -184,12 +184,6 @@ export function Component() {
184
184
const queryClient = useApiQueryClient ( )
185
185
const { project } = useProjectSelector ( )
186
186
187
- // Note: abort currently only works if it fires during the upload file step.
188
- // We could make it work between the other steps by calling
189
- // `abortController.throwIfAborted()` after each one. We could technically
190
- // plumb through the signal to the requests themselves, but they complete so
191
- // quickly it's probably not necessary.
192
-
193
187
// The state in this component is very complex because we are doing a bunch of
194
188
// requests in order, all of which can fail, plus the whole thing can be
195
189
// aborted. We have the usual form state, plus an additional validation step
@@ -250,8 +244,19 @@ export function Component() {
250
244
// separate so we can distinguish between cleanup due to error vs. cleanup after success
251
245
const stopImportCleanup = useApiMutation ( 'diskBulkWriteImportStop' )
252
246
const finalizeDiskCleanup = useApiMutation ( 'diskFinalizeImport' )
253
- const deleteDiskCleanup = useApiMutation ( 'diskDelete' )
254
- const deleteSnapshotCleanup = useApiMutation ( 'snapshotDelete' )
247
+ // in production these invalidations are unlikely to matter, but they help a
248
+ // lot in the tests when we check the disk list after canceling to make sure
249
+ // the temp resources got deleted
250
+ const deleteDiskCleanup = useApiMutation ( 'diskDelete' , {
251
+ onSuccess ( ) {
252
+ queryClient . invalidateQueries ( 'diskList' )
253
+ } ,
254
+ } )
255
+ const deleteSnapshotCleanup = useApiMutation ( 'snapshotDelete' , {
256
+ onSuccess ( ) {
257
+ queryClient . invalidateQueries ( 'snapshotList' )
258
+ } ,
259
+ } )
255
260
256
261
const cleanupMutations = [
257
262
stopImportCleanup ,
@@ -270,31 +275,36 @@ export function Component() {
270
275
const snapshot = useRef < Snapshot | null > ( null )
271
276
const disk = useRef < Disk | null > ( null )
272
277
273
- // if closeModal runs during bulk upload due to a cancel, cancelEverything
274
- // causes an abort of the bulk upload, which throws an error to onSubmit's
275
- // catch, which calls `cleanup`. so when we call cleanup here, it will be a
276
- // double cleanup. we could get rid of this one, but for the rare cancel *not*
277
- // during bulk upload we will still want to call cleanup. rather than
278
- // coordinating when to cleanup, we make cleanup idempotent by having it check
279
- // whether it has already been run, or more concretely before each action,
280
- // check whether it needs to be done
281
278
function closeModal ( ) {
282
279
if ( allDone ) {
283
280
backToImages ( )
284
281
return
285
282
}
286
283
287
- // if we're still going, need to confirm cancelation . if we have an error,
284
+ // if we're still going, need to confirm cancellation . if we have an error,
288
285
// everything is already stopped
289
286
if ( modalError || confirm ( 'Are you sure? Closing the modal will cancel the upload.' ) ) {
287
+ // Note we don't run cleanup() here -- cancelEverything triggers an
288
+ // abort, which gets caught by the try/catch in the onSubmit on the upload
289
+ // form, which does the cleanup. We used to call cleanup here and used
290
+ // error-prone state logic to avoid it running twice.
291
+ //
292
+ // Because we are working with a closed-over copy of allDone, there is
293
+ // a possibility that the upload finishes while the user is looking at
294
+ // the confirm modal, in which case cancelEverything simply won't do
295
+ // anything. The finally{} in onSubmit clears out the abortController so
296
+ // cancelEverything() is a noop.
290
297
cancelEverything ( )
291
- // TODO: probably shouldn't await this, but we do need to catch errors
292
- cleanup ( )
293
298
resetMainFlow ( )
294
299
setModalOpen ( false )
295
300
}
296
301
}
297
302
303
+ // Aborting works for steps other than file upload despite the
304
+ // signal not being used directly in the former because we call
305
+ // `abortController.throwIfAborted()` after each step. We could technically
306
+ // plumb through the signal to the requests themselves, but they complete so
307
+ // quickly it's probably not necessary.
298
308
function cancelEverything ( ) {
299
309
abortController . current ?. abort ( ABORT_ERROR )
300
310
}
@@ -306,14 +316,8 @@ export function Component() {
306
316
setSyntheticUploadState ( initSyntheticState )
307
317
}
308
318
309
- const cleaningUp = useRef ( false )
310
-
311
319
/** If a snapshot or disk was created, clean it up*/
312
320
async function cleanup ( ) {
313
- // don't run if already running
314
- if ( cleaningUp . current ) return
315
- cleaningUp . current = true
316
-
317
321
if ( snapshot . current ) {
318
322
await deleteSnapshotCleanup . mutateAsync ( { path : { snapshot : snapshot . current . id } } )
319
323
snapshot . current = null
@@ -335,7 +339,6 @@ export function Component() {
335
339
await deleteDiskCleanup . mutateAsync ( { path : { disk : disk . current . id } } )
336
340
disk . current = null
337
341
}
338
- cleaningUp . current = false
339
342
}
340
343
341
344
async function onSubmit ( {
@@ -500,10 +503,6 @@ export function Component() {
500
503
title = "Upload image"
501
504
onDismiss = { backToImages }
502
505
onSubmit = { async ( values ) => {
503
- // every submit needs its own AbortController because they can't be
504
- // reset
505
- abortController . current = new AbortController ( )
506
-
507
506
setFormError ( null )
508
507
509
508
// check that image name isn't taken before starting the whole thing
@@ -532,17 +531,29 @@ export function Component() {
532
531
return
533
532
}
534
533
534
+ // every submit needs its own AbortController because they can't be
535
+ // reset
536
+ abortController . current = new AbortController ( )
537
+
535
538
try {
536
539
await onSubmit ( values )
537
540
} catch ( e ) {
538
541
if ( e !== ABORT_ERROR ) {
539
542
console . error ( e )
540
543
setModalError ( 'Something went wrong. Please try again.' )
544
+ // abort anything in flight in case
545
+ cancelEverything ( )
541
546
}
542
- cancelEverything ( )
543
547
// user canceled
544
548
await cleanup ( )
545
549
// TODO: if we get here, show failure state in the upload modal
550
+ } finally {
551
+ // Clear the abort controller. This is aimed at the case where the
552
+ // user clicks cancel and then stares at the confirm modal without
553
+ // clicking for so long that the upload manages to finish, which means
554
+ // there's no longer anything to cancel. If abortController is gone,
555
+ // cancelEverything is a noop.
556
+ abortController . current = null
546
557
}
547
558
} }
548
559
loading = { formLoading }
0 commit comments