fix(ui): shorten install/uninstall/update timeouts for async RPCs
With the backend flipped to async-spawn, install/uninstall/update return
immediately with a { status, package_id } envelope. Client timeouts of
45m/11m were a leftover from synchronous handlers and masked real RPC
failures.
Drop all install/uninstall/update RPC timeouts to 15s. Progress and
terminal state still arrive through the live state stream — the RPC
only needs to confirm the spawn was accepted.
Return-type annotations updated in rpc-client.ts and stores/server.ts.
Five direct rpcClient.call sites across Marketplace.vue, Discover.vue,
and MarketplaceAppDetails.vue updated with the shorter timeout.
This commit is contained in:
parent
1ad889608f
commit
702b5d64d3
@ -521,23 +521,30 @@ class RPCClient {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async installPackage(id: string, marketplaceUrl: string, version: string): Promise<string> {
|
async installPackage(
|
||||||
|
id: string,
|
||||||
|
marketplaceUrl: string,
|
||||||
|
version: string,
|
||||||
|
): Promise<{ status: string; package_id: string }> {
|
||||||
|
// Backend is async — returns { status: 'installing' } in <1s after
|
||||||
|
// flipping state and spawning the pull/install pipeline. Progress is
|
||||||
|
// streamed via WebSocket (install_progress field on the package entry).
|
||||||
return this.call({
|
return this.call({
|
||||||
method: 'package.install',
|
method: 'package.install',
|
||||||
params: { id, 'marketplace-url': marketplaceUrl, version },
|
params: { id, 'marketplace-url': marketplaceUrl, version },
|
||||||
// 45 min — IndeedHub is 6 images and gitea raw-file throughput is
|
timeout: 15000,
|
||||||
// ~70 KB/s per image; 15 min was short enough to kill the install
|
|
||||||
// mid-pull and land the user on a "didn't work" screen while the
|
|
||||||
// backend kept working in the background.
|
|
||||||
timeout: 2700000,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async uninstallPackage(id: string): Promise<void> {
|
async uninstallPackage(id: string): Promise<{ status: string; package_id: string }> {
|
||||||
|
// Backend is async — returns { status: 'removing' } immediately after
|
||||||
|
// flipping state. Graceful stop (up to 600s for bitcoin) and data wipe
|
||||||
|
// (up to minutes for large chainstate) run in a background task.
|
||||||
|
// Progress shown via uninstall_stage field on the package entry.
|
||||||
return this.call({
|
return this.call({
|
||||||
method: 'package.uninstall',
|
method: 'package.uninstall',
|
||||||
params: { id },
|
params: { id },
|
||||||
timeout: 660000, // Bitcoin Knots needs up to 600s for UTXO flush
|
timeout: 15000,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -545,7 +552,7 @@ class RPCClient {
|
|||||||
return this.call({
|
return this.call({
|
||||||
method: 'package.start',
|
method: 'package.start',
|
||||||
params: { id },
|
params: { id },
|
||||||
timeout: 60000,
|
timeout: 15000,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -553,7 +560,7 @@ class RPCClient {
|
|||||||
return this.call({
|
return this.call({
|
||||||
method: 'package.stop',
|
method: 'package.stop',
|
||||||
params: { id },
|
params: { id },
|
||||||
timeout: 120000,
|
timeout: 15000,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -561,15 +568,18 @@ class RPCClient {
|
|||||||
return this.call({
|
return this.call({
|
||||||
method: 'package.restart',
|
method: 'package.restart',
|
||||||
params: { id },
|
params: { id },
|
||||||
timeout: 120000,
|
timeout: 15000,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async updatePackage(id: string): Promise<{ status: string }> {
|
async updatePackage(id: string): Promise<{ status: string; package_id: string }> {
|
||||||
|
// Backend is async — returns { status: 'updating' } immediately after
|
||||||
|
// flipping state. Pull / stop / recreate / verify runs in background,
|
||||||
|
// with rollback-on-failure.
|
||||||
return this.call({
|
return this.call({
|
||||||
method: 'package.update',
|
method: 'package.update',
|
||||||
params: { id },
|
params: { id },
|
||||||
timeout: 660000, // Bitcoin Knots needs up to 600s for graceful shutdown
|
timeout: 15000,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -100,12 +100,19 @@ export const useServerStore = defineStore('server', () => {
|
|||||||
const isShuttingDown = computed(() => sync.serverInfo?.['status-info']?.['shutting-down'] || false)
|
const isShuttingDown = computed(() => sync.serverInfo?.['status-info']?.['shutting-down'] || false)
|
||||||
const isOffline = computed(() => !sync.isConnected || isRestarting.value || isShuttingDown.value)
|
const isOffline = computed(() => !sync.isConnected || isRestarting.value || isShuttingDown.value)
|
||||||
|
|
||||||
// Package actions
|
// Package actions. install/uninstall/update are async on the backend:
|
||||||
async function installPackage(id: string, marketplaceUrl: string, version: string): Promise<string> {
|
// the RPC returns immediately with { status: 'installing'|'removing'|'updating',
|
||||||
|
// package_id } after flipping state, and the real work runs in a spawn.
|
||||||
|
// Progress is streamed via the WebSocket state push, not the RPC response.
|
||||||
|
async function installPackage(
|
||||||
|
id: string,
|
||||||
|
marketplaceUrl: string,
|
||||||
|
version: string,
|
||||||
|
): Promise<{ status: string; package_id: string }> {
|
||||||
return rpcClient.installPackage(id, marketplaceUrl, version)
|
return rpcClient.installPackage(id, marketplaceUrl, version)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function uninstallPackage(id: string): Promise<void> {
|
async function uninstallPackage(id: string): Promise<{ status: string; package_id: string }> {
|
||||||
return rpcClient.uninstallPackage(id)
|
return rpcClient.uninstallPackage(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,7 +128,7 @@ export const useServerStore = defineStore('server', () => {
|
|||||||
return rpcClient.restartPackage(id)
|
return rpcClient.restartPackage(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updatePackage(id: string): Promise<{ status: string }> {
|
async function updatePackage(id: string): Promise<{ status: string; package_id: string }> {
|
||||||
return rpcClient.updatePackage(id)
|
return rpcClient.updatePackage(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -489,7 +489,7 @@ async function installApp(app: MarketplaceApp) {
|
|||||||
try {
|
try {
|
||||||
const installUrl = app.url || app.manifestUrl || app.s9pkUrl
|
const installUrl = app.url || app.manifestUrl || app.s9pkUrl
|
||||||
installingApps.set(app.id, { ...installingApps.get(app.id)!, status: 'downloading', progress: 30, message: 'Downloading package...' })
|
installingApps.set(app.id, { ...installingApps.get(app.id)!, status: 'downloading', progress: 30, message: 'Downloading package...' })
|
||||||
await rpcClient.call({ method: 'package.install', params: { id: app.id, url: installUrl, version: app.version } })
|
await rpcClient.call({ method: 'package.install', params: { id: app.id, url: installUrl, version: app.version }, timeout: 15000 })
|
||||||
installingApps.set(app.id, { ...installingApps.get(app.id)!, status: 'installing', progress: 60, message: 'Installing package...' })
|
installingApps.set(app.id, { ...installingApps.get(app.id)!, status: 'installing', progress: 60, message: 'Installing package...' })
|
||||||
startInstallPolling(app.id, 'Starting application...')
|
startInstallPolling(app.id, 'Starting application...')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -511,7 +511,7 @@ async function installCommunityApp(app: MarketplaceApp) {
|
|||||||
if ((app as Record<string, unknown>).containerConfig) {
|
if ((app as Record<string, unknown>).containerConfig) {
|
||||||
installParams.containerConfig = (app as Record<string, unknown>).containerConfig
|
installParams.containerConfig = (app as Record<string, unknown>).containerConfig
|
||||||
}
|
}
|
||||||
await rpcClient.call({ method: 'package.install', params: installParams, timeout: 180000 })
|
await rpcClient.call({ method: 'package.install', params: installParams, timeout: 15000 })
|
||||||
installingApps.set(app.id, { ...installingApps.get(app.id)!, status: 'installing', progress: 60, message: 'Starting container...' })
|
installingApps.set(app.id, { ...installingApps.get(app.id)!, status: 'installing', progress: 60, message: 'Starting container...' })
|
||||||
startInstallPolling(app.id, 'Initializing application...')
|
startInstallPolling(app.id, 'Initializing application...')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@ -418,7 +418,7 @@ async function installApp(app: MarketplaceApp) {
|
|||||||
|
|
||||||
installingApps.set(app.id, { ...installingApps.get(app.id)!, status: 'downloading', progress: 30, message: 'Downloading package...' })
|
installingApps.set(app.id, { ...installingApps.get(app.id)!, status: 'downloading', progress: 30, message: 'Downloading package...' })
|
||||||
|
|
||||||
await rpcClient.call({ method: 'package.install', params: { id: app.id, url: installUrl, version: app.version } })
|
await rpcClient.call({ method: 'package.install', params: { id: app.id, url: installUrl, version: app.version }, timeout: 15000 })
|
||||||
|
|
||||||
installingApps.set(app.id, { ...installingApps.get(app.id)!, status: 'installing', progress: 60, message: 'Installing package...' })
|
installingApps.set(app.id, { ...installingApps.get(app.id)!, status: 'installing', progress: 60, message: 'Installing package...' })
|
||||||
|
|
||||||
@ -447,7 +447,7 @@ async function installCommunityApp(app: MarketplaceApp) {
|
|||||||
await rpcClient.call({
|
await rpcClient.call({
|
||||||
method: 'package.install',
|
method: 'package.install',
|
||||||
params: { id: app.id, dockerImage: app.dockerImage, version: app.version },
|
params: { id: app.id, dockerImage: app.dockerImage, version: app.version },
|
||||||
timeout: 180000
|
timeout: 15000
|
||||||
})
|
})
|
||||||
|
|
||||||
installingApps.set(app.id, { ...installingApps.get(app.id)!, status: 'installing', progress: 60, message: 'Starting container...' })
|
installingApps.set(app.id, { ...installingApps.get(app.id)!, status: 'installing', progress: 60, message: 'Starting container...' })
|
||||||
|
|||||||
@ -546,7 +546,7 @@ async function installDependencies() {
|
|||||||
id: dep.id,
|
id: dep.id,
|
||||||
dockerImage: dep.dockerImage,
|
dockerImage: dep.dockerImage,
|
||||||
},
|
},
|
||||||
timeout: 180000,
|
timeout: 15000,
|
||||||
})
|
})
|
||||||
// Wait for package to register before installing next
|
// Wait for package to register before installing next
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||||
@ -579,7 +579,7 @@ async function installApp() {
|
|||||||
dockerImage: app.value.dockerImage,
|
dockerImage: app.value.dockerImage,
|
||||||
version: app.value.version,
|
version: app.value.version,
|
||||||
},
|
},
|
||||||
timeout: 180000,
|
timeout: 15000,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// Package-based installation
|
// Package-based installation
|
||||||
@ -591,6 +591,7 @@ async function installApp() {
|
|||||||
url: installUrl,
|
url: installUrl,
|
||||||
version: app.value.version,
|
version: app.value.version,
|
||||||
},
|
},
|
||||||
|
timeout: 15000,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user