Skip to content

Commit 33dc6cb

Browse files
committed
fix: resolve third native check review follow-ups
1 parent 60f5fd2 commit 33dc6cb

12 files changed

Lines changed: 209 additions & 70 deletions

File tree

NATIVE_CHECK_FOLLOWUPS.md

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@
2525
| P4 endpoint 斜杠重复 | 已修复 | 三端首轮请求也先查规范化后的 `tried` 集合,重复项不计入 8 次上限 |
2626
| P4 Harmony 整请求超时 | 已修复 | check HTTP 增加 15s whole-call cap;更新下载增加绝对 deadline 并在超时后销毁请求 |
2727

28-
代码验证基线:JS 完整回归 172 项、Biome/TypeScript/Harmony strict 类型检查、
29-
77 项 flow core ASan/UBSan、29 项 patch core、Android Release Java 编译、
30-
iOS Release simulator 构建均通过
28+
代码验证基线:JS 完整回归 173 项、Biome/TypeScript/Harmony strict 类型检查、
29+
77 项 flow core ASan/UBSan、29 项 patch core、Harmony debug HAR、Android
30+
Release Java 编译、iOS Release simulator 静态库构建均通过
3131

3232
---
3333

@@ -36,6 +36,20 @@ iOS Release simulator 构建均通过。
3636
上表 13 项的修复经复评确认全部属实;以下为修复自身引入/暴露的新开放项
3737
(详情与逐条修法见评审面板)。
3838

39+
复核后处理结论:
40+
41+
|| 结论 | 落地方式 |
42+
|---|---|---|
43+
| 1 iOS joiner 预算 | 已修复 | 注册表记录 owner 的单调时钟 deadline;预算更长的 waiter 观察当前进度但 deferred,owner 成功时由完成标记立即命中,失败时以自己的完整预算重启 |
44+
| 2 Harmony 外层时限 | 已修复 | `performAttempts` 用绝对单调 deadline 包住排队、HTTP、解压和 hpatch 的完整 Promise;底层串行任务即使晚结束也不再阻止编排器落响应缓存 |
45+
| 3 壁钟 deadline | 已修复并修正文档结论 | iOS 改用 `systemUptime`,Harmony 改用 `systemDateTime.getUptime`;full 预算进入 full 阶段才创建,因此原文“增量阶段校时会同时耗尽尚未创建的 full 预算”不成立 |
46+
| 4 Android full 判定 | 已修复 | 与分发逻辑及 iOS/Harmony 一致,非 diff/pdiff 统一视为 full;上游当前只生成三种合法类型,此项属于防御性收口 |
47+
| 5 坏发布遥测膨胀 | 已修复 | 缺 hash 与 noArtifact 共用按 appKey/reason/hash 的进程内去重,保留一次服务端可见的坏发布信号 |
48+
| 6 有 hash 无产物弹窗 | 已修复 | Provider 在展示/静默下载前复用 `decideDownload`,noArtifact 降级为 `upToDate` 与一次开发者遥测 |
49+
| 7 deferred UX/deadline | 已修复 | deferred waiter 订阅当前同 hash 进度;旧 deadline 在重新注册前校验,过期的编排器请求不会成为 owner 或结算后来的 JS 请求 |
50+
| 8 owner-only 进度事件 | 不采纳 | 支持路径在 JS 已按 hash 维持单一原生监听;常见 join 是无监听器的冷启动 engine + JS bridge。限制为 owner 发事件会在 engine 先成为 owner 时丢失 JS 进度 |
51+
| 9 iOS 完成判定重复 | 已修复 | 抽取 `PushyHasCompletedVersionAtPath`,预检与冷启动编排器共用同一 bundle+marker 判定 |
52+
3953
**P2(三端时限模型的二阶问题,建议一并收口)**
4054
1. **iOS 合流者继承 owner 剩余预算**:JS 同 hash 同类型合流到编排器下载时,
4155
共享会话带的是编排器所剩阶段预算(可能只剩几十秒),其超时会结算全部

