@@ -184,12 +184,6 @@ export function Component() {
184184 const queryClient = useApiQueryClient ( )
185185 const { project } = useProjectSelector ( )
186186
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-
193187 // The state in this component is very complex because we are doing a bunch of
194188 // requests in order, all of which can fail, plus the whole thing can be
195189 // aborted. We have the usual form state, plus an additional validation step
@@ -250,8 +244,19 @@ export function Component() {
250244 // separate so we can distinguish between cleanup due to error vs. cleanup after success
251245 const stopImportCleanup = useApiMutation ( 'diskBulkWriteImportStop' )
252246 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+ } )
255260
256261 const cleanupMutations = [
257262 stopImportCleanup ,
@@ -270,31 +275,36 @@ export function Component() {
270275 const snapshot = useRef < Snapshot | null > ( null )
271276 const disk = useRef < Disk | null > ( null )
272277
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
281278 function closeModal ( ) {
282279 if ( allDone ) {
283280 backToImages ( )
284281 return
285282 }
286283
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,
288285 // everything is already stopped
289286 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.
290297 cancelEverything ( )
291- // TODO: probably shouldn't await this, but we do need to catch errors
292- cleanup ( )
293298 resetMainFlow ( )
294299 setModalOpen ( false )
295300 }
296301 }
297302
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.
298308 function cancelEverything ( ) {
299309 abortController . current ?. abort ( ABORT_ERROR )
300310 }
@@ -306,14 +316,8 @@ export function Component() {
306316 setSyntheticUploadState ( initSyntheticState )
307317 }
308318
309- const cleaningUp = useRef ( false )
310-
311319 /** If a snapshot or disk was created, clean it up*/
312320 async function cleanup ( ) {
313- // don't run if already running
314- if ( cleaningUp . current ) return
315- cleaningUp . current = true
316-
317321 if ( snapshot . current ) {
318322 await deleteSnapshotCleanup . mutateAsync ( { path : { snapshot : snapshot . current . id } } )
319323 snapshot . current = null
@@ -335,7 +339,6 @@ export function Component() {
335339 await deleteDiskCleanup . mutateAsync ( { path : { disk : disk . current . id } } )
336340 disk . current = null
337341 }
338- cleaningUp . current = false
339342 }
340343
341344 async function onSubmit ( {
@@ -500,10 +503,6 @@ export function Component() {
500503 title = "Upload image"
501504 onDismiss = { backToImages }
502505 onSubmit = { async ( values ) => {
503- // every submit needs its own AbortController because they can't be
504- // reset
505- abortController . current = new AbortController ( )
506-
507506 setFormError ( null )
508507
509508 // check that image name isn't taken before starting the whole thing
@@ -532,16 +531,28 @@ export function Component() {
532531 return
533532 }
534533
534+ // every submit needs its own AbortController because they can't be
535+ // reset
536+ abortController . current = new AbortController ( )
537+
535538 try {
536539 await onSubmit ( values )
537540 } catch ( e ) {
538541 if ( e !== ABORT_ERROR ) {
539542 setModalError ( 'Something went wrong. Please try again.' )
543+ // abort anything in flight in case
544+ cancelEverything ( )
540545 }
541- cancelEverything ( )
542546 // user canceled
543547 await cleanup ( )
544548 // TODO: if we get here, show failure state in the upload modal
549+ } finally {
550+ // Clear the abort controller. This is aimed at the case where the
551+ // user clicks cancel and then stares at the confirm modal without
552+ // clicking for so long that the upload manages to finish, which means
553+ // there's no longer anything to cancel. If abortController is gone,
554+ // cancelEverything is a noop.
555+ abortController . current = null
545556 }
546557 } }
547558 loading = { formLoading }
0 commit comments