fix(update): detach in-place update from the HTTP request lifetime

The in-place update ran entirely inside c.Request.Context(). Browsers and
reverse proxies commonly abort long-idle requests (axios global timeout
30s, nginx proxy_read_timeout 60s by default), which canceled the request
context mid-download and killed every slow update with
'download failed: context canceled' while the version stayed unchanged.
Users behind slow GitHub links saw the update button fail at a wall-clock
ceiling (~60s) on every attempt (#4504).

- Run PerformUpdate and RollbackToVersion on a context detached from the
  request (context.WithoutCancel) and bounded by a 15-minute deadline so
  the 10-minute GitHub download client owns its own timeout. A client
  disconnect no longer aborts the binary swap; retries then hit the
  system operation lock or report 'Already up to date'.
- Raise the frontend timeout for the update/rollback calls from the
  global 30s axios default to 15 minutes so the browser can actually
  wait for the result.

Fixes #4504
This commit is contained in:
superman2003
2026-07-19 22:40:44 +08:00
parent d4b9797ff7
commit 35b5edb24c
3 changed files with 114 additions and 19 deletions
+13 -2
View File
@@ -61,12 +61,22 @@ export async function getRollbackVersions(): Promise<{ versions: RollbackVersion
return data
}
/**
* In-place update/rollback downloads a full release binary from GitHub, which
* can take several minutes on slow links. The global 30s axios timeout would
* abort the request mid-download (#4504), so these calls wait as long as the
* backend allows (15 minutes server-side).
*/
const UPDATE_REQUEST_TIMEOUT_MS = 15 * 60 * 1000
/**
* Perform system update
* Downloads and applies the latest version
*/
export async function performUpdate(): Promise<UpdateResult> {
const { data } = await apiClient.post<UpdateResult>('/admin/system/update')
const { data } = await apiClient.post<UpdateResult>('/admin/system/update', undefined, {
timeout: UPDATE_REQUEST_TIMEOUT_MS
})
return data
}
@@ -77,7 +87,8 @@ export async function performUpdate(): Promise<UpdateResult> {
export async function rollback(version?: string): Promise<UpdateResult> {
const { data } = await apiClient.post<UpdateResult>(
'/admin/system/rollback',
version ? { version } : undefined
version ? { version } : undefined,
{ timeout: UPDATE_REQUEST_TIMEOUT_MS }
)
return data
}