From 719a870d349ff255d3888f3cfa5f10189b88d26b Mon Sep 17 00:00:00 2001 From: Igor Randjelovic Date: Fri, 30 Oct 2026 17:23:45 +0100 Subject: [PATCH 1/2] fix(ios): launch the app once when restarting it on a device restartApplication launched the app and then connected to its debug socket with ensureAppStarted, which launched it again with --terminate-existing and killed the first process. The socket now connects to the running instance. A launch waiting for a debugger keeps the second start, since that instance cannot answer the attach request. --- .../livesync/ios-device-livesync-service.ts | 13 ++-- .../livesync/ios-device-livesync-service.ts | 62 +++++++++++++++++++ 2 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 test/services/livesync/ios-device-livesync-service.ts diff --git a/lib/services/livesync/ios-device-livesync-service.ts b/lib/services/livesync/ios-device-livesync-service.ts index 78c3150ff9..8182663889 100644 --- a/lib/services/livesync/ios-device-livesync-service.ts +++ b/lib/services/livesync/ios-device-livesync-service.ts @@ -58,7 +58,8 @@ export class IOSDeviceLiveSyncService } private async setupSocketIfNeeded( - projectData: IProjectData + projectData: IProjectData, + ensureAppStarted: boolean = true ): Promise { // TODO: persist the sockets per app in order to support LiveSync on multiple apps on the same device if (this.socket) { @@ -67,8 +68,6 @@ export class IOSDeviceLiveSyncService const appId = projectData.projectIdentifiers.ios; try { - // TODO: temp workaround till we setup the sockets along with the app start - const ensureAppStarted = true; this.socket = await this.device.getDebugSocket( appId, projectData.projectName, @@ -217,7 +216,13 @@ export class IOSDeviceLiveSyncService if (liveSyncInfo.useHotModuleReload) { // enable HOT updates - await this.setupSocketIfNeeded(projectData); + // Starting the app again here would terminate the instance launched + // above. Only a launch that waits for a debugger still needs it, as + // that instance cannot answer the attach request. + await this.setupSocketIfNeeded( + projectData, + !!liveSyncInfo.waitForDebugger + ); } } diff --git a/test/services/livesync/ios-device-livesync-service.ts b/test/services/livesync/ios-device-livesync-service.ts new file mode 100644 index 0000000000..5ac2b57985 --- /dev/null +++ b/test/services/livesync/ios-device-livesync-service.ts @@ -0,0 +1,62 @@ +import * as assert from "assert"; +import { IOSDeviceLiveSyncService } from "../../../lib/services/livesync/ios-device-livesync-service"; + +describe("IOSDeviceLiveSyncService", () => { + const projectData: any = { + projectName: "app", + projectDir: "/app", + projectIdentifiers: { ios: "org.app" }, + }; + + const restart = async (waitForDebugger: boolean) => { + const calls: string[] = []; + const ensureAppStartedValues: boolean[] = []; + const device: any = { + applicationManager: { + restartApplication: async () => { + calls.push("restart"); + }, + }, + getDebugSocket: async ( + _appId: string, + _projectName: string, + _projectDir: string, + ensureAppStarted: boolean + ) => { + calls.push("socket"); + ensureAppStartedValues.push(ensureAppStarted); + return { on: () => {}, removeAllListeners: () => {} }; + }, + }; + const service = new (IOSDeviceLiveSyncService)( + { trace: () => {} }, + {}, + {}, + {}, + {}, + {}, + device + ); + + await service.restartApplication(projectData, { + deviceAppData: { appIdentifier: "org.app" }, + useHotModuleReload: true, + waitForDebugger, + }); + + return { calls, ensureAppStartedValues }; + }; + + it("connects to the app it restarted without starting it again", async () => { + const { calls, ensureAppStartedValues } = await restart(false); + + assert.deepStrictEqual(calls, ["restart", "socket"]); + assert.deepStrictEqual(ensureAppStartedValues, [false]); + }); + + it("starts the app again when the restarted instance waits for a debugger", async () => { + const { ensureAppStartedValues } = await restart(true); + + assert.deepStrictEqual(ensureAppStartedValues, [true]); + }); +}); From aa9454721b745371744681331ac3828c7038dd80 Mon Sep 17 00:00:00 2001 From: Igor Randjelovic Date: Fri, 30 Oct 2026 22:27:34 +0100 Subject: [PATCH 2/2] fix(ios): keep an app that waits for a debugger running The socket setup started the app a second time for debug-brk, replacing the instance waiting for the debugger with one that ran straight on. --- lib/services/livesync/ios-device-livesync-service.ts | 8 ++------ test/services/livesync/ios-device-livesync-service.ts | 7 ++++--- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/services/livesync/ios-device-livesync-service.ts b/lib/services/livesync/ios-device-livesync-service.ts index 8182663889..8668c0fca0 100644 --- a/lib/services/livesync/ios-device-livesync-service.ts +++ b/lib/services/livesync/ios-device-livesync-service.ts @@ -217,12 +217,8 @@ export class IOSDeviceLiveSyncService if (liveSyncInfo.useHotModuleReload) { // enable HOT updates // Starting the app again here would terminate the instance launched - // above. Only a launch that waits for a debugger still needs it, as - // that instance cannot answer the attach request. - await this.setupSocketIfNeeded( - projectData, - !!liveSyncInfo.waitForDebugger - ); + // above. + await this.setupSocketIfNeeded(projectData, false); } } diff --git a/test/services/livesync/ios-device-livesync-service.ts b/test/services/livesync/ios-device-livesync-service.ts index 5ac2b57985..97e03af8f3 100644 --- a/test/services/livesync/ios-device-livesync-service.ts +++ b/test/services/livesync/ios-device-livesync-service.ts @@ -54,9 +54,10 @@ describe("IOSDeviceLiveSyncService", () => { assert.deepStrictEqual(ensureAppStartedValues, [false]); }); - it("starts the app again when the restarted instance waits for a debugger", async () => { - const { ensureAppStartedValues } = await restart(true); + it("keeps an instance that waits for a debugger", async () => { + const { calls, ensureAppStartedValues } = await restart(true); - assert.deepStrictEqual(ensureAppStartedValues, [true]); + assert.deepStrictEqual(calls, ["restart", "socket"]); + assert.deepStrictEqual(ensureAppStartedValues, [false]); }); });