Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions frontend/src/infrastructure/location/ExpoLocationMonitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,12 +209,15 @@ export class ExpoLocationMonitor implements LocationMonitorPort, LocationProvide
/** 把一次区域同步接到 syncChain 末尾,保证上一次真正执行完(不管成功与否)才轮到
* 这一次——不是排队等着处理各自不同的输入,每次都是重新读 this.watches 当前
* 状态,纯粹为了不让 syncRegions() 并发跑,参照 AssistantContinuousConversationService
* 的 chainPlayback()/playbackChain 同一个模式。 */
* 的 chainPlayback()/playbackChain 同一个模式。.catch(() => {}) 必须紧跟在同一条
* 语句里同步接上——syncRegions() 里 getForegroundPermissionsAsync()/
* getBackgroundPermissionsAsync() 没包 try/catch,哪次真抛了,如果这段失败要
* 等下一次 chainSync() 调用(比如靠 then 的第二个参数)才被接住,中间这段没人
* 接的窗口期会被 Node/Hermes 判定成 unhandled rejection 直接崩进程——不是理论
* 风险,AssistantContinuousConversationService 的 commandResultChain 用一个会
* 抛错的场景实测复现过。 */
private chainSync(): Promise<void> {
this.syncChain = this.syncChain.then(
() => this.syncRegions(),
() => this.syncRegions(),
);
this.syncChain = this.syncChain.then(() => this.syncRegions()).catch(() => {});
return this.syncChain;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -476,5 +476,27 @@ describe('ExpoLocationMonitor', () => {
const monitor = new ExpoLocationMonitor();
await expect(monitor.watch(request(), jest.fn())).resolves.toBeDefined();
});

it('recovers after syncRegions() itself throws instead of leaving the sync chain stuck', async () => {
// getForegroundPermissionsAsync/getBackgroundPermissionsAsync above are not
// wrapped in try/catch, so a real native hiccup makes syncRegions() itself
// reject (not just warn). chainSync() must neutralize that immediately --
// handleAppState's resync is fire-and-forget (void this.chainSync()), so if
// the rejection is left unhandled even briefly, Node/Hermes treats it as an
// unhandled rejection and crashes the process outright (reproduced while
// writing this test, before switching chainSync() to .then().catch(() => {})).
const monitor = new ExpoLocationMonitor();
await monitor.watch(request(), jest.fn());
startGeofencing.mockClear();

getForeground.mockRejectedValueOnce(new Error('native location module hiccup'));
appStateHandler?.('active');
await new Promise((resolve) => setImmediate(resolve));

await expect(
monitor.watch(request({ schedule_id: 'schedule-2' }), jest.fn()),
).resolves.toBeDefined();
expect(startGeofencing).toHaveBeenCalled();
});
});
});
Loading