mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-06 08:06:19 +00:00
fix: reload after updated service is ready
Track the target update version and poll the version endpoint through process replacement. Reload as soon as the new backend is ready without requiring the browser to observe a brief shutdown window. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -2,7 +2,6 @@ import request from '../request'
|
||||
import { API_BASE_URL } from '../constants'
|
||||
|
||||
export const SYSTEM_UPDATE_EVENTS_URL = `${API_BASE_URL}/api/admin/system/update-events`
|
||||
const SYSTEM_HEALTH_URL = `${API_BASE_URL}/healthz`
|
||||
|
||||
export interface SystemVersion {
|
||||
version: string
|
||||
@@ -47,20 +46,14 @@ export interface SystemOperationStarted {
|
||||
needRestart?: boolean
|
||||
}
|
||||
|
||||
export function getSystemVersion() {
|
||||
export function getSystemVersion(options: { timeoutMs?: number } = {}) {
|
||||
return request<SystemVersion>({
|
||||
url: '/api/admin/system/version',
|
||||
method: 'GET',
|
||||
...(options.timeoutMs ? { timeout: options.timeoutMs } : {}),
|
||||
})
|
||||
}
|
||||
|
||||
export async function checkSystemHealth() {
|
||||
const response = await fetch(SYSTEM_HEALTH_URL, { cache: 'no-store' })
|
||||
if (!response.ok) {
|
||||
throw new Error('service is not ready')
|
||||
}
|
||||
}
|
||||
|
||||
export function getSystemUpdateDetail(refresh = false) {
|
||||
return request<SystemUpdateInfo>({
|
||||
url: '/api/admin/system/update-detail',
|
||||
|
||||
@@ -2,7 +2,6 @@ import { computed, ref, shallowRef } from 'vue'
|
||||
|
||||
import {
|
||||
SYSTEM_UPDATE_EVENTS_URL,
|
||||
checkSystemHealth,
|
||||
getSystemVersion,
|
||||
getSystemUpdateDetail,
|
||||
performSystemUpdate,
|
||||
@@ -38,14 +37,14 @@ const updateLogs = ref<SystemUpdateLog[]>([])
|
||||
const updateStreaming = shallowRef(false)
|
||||
const updateStreamError = shallowRef('')
|
||||
const updateEventSource = shallowRef<EventSource | null>(null)
|
||||
const restartTargetVersion = shallowRef('')
|
||||
|
||||
let loadVersionPromise: Promise<SystemVersion> | undefined
|
||||
let loadSystemPromise: Promise<void> | undefined
|
||||
const maxUpdateLogs = 200
|
||||
const restartStopPollAttempts = 50
|
||||
const restartStopPollIntervalMs = 200
|
||||
const restartReadyPollAttempts = 60
|
||||
const restartReadyPollIntervalMs = 1000
|
||||
const restartReadyTimeoutMs = 60_000
|
||||
const restartProbeTimeoutMs = 2_000
|
||||
const restartReadyPollIntervalMs = 500
|
||||
|
||||
const hasUpdate = computed(() => Boolean(updateInfo.value?.hasUpdate ?? version.value?.hasUpdate))
|
||||
const isReleaseBuild = computed(() => updateInfo.value?.buildType === 'release')
|
||||
@@ -63,6 +62,7 @@ function resetUpdateResult() {
|
||||
updateError.value = ''
|
||||
updateSuccess.value = false
|
||||
needRestart.value = false
|
||||
restartTargetVersion.value = ''
|
||||
}
|
||||
|
||||
function isUpdateLog(value: unknown): value is SystemUpdateLog {
|
||||
@@ -206,6 +206,7 @@ async function updateNow(targetVersion: string) {
|
||||
const result = await performSystemUpdate(confirmedTargetVersion)
|
||||
updateSuccess.value = true
|
||||
needRestart.value = result.needRestart
|
||||
restartTargetVersion.value = result.needRestart ? normalizeVersion(result.targetVersion) : ''
|
||||
updateInfo.value = {
|
||||
...currentInfo,
|
||||
latestVersion: result.targetVersion,
|
||||
@@ -233,39 +234,36 @@ function delay(ms: number) {
|
||||
}
|
||||
|
||||
async function waitForServiceAndReload() {
|
||||
let stopped = false
|
||||
for (let attempt = 0; attempt < restartStopPollAttempts; attempt += 1) {
|
||||
try {
|
||||
await checkSystemHealth()
|
||||
} catch {
|
||||
stopped = true
|
||||
break
|
||||
}
|
||||
await delay(restartStopPollIntervalMs)
|
||||
}
|
||||
const expectedVersion = restartTargetVersion.value
|
||||
const deadline = Date.now() + restartReadyTimeoutMs
|
||||
|
||||
if (!stopped) {
|
||||
updateError.value = '未检测到服务停止,请检查重启进程状态'
|
||||
restarting.value = false
|
||||
return
|
||||
}
|
||||
|
||||
for (let attempt = 0; attempt < restartReadyPollAttempts; attempt += 1) {
|
||||
while (Date.now() < deadline) {
|
||||
try {
|
||||
await checkSystemHealth()
|
||||
window.location.reload()
|
||||
return
|
||||
} catch {
|
||||
if (attempt < restartReadyPollAttempts - 1) {
|
||||
await delay(restartReadyPollIntervalMs)
|
||||
const readyVersion = await getSystemVersion({ timeoutMs: restartProbeTimeoutMs })
|
||||
if (normalizeVersion(readyVersion.version) === expectedVersion) {
|
||||
window.location.reload()
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
// 进程切换期间短暂不可达,继续等待目标版本就绪
|
||||
}
|
||||
|
||||
const remainingMs = deadline - Date.now()
|
||||
if (remainingMs > 0) {
|
||||
await delay(Math.min(restartReadyPollIntervalMs, remainingMs))
|
||||
}
|
||||
}
|
||||
|
||||
updateError.value = '服务重启恢复超时,请检查进程是否已被自重启或外部守护进程拉起'
|
||||
updateError.value = `服务未在预期时间内启动 v${expectedVersion}`
|
||||
restarting.value = false
|
||||
}
|
||||
|
||||
function normalizeVersion(value: unknown) {
|
||||
return String(value ?? '')
|
||||
.trim()
|
||||
.replace(/^v/i, '')
|
||||
}
|
||||
|
||||
function apiErrorStatus(error: unknown) {
|
||||
return error instanceof ApiError ? error.status : 0
|
||||
}
|
||||
@@ -273,6 +271,12 @@ function apiErrorStatus(error: unknown) {
|
||||
async function restartNow() {
|
||||
if (restarting.value) return
|
||||
|
||||
if (!restartTargetVersion.value) {
|
||||
const error = new Error('缺少待生效的目标版本')
|
||||
updateError.value = error.message
|
||||
throw error
|
||||
}
|
||||
|
||||
restarting.value = true
|
||||
updateError.value = ''
|
||||
disconnectUpdateEvents()
|
||||
|
||||
Reference in New Issue
Block a user