android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ private static boolean performAttempts(
366366
// diff patches from the running version; none is running.
367367
continue;
368368
}
369-
final boolean isFullAttempt = "full".equals(type);
369+
final boolean isFullAttempt = !"diff".equals(type) && !"pdiff".equals(type);
370370
if (isFullAttempt && fullDeadlineNanos == 0) {
371371
// Incremental failures must not consume the last-resort full
372372
// download's budget. Each phase gets one bounded 10min window.

harmony/pushy/src/main/ets/DownloadTask.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import NativePatchCore, {
1111
ARCHIVE_PATCH_TYPE_FROM_PPK,
1212
CopyGroupResult,
1313
} from './NativePatchCore';
14+
import { monotonicNowMs } from './MonotonicClock';
1415

1516
export const VERSION_COMPLETE_FILE_NAME = '.pushy-complete';
1617

@@ -346,10 +347,10 @@ export class DownloadTask {
346347
let writeQueue = Promise.resolve();
347348
let lastReportedPercentage = -1;
348349
let lastReportedBytes = 0;
349-
const deadlineAtMs = params.deadlineAtMs > 0
350-
? params.deadlineAtMs
351-
: Date.now() + DOWNLOAD_CALL_TIMEOUT_MS;
352-
if (deadlineAtMs <= Date.now()) {
350+
const deadlineUptimeMs = params.deadlineUptimeMs > 0
351+
? params.deadlineUptimeMs
352+
: monotonicNowMs() + DOWNLOAD_CALL_TIMEOUT_MS;
353+
if (deadlineUptimeMs <= monotonicNowMs()) {
353354
throw Error('Download deadline expired before start');
354355
}
355356

@@ -495,7 +496,7 @@ export class DownloadTask {
495496
const deadlinePromise = new Promise<never>((_, reject) => {
496497
deadlineTimer = setTimeout(() => {
497498
reject(Error('Download exceeded its whole-call deadline'));
498-
}, Math.max(1, deadlineAtMs - Date.now()));
499+
}, Math.max(1, deadlineUptimeMs - monotonicNowMs()));
499500
});
500501
const responseCode = await Promise.race([
501502
httpRequest.requestInStream(params.url, {

harmony/pushy/src/main/ets/DownloadTaskParams.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export class DownloadTaskParams {
1616
targetFile: string = ''; // 目标文件路径
1717
unzipDirectory: string = ''; // 解压目录路径
1818
originDirectory: string = ''; // 原始文件目录路径
19-
// Native cold-start orchestrator's absolute wall-clock deadline. Zero uses
20-
// the normal public download API's 10-minute whole-call cap.
21-
deadlineAtMs: number = 0;
19+
// Native cold-start orchestrator's absolute monotonic-uptime deadline. Zero
20+
// uses the normal public download API's 10-minute whole-call cap.
21+
deadlineUptimeMs: number = 0;
2222
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { systemDateTime } from '@kit.BasicServicesKit';
2+
3+
// Absolute deadlines must not use Date.now(): automatic time synchronization
4+
// can move the wall clock while the cold-start rescue round is running.
5+
export function monotonicNowMs(): number {
6+
return systemDateTime.getUptime(systemDateTime.TimeType.STARTUP);
7+
}

harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import logger from './Logger';
44
import NativePatchCore from './NativePatchCore';
55
import type { UpdateContext } from './UpdateContext';
66
import { isSafePathComponent } from './PathUtils';
7+
import { monotonicNowMs } from './MonotonicClock';
78

89
// 原生冷启动检测(NATIVE_CHECKUPDATE_DESIGN §10):每进程一次,getBundleUrl
910
// 后延迟数秒运行,完全不依赖 app bundle——坏热更把 JS 砸挂后,下次启动仍能
@@ -409,13 +410,37 @@ function normalizeEndpointBase(base: string): string {
409410
return base.replace(/\/+$/, '');
410411
}
411412

413+
async function runWithinDeadline(
414+
start: () => Promise<void>,
415+
deadlineUptimeMs: number,
416+
): Promise<void> {
417+
const remainingMs = deadlineUptimeMs - monotonicNowMs();
418+
if (remainingMs <= 0) {
419+
throw Error('Download phase deadline expired before start');
420+
}
421+
let deadlineTimer = 0;
422+
const deadlinePromise = new Promise<void>((_, reject) => {
423+
deadlineTimer = setTimeout(() => {
424+
reject(Error('Download phase deadline exceeded'));
425+
}, remainingMs);
426+
});
427+
try {
428+
// This bounds queueing, HTTP, decompression and native hpatch work from
429+
// the orchestrator's perspective. The serialized task may still finish
430+
// later, but it can no longer prevent the response cache from settling.
431+
await Promise.race([start(), deadlinePromise]);
432+
} finally {
433+
clearTimeout(deadlineTimer);
434+
}
435+
}
436+
412437
async function performAttempts(
413438
context: UpdateContext,
414439
attempts: DecisionAttempt[],
415440
hash: string,
416441
originHash: string,
417442
): Promise<boolean> {
418-
const incrementalDeadline = Date.now() + DOWNLOAD_PHASE_TIMEOUT_MS;
443+
const incrementalDeadline = monotonicNowMs() + DOWNLOAD_PHASE_TIMEOUT_MS;
419444
let fullDeadline = 0;
420445
for (const attempt of attempts) {
421446
const type = attempt.type ?? '';
@@ -428,26 +453,35 @@ async function performAttempts(
428453
if (isFullAttempt && fullDeadline === 0) {
429454
// Preserve a full 10min rescue budget even when diff/pdiff exhausted
430455
// their own phase window.
431-
fullDeadline = Date.now() + DOWNLOAD_PHASE_TIMEOUT_MS;
456+
fullDeadline = monotonicNowMs() + DOWNLOAD_PHASE_TIMEOUT_MS;
432457
}
433458
const deadline = isFullAttempt ? fullDeadline : incrementalDeadline;
434459
for (const url of attempt.urls ?? []) {
435460
if (!url) {
436461
continue;
437462
}
438-
if (Date.now() >= deadline) {
463+
if (monotonicNowMs() >= deadline) {
439464
if (isFullAttempt) {
440465
return false;
441466
}
442467
break;
443468
}
444469
try {
445470
if (type === DOWNLOAD_TYPE_DIFF) {
446-
await context.downloadPatchFromPpk(url, hash, originHash, deadline);
471+
await runWithinDeadline(
472+
() => context.downloadPatchFromPpk(url, hash, originHash, deadline),
473+
deadline,
474+
);
447475
} else if (type === DOWNLOAD_TYPE_PDIFF) {
448-
await context.downloadPatchFromPackage(url, hash, deadline);
476+
await runWithinDeadline(
477+
() => context.downloadPatchFromPackage(url, hash, deadline),
478+
deadline,
479+
);
449480
} else {
450-
await context.downloadFullUpdate(url, hash, deadline);
481+
await runWithinDeadline(
482+
() => context.downloadFullUpdate(url, hash, deadline),
483+
deadline,
484+
);
451485
}
452486
return true;
453487
} catch (e) {

harmony/pushy/src/main/ets/UpdateContext.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ export class UpdateContext {
467467
public async downloadFullUpdate(
468468
url: string,
469469
hash: string,
470-
deadlineAtMs: number = 0,
470+
deadlineUptimeMs: number = 0,
471471
): Promise<void> {
472472
try {
473473
const params = this.createTaskParams(
@@ -477,7 +477,7 @@ export class UpdateContext {
477477
);
478478
params.targetFile = `${this.rootDir}/${hash}.ppk`;
479479
params.unzipDirectory = `${this.rootDir}/${hash}`;
480-
params.deadlineAtMs = deadlineAtMs;
480+
params.deadlineUptimeMs = deadlineUptimeMs;
481481
await this.executeTask(params);
482482
} catch (e) {
483483
console.error('Failed to download full update:', e);
@@ -503,7 +503,7 @@ export class UpdateContext {
503503
url: string,
504504
hash: string,
505505
originHash: string,
506-
deadlineAtMs: number = 0,
506+
deadlineUptimeMs: number = 0,
507507
): Promise<void> {
508508
const params = this.createTaskParams(
509509
DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK,
@@ -514,14 +514,14 @@ export class UpdateContext {
514514
params.targetFile = `${this.rootDir}/${originHash}_${hash}.ppk.patch`;
515515
params.unzipDirectory = `${this.rootDir}/${hash}`;
516516
params.originDirectory = `${this.rootDir}/${params.originHash}`;
517-
params.deadlineAtMs = deadlineAtMs;
517+
params.deadlineUptimeMs = deadlineUptimeMs;
518518
await this.executeTask(params);
519519
}
520520

521521
public async downloadPatchFromPackage(
522522
url: string,
523523
hash: string,
524-
deadlineAtMs: number = 0,
524+
deadlineUptimeMs: number = 0,
525525
): Promise<void> {
526526
try {
527527
const params = this.createTaskParams(
@@ -531,7 +531,7 @@ export class UpdateContext {
531531
);
532532
params.targetFile = `${this.rootDir}/${hash}.app.patch`;
533533
params.unzipDirectory = `${this.rootDir}/${hash}`;
534-
params.deadlineAtMs = deadlineAtMs;
534+
params.deadlineUptimeMs = deadlineUptimeMs;
535535
return await this.executeTask(params);
536536
} catch (e) {
537537
console.error('Failed to download package patch:', e);

0 commit comments

Comments
 (0)