Guard model-load success path against mid-refresh cancellation (#5944)

* Guard model-load success path against mid-refresh cancellation

* Skip refresh state writes when cancelled during model load
This commit is contained in:
Roland Tannous 2026-06-03 10:18:57 +04:00 committed by GitHub
commit 21ddb71bed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -255,7 +255,8 @@ export function useChatModelRuntime() {
[],
);
const refresh = useCallback(async () => {
const refresh = useCallback(async (options?: { signal?: AbortSignal }) => {
const signal = options?.signal;
setModelsError(null);
try {
const [listRes, statusRes, lorasRes] = await Promise.all([
@ -264,6 +265,11 @@ export function useChatModelRuntime() {
listLoras(),
]);
// Cancellation can land while the requests above are in flight (e.g. the
// user cancels a load during this refresh). Bail before writing any
// backend state back into the store -- cancelLoading already cleared it.
if (signal?.aborted) return;
setModels(listRes.models.map(toChatModelSummary));
setLoras(lorasRes.loras.map(toLoraSummary));
@ -405,6 +411,7 @@ export function useChatModelRuntime() {
});
}
} catch (error) {
if (signal?.aborted) return;
const message =
error instanceof Error ? error.message : "Failed to load models";
setModelsError(message);
@ -761,7 +768,7 @@ export function useChatModelRuntime() {
store.setParams({ ...store.params, ...p });
}
}
await refresh();
await refresh({ signal: abortCtrl.signal });
} catch (error) {
// Skip rollback if user cancelled -- model is already being unloaded.
if (abortCtrl.signal.aborted) throw error;
@ -1056,6 +1063,8 @@ export function useChatModelRuntime() {
try {
await performLoad();
// User cancelled mid-refresh; cancelLoading handles teardown.
if (abortCtrl.signal.aborted) return;
if (loadToastDismissedRef.current) {
toast.success(`${toastDisplayName} loaded`, {
classNames: MODEL_LOADED_TOAST_CLASSNAMES,