Skip to content

Support canceling imports - #3387

Open
fakemonster wants to merge 4 commits into
mainfrom
image-import-cancellation
Open

fakemonster wants to merge 4 commits into
mainfrom
image-import-cancellation

Conversation

@fakemonster

Copy link
Copy Markdown
Contributor

This constitutes two closely-related workflows:

  1. Finalizing a disk from import_ready to detached. You can get yourself stuck in import_ready by starting and immediately abandoning an image upload (which creates a temp disk).
  2. Canceling a bulk write and then finalizing a disk, moving from importing_from_bulk_writes to detached. Same activity, but you get to that stuck state by abandoning an image upload after the actual upload starts.

While we won't pretend the states are same when viewing them, "doing something about it" is basically the same thing in both cases, even if the API actions are slightly different.

cancel-import

Resolves #1568

This constitutes two closely-related workflows:

1. Finalizing a disk from `import_ready` to `detached`. You can get
   yourself stuck in `import_ready` by starting and immediately
   abandoning an image upload (which creates a temp disk).
2. Canceling a bulk write and _then_ finalizing a disk, moving from
   `importing_from_bulk_writes` to `detached`. Same activity, but
   you get to that stuck state by abandoning an image upload after the
   actual upload starts.

While we won't pretend the states are same when viewing them, "doing
something about it" is basically the same thing in both cases, even if
the API actions are slightly different.
@vercel

vercel Bot commented Sep 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
console Ready Ready Preview Sep 15, 2026 2:07am UTC

Request Review

@david-crespo

Copy link
Copy Markdown
Collaborator

Would be cool to have an e2e test exercise the try/catch that wraps the action function by failing one of the requests.

Kind of a middle ground between "show two toasts for this action" and
"pretend we aren't making two api calls".
@fakemonster

Copy link
Copy Markdown
Contributor Author

Would be cool to have an e2e test exercise the try/catch that wraps the action function by failing one of the requests.

sure, i'll have one with finalize failing so we can also assert that the state still updates to the middle point

)
},
modalTitle: 'Cancel import',
modalContent: `Are you sure you want to cancel import for ${disk.name}?`,

@david-crespo david-crespo Sep 15, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could also explain what's going to happen. Not sure about the level of detail, could be "It's going to end up detached" or "First it's going to stop the import and then it's going to finalize it." Second seems like too much info.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Are you sure you want to cancel import for disk-1? This will detach your disk" seems confusing. Maybe we flip the script: the action here is generally referred to as "detaching", and the modal says "Are you sure you want to detach disk-1? This will cancel any ongoing import."

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that helps benefit the case we were discussing on the issue: understanding that what you're seeing could be a disk mid-healthy-upload

@david-crespo david-crespo Sep 15, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Detach" as a verb doesn't work here because the importing state is distinct from the attached state, which means attached to an instance. Here there's nothing it's being detached from. What I was trying to do is sort of telegraph the state it would end up in, which is kind of the generic Doing Nothing state, hence its terminal position in the beautiful state graph. Still, unless the user understands that that's what detached is (which we should not assume), telling them that might raise more questions than it answers. So maybe "cancel import" is about as good as it gets. The most literal version would be "get out of the importing state", but that's not really any better unless you're looking at the graph.

Image

) : (
<>Only disks in state {fancifyStates(diskCan.delete.states)} can be deleted</>
)),
})),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the time we like to leave delete there but disabled with a nice message explaining why. I think that would be helpful in the importing case just as it is in the rest of the non-detached states.

path: { disk: disk.name },
query: { project },
body: {},
})

@david-crespo david-crespo Sep 15, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fable had an interesting point: if the disk has changed state, you might get an error trying to cancel import on an already-detached disk, when you could have just done a noop and said "tada!" It suggested fetching the disk again before doing anything to get the latest state. It's not bad, it's kinda cute. And I kind of like the match over the conditional skipping of the first step. Not sure about keeping the message the same whether we did anything or not. It's fine I guess?

const path = { disk: disk.name }
const query = { project }
// The row's state may be stale, e.g., an upload in another tab
// has moved on since the list loaded. Fetch the disk fresh so
// we make the right calls for its actual state.
const fresh = await queryClient.fetchQuery(q(api.diskView, { path, query }))
await match(fresh.state.state)
  .with('importing_from_bulk_writes', async () => {
    await stopBulkWriteImport({ path, query })
    await finalize({ path, query, body: {} })
  })
  .with('import_ready', () => finalize({ path, query, body: {} }))
  // already out of import mode, nothing to do but refresh the list
  .otherwise(() => queryClient.invalidateEndpoint('diskList'))

addToast(
  <>
    Import canceled for <HL>{disk.name}</HL>
  </>
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the disk has changed state, you might get an error

certainly this is the case with any action, right? I assume you can't stop a stopped instance, delete a deleted disk, etc.

beyond that, if we do refetch state, we should do the exact opposite: nothing, and say we did nothing. for instance, the state may have changed because the upload succeeded!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that code, if it does something it's only because the new state is still importing. But yeah, that's a good point, though I think the idea is that some other resources that are liable to change do polling to keep the state relatively up to date. But those are more actively transitional, like instance starting or support bundle collecting. I could definitely live with doing nothing here instead of adding the latency of an extra fetch up front.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

No way to get disk out of bulk import state

2 participants