mirror of
https://github.com/whyour/qinglong.git
synced 2026-10-18 18:15:45 +08:00
fix: allow ARMv6 upgrades enough time to become healthy
This commit is contained in:
@@ -391,7 +391,7 @@ async function runJob({ root, tmp, env = process.env }, id) {
|
||||
);
|
||||
await artifacts.waitForHealth(
|
||||
env,
|
||||
60000,
|
||||
undefined,
|
||||
recover ? undefined : controller.signal,
|
||||
);
|
||||
if (!recover)
|
||||
|
||||
@@ -668,7 +668,16 @@ async function reloadSystem({
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
async function waitForHealth(env, timeout = 60000, signal) {
|
||||
function startupHealthTimeout(runtime = process) {
|
||||
// ARMv6 starts the database, scheduler and HTTP workers sequentially. Give
|
||||
// slow devices time to become healthy before deciding to restore a backup.
|
||||
return runtime.arch === 'arm' &&
|
||||
Number(runtime.config.variables.arm_version) === 6
|
||||
? 300000
|
||||
: 60000;
|
||||
}
|
||||
|
||||
async function waitForHealth(env, timeout = startupHealthTimeout(), signal) {
|
||||
const port = Number(env.QlPort || 5700);
|
||||
let base = env.QlBaseUrl || '';
|
||||
if (base && !base.startsWith('/')) base = `/${base}`;
|
||||
@@ -856,7 +865,7 @@ async function main() {
|
||||
await hook('start', [], recover ? undefined : controller.signal);
|
||||
await waitForHealth(
|
||||
env,
|
||||
60000,
|
||||
undefined,
|
||||
recover ? undefined : controller.signal,
|
||||
);
|
||||
},
|
||||
@@ -903,6 +912,7 @@ module.exports = {
|
||||
selectedUpgrade,
|
||||
replaceAndReload,
|
||||
reloadSystem,
|
||||
startupHealthTimeout,
|
||||
waitForHealth,
|
||||
runProcess,
|
||||
withLifecycleHooks,
|
||||
|
||||
@@ -45,7 +45,7 @@ ARMv6 glibc 小设备(例如 Raspberry Pi 1/Zero 的 Raspbian/Raspberry Pi OS
|
||||
- glibc、musl 分包,并记录构建系统 libc 下限。系统库仍以目标设备加载测试作为最后检查。
|
||||
- ARMv6 glibc 使用签名校验的 Raspbian bullseye(glibc 2.31)构建环境,以固定 SHA256 的 ARMv6 Node 20.18.1 头文件编译 SQLite,强制 `-march=armv6 -mfpu=vfp -mfloat-abi=hard`。CI 检查 ELF 的 ARMv6 和硬浮点标记,并在 QEMU ARM1176 下完成归档复验、SQLite 读写和空目录离线安装;验证辅助镜像按不可变摘要记录,不进入用户安装包。Debian ARMv7 镜像仅提供同次构建的应用文件,不复用其 Node、libc 或 SQLite 二进制。
|
||||
- ARMv6 与 ARMv7 使用不同包名。ARMv6 在目标镜像中从源码编译 SQLite,禁止复用 generic `arm` 的 ARMv7 预编译件;构建需通过加载和内存库读写,失败则不发布渠道。
|
||||
- ARMv6 的归档构建、在线更新工作进程及独立离线安装器使用 `--jitless`,避开 ARM1176 上 V8 的优化编译崩溃;保留现有 Node 二进制,正常面板进程的启动参数不变。依赖加载和 SQLite 读写仍由现有 Node 的正常模式独立进程验证,使 Node 20.18.1 的内置 HTTP/WebAssembly 组件保持可用。
|
||||
- ARMv6 的归档构建、在线更新工作进程及独立离线安装器使用 `--jitless`,避开 ARM1176 上 V8 的优化编译崩溃;保留现有 Node 二进制,正常面板进程的启动参数不变。ARMv6 升级后的健康检查最多等待 5 分钟,覆盖慢设备按顺序启动数据库、调度与 HTTP 工作进程的耗时;其他平台仍为 60 秒,超时或版本校验失败仍执行回退。依赖加载和 SQLite 读写仍由现有 Node 的正常模式独立进程验证,使 Node 20.18.1 的内置 HTTP/WebAssembly 组件保持可用。
|
||||
- tar 中允许 pnpm 的包内符号链接;拒绝绝对路径、越界路径、包外链接、硬链接、设备条目和经符号链接写入的条目。重启前再次校验依赖,暂存失败保留上一次可用的就绪包。
|
||||
|
||||
自托管镜像可设置 `QL_UPGRADE_INDEX_URL` 指向兼容索引;要求 HTTPS,测试时仅放行 loopback HTTP。索引和所有平台归档应部署到同一可访问源,避免依赖额外的 raw 域名。
|
||||
|
||||
@@ -256,7 +256,7 @@ async function installBundle(argv = process.argv.slice(2), env = process.env) {
|
||||
);
|
||||
await artifacts.waitForHealth(
|
||||
environment,
|
||||
60000,
|
||||
undefined,
|
||||
recover ? undefined : controller.signal,
|
||||
);
|
||||
if (!recover)
|
||||
|
||||
@@ -283,3 +283,54 @@ test('unhealthy, malformed and oversized health responses fail within the deadli
|
||||
await assert.rejects(waitForHealth({ QlPort: port }, 20), /health check|后端启动检查失败/);
|
||||
}
|
||||
});
|
||||
|
||||
test('ARMv6 startup can become healthy after the standard one-minute deadline', async (t) => {
|
||||
const { EventEmitter } = require('node:events');
|
||||
const descriptors = ['arch', 'config'].map((key) => [
|
||||
key,
|
||||
Object.getOwnPropertyDescriptor(process, key),
|
||||
]);
|
||||
t.after(() => {
|
||||
for (const [key, descriptor] of descriptors)
|
||||
Object.defineProperty(process, key, descriptor);
|
||||
});
|
||||
Object.defineProperty(process, 'arch', { value: 'arm', configurable: true });
|
||||
Object.defineProperty(process, 'config', {
|
||||
value: { variables: { arm_version: 6 } },
|
||||
configurable: true,
|
||||
});
|
||||
let now = 0;
|
||||
let probes = 0;
|
||||
t.mock.method(Date, 'now', () => now);
|
||||
t.mock.method(http, 'get', (_options, callback) => {
|
||||
const request = new EventEmitter();
|
||||
request.destroy = (error) => request.emit('error', error);
|
||||
queueMicrotask(() => {
|
||||
const response = new EventEmitter();
|
||||
response.statusCode = ++probes === 1 ? 503 : 200;
|
||||
callback(response);
|
||||
now = probes * 70000;
|
||||
response.emit(
|
||||
'data',
|
||||
JSON.stringify({
|
||||
code: response.statusCode,
|
||||
data: { status: probes > 1 ? 'ok' : 'starting' },
|
||||
}),
|
||||
);
|
||||
response.emit('end');
|
||||
});
|
||||
return request;
|
||||
});
|
||||
await waitForHealth({});
|
||||
assert.equal(probes, 2);
|
||||
assert.equal(now, 140000);
|
||||
const { startupHealthTimeout } = require('../../shell/upgrade.cjs');
|
||||
assert.equal(
|
||||
startupHealthTimeout({
|
||||
arch: 'arm',
|
||||
config: { variables: { arm_version: 7 } },
|
||||
}),
|
||||
60000,
|
||||
);
|
||||
assert.equal(startupHealthTimeout({ arch: 'arm64' }), 60000);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user