diff --git a/CHANGELOG.md b/CHANGELOG.md index 89368d3bfd..7255014731 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ This is the log of notable changes to EAS CLI and related packages. ### ๐Ÿ› Bug fixes - [build-tools] Fix `eas/start_ios_simulator` hanging on Xcode 26.4 by writing the readiness screenshot to a temp file instead of `/dev/null`. ([#3794](https://github.com/expo/eas-cli/pull/3794) by [@gwdp](https://github.com/gwdp)) +- [build-tools] Disable `apsd` in the iOS Simulator after boot to stop a push-registration log storm that pegs `diagnosticd` CPU for the lifetime of the Simulator. ([#3795](https://github.com/expo/eas-cli/pull/3795) by [@gwdp](https://github.com/gwdp)) ### ๐Ÿงน Chores diff --git a/packages/build-tools/src/steps/functions/__tests__/startIosSimulator.test.ts b/packages/build-tools/src/steps/functions/__tests__/startIosSimulator.test.ts new file mode 100644 index 0000000000..d28b3b8ec4 --- /dev/null +++ b/packages/build-tools/src/steps/functions/__tests__/startIosSimulator.test.ts @@ -0,0 +1,76 @@ +import spawn from '@expo/turtle-spawn'; + +import { createGlobalContextMock } from '../../../__tests__/utils/context'; +import { createMockLogger } from '../../../__tests__/utils/logger'; +import { IosSimulatorUtils } from '../../../utils/IosSimulatorUtils'; +import { createStartIosSimulatorBuildFunction } from '../startIosSimulator'; + +jest.mock('@expo/turtle-spawn', () => ({ + __esModule: true, + default: jest.fn(), +})); + +jest.mock('../../../utils/IosSimulatorUtils', () => ({ + IosSimulatorUtils: { + getAvailableDevicesAsync: jest.fn(), + getDeviceAsync: jest.fn(), + cloneAsync: jest.fn(), + startAsync: jest.fn(), + waitForReadyAsync: jest.fn(), + disableApsdAsync: jest.fn(), + }, +})); + +const mockedSpawn = jest.mocked(spawn); +const mockedUtils = jest.mocked(IosSimulatorUtils); + +function createStep(callInputs?: Record) { + const logger = createMockLogger(); + const fn = createStartIosSimulatorBuildFunction(); + const globalCtx = createGlobalContextMock({ logger }); + const step = fn.createBuildStepFromFunctionCall(globalCtx, { callInputs }); + return Object.assign(step, { logger }); +} + +describe(createStartIosSimulatorBuildFunction, () => { + beforeEach(() => { + mockedSpawn.mockResolvedValue({ stdout: '', stderr: '' } as any); + mockedUtils.getAvailableDevicesAsync.mockResolvedValue([]); + mockedUtils.getDeviceAsync.mockResolvedValue(null); + mockedUtils.cloneAsync.mockResolvedValue(undefined); + mockedUtils.startAsync.mockResolvedValue({ udid: 'test-udid' as any }); + mockedUtils.waitForReadyAsync.mockResolvedValue(undefined); + mockedUtils.disableApsdAsync.mockResolvedValue(undefined); + }); + + it('disables apsd on the main device and every clone', async () => { + mockedUtils.startAsync + .mockResolvedValueOnce({ udid: 'base' as any }) + .mockResolvedValueOnce({ udid: 'clone-1' as any }) + .mockResolvedValueOnce({ udid: 'clone-2' as any }); + + await createStep({ device_identifier: 'iPhone 15', count: 2 }).executeAsync(); + + expect(mockedUtils.disableApsdAsync).toHaveBeenCalledWith({ + udid: 'base', + env: expect.any(Object), + }); + expect(mockedUtils.disableApsdAsync).toHaveBeenCalledWith({ + udid: 'clone-1', + env: expect.any(Object), + }); + expect(mockedUtils.disableApsdAsync).toHaveBeenCalledWith({ + udid: 'clone-2', + env: expect.any(Object), + }); + }); + + it('continues when disabling apsd fails', async () => { + mockedUtils.disableApsdAsync.mockRejectedValue(new Error('apsd disable failed')); + + await createStep({ device_identifier: 'iPhone 15', count: 2 }).executeAsync(); + + // Startup is not aborted: readiness is still awaited for each device. + expect(mockedUtils.waitForReadyAsync).toHaveBeenCalled(); + }); +}); diff --git a/packages/build-tools/src/steps/functions/startIosSimulator.ts b/packages/build-tools/src/steps/functions/startIosSimulator.ts index 29fcf7c9ab..25943a7311 100644 --- a/packages/build-tools/src/steps/functions/startIosSimulator.ts +++ b/packages/build-tools/src/steps/functions/startIosSimulator.ts @@ -65,6 +65,12 @@ export function createStartIosSimulatorBuildFunction(): BuildFunction { env, }); + try { + await IosSimulatorUtils.disableApsdAsync({ udid, env }); + } catch (err) { + logger.warn({ err }, 'Failed to disable apsd in the Simulator.'); + } + await IosSimulatorUtils.waitForReadyAsync({ udid, env }); logger.info(''); @@ -96,6 +102,12 @@ export function createStartIosSimulatorBuildFunction(): BuildFunction { env, }); + try { + await IosSimulatorUtils.disableApsdAsync({ udid: cloneUdid, env }); + } catch (err) { + logger.warn({ err }, 'Failed to disable apsd in the Simulator.'); + } + await IosSimulatorUtils.waitForReadyAsync({ udid: cloneUdid, env, diff --git a/packages/build-tools/src/utils/IosSimulatorUtils.ts b/packages/build-tools/src/utils/IosSimulatorUtils.ts index 3ed4e91ec3..7cb14e9935 100644 --- a/packages/build-tools/src/utils/IosSimulatorUtils.ts +++ b/packages/build-tools/src/utils/IosSimulatorUtils.ts @@ -157,6 +157,25 @@ export namespace IosSimulatorUtils { ); } + export async function disableApsdAsync({ + udid, + env, + }: { + udid: IosSimulatorUuid; + env: NodeJS.ProcessEnv; + }): Promise { + await spawn( + 'xcrun', + ['simctl', 'spawn', udid, 'launchctl', 'disable', 'system/com.apple.apsd'], + { env } + ); + await spawn( + 'xcrun', + ['simctl', 'spawn', udid, 'launchctl', 'bootout', 'system/com.apple.apsd'], + { env } + ); + } + export async function collectLogsAsync({ deviceIdentifier, env, diff --git a/packages/build-tools/src/utils/__tests__/IosSimulatorUtils.test.ts b/packages/build-tools/src/utils/__tests__/IosSimulatorUtils.test.ts index f1aa0dad5f..19afeb8b62 100644 --- a/packages/build-tools/src/utils/__tests__/IosSimulatorUtils.test.ts +++ b/packages/build-tools/src/utils/__tests__/IosSimulatorUtils.test.ts @@ -41,4 +41,24 @@ describe('IosSimulatorUtils', () => { ); }); }); + + describe(IosSimulatorUtils.disableApsdAsync, () => { + it('disables and boots out apsd in the simulator', async () => { + await IosSimulatorUtils.disableApsdAsync({ + udid: 'test-udid' as any, + env: process.env, + }); + + expect(mockedSpawn).toHaveBeenCalledWith( + 'xcrun', + ['simctl', 'spawn', 'test-udid', 'launchctl', 'disable', 'system/com.apple.apsd'], + { env: process.env } + ); + expect(mockedSpawn).toHaveBeenCalledWith( + 'xcrun', + ['simctl', 'spawn', 'test-udid', 'launchctl', 'bootout', 'system/com.apple.apsd'], + { env: process.env } + ); + }); + }); });