Skip to content

Commit 543261b

Browse files
authored
[build-tools] disable apsd in iOS simulator (#3795)
* [build-tools] Disable apsd in iOS Simulator after boot * [build-tools] Add unit test for disableApsdAsync * [build-tools] Use { err } logging convention for apsd warning * [build-tools] Test apsd disable in start_ios_simulator
1 parent e1148f2 commit 543261b

5 files changed

Lines changed: 128 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ This is the log of notable changes to EAS CLI and related packages.
2828
### 🐛 Bug fixes
2929

3030
- [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))
31+
- [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))
3132

3233
## [19.1.0](https://github.com/expo/eas-cli/releases/tag/v19.1.0) - 2026-05-25
3334

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import spawn from '@expo/turtle-spawn';
2+
3+
import { createGlobalContextMock } from '../../../__tests__/utils/context';
4+
import { createMockLogger } from '../../../__tests__/utils/logger';
5+
import { IosSimulatorUtils } from '../../../utils/IosSimulatorUtils';
6+
import { createStartIosSimulatorBuildFunction } from '../startIosSimulator';
7+
8+
jest.mock('@expo/turtle-spawn', () => ({
9+
__esModule: true,
10+
default: jest.fn(),
11+
}));
12+
13+
jest.mock('../../../utils/IosSimulatorUtils', () => ({
14+
IosSimulatorUtils: {
15+
getAvailableDevicesAsync: jest.fn(),
16+
getDeviceAsync: jest.fn(),
17+
cloneAsync: jest.fn(),
18+
startAsync: jest.fn(),
19+
waitForReadyAsync: jest.fn(),
20+
disableApsdAsync: jest.fn(),
21+
},
22+
}));
23+
24+
const mockedSpawn = jest.mocked(spawn);
25+
const mockedUtils = jest.mocked(IosSimulatorUtils);
26+
27+
function createStep(callInputs?: Record<string, unknown>) {
28+
const logger = createMockLogger();
29+
const fn = createStartIosSimulatorBuildFunction();
30+
const globalCtx = createGlobalContextMock({ logger });
31+
const step = fn.createBuildStepFromFunctionCall(globalCtx, { callInputs });
32+
return Object.assign(step, { logger });
33+
}
34+
35+
describe(createStartIosSimulatorBuildFunction, () => {
36+
beforeEach(() => {
37+
mockedSpawn.mockResolvedValue({ stdout: '', stderr: '' } as any);
38+
mockedUtils.getAvailableDevicesAsync.mockResolvedValue([]);
39+
mockedUtils.getDeviceAsync.mockResolvedValue(null);
40+
mockedUtils.cloneAsync.mockResolvedValue(undefined);
41+
mockedUtils.startAsync.mockResolvedValue({ udid: 'test-udid' as any });
42+
mockedUtils.waitForReadyAsync.mockResolvedValue(undefined);
43+
mockedUtils.disableApsdAsync.mockResolvedValue(undefined);
44+
});
45+
46+
it('disables apsd on the main device and every clone', async () => {
47+
mockedUtils.startAsync
48+
.mockResolvedValueOnce({ udid: 'base' as any })
49+
.mockResolvedValueOnce({ udid: 'clone-1' as any })
50+
.mockResolvedValueOnce({ udid: 'clone-2' as any });
51+
52+
await createStep({ device_identifier: 'iPhone 15', count: 2 }).executeAsync();
53+
54+
expect(mockedUtils.disableApsdAsync).toHaveBeenCalledWith({
55+
udid: 'base',
56+
env: expect.any(Object),
57+
});
58+
expect(mockedUtils.disableApsdAsync).toHaveBeenCalledWith({
59+
udid: 'clone-1',
60+
env: expect.any(Object),
61+
});
62+
expect(mockedUtils.disableApsdAsync).toHaveBeenCalledWith({
63+
udid: 'clone-2',
64+
env: expect.any(Object),
65+
});
66+
});
67+
68+
it('continues when disabling apsd fails', async () => {
69+
mockedUtils.disableApsdAsync.mockRejectedValue(new Error('apsd disable failed'));
70+
71+
await createStep({ device_identifier: 'iPhone 15', count: 2 }).executeAsync();
72+
73+
// Startup is not aborted: readiness is still awaited for each device.
74+
expect(mockedUtils.waitForReadyAsync).toHaveBeenCalled();
75+
});
76+
});

packages/build-tools/src/steps/functions/startIosSimulator.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ export function createStartIosSimulatorBuildFunction(): BuildFunction {
6565
env,
6666
});
6767

68+
try {
69+
await IosSimulatorUtils.disableApsdAsync({ udid, env });
70+
} catch (err) {
71+
logger.warn({ err }, 'Failed to disable apsd in the Simulator.');
72+
}
73+
6874
await IosSimulatorUtils.waitForReadyAsync({ udid, env });
6975

7076
logger.info('');
@@ -96,6 +102,12 @@ export function createStartIosSimulatorBuildFunction(): BuildFunction {
96102
env,
97103
});
98104

105+
try {
106+
await IosSimulatorUtils.disableApsdAsync({ udid: cloneUdid, env });
107+
} catch (err) {
108+
logger.warn({ err }, 'Failed to disable apsd in the Simulator.');
109+
}
110+
99111
await IosSimulatorUtils.waitForReadyAsync({
100112
udid: cloneUdid,
101113
env,

packages/build-tools/src/utils/IosSimulatorUtils.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,25 @@ export namespace IosSimulatorUtils {
157157
);
158158
}
159159

160+
export async function disableApsdAsync({
161+
udid,
162+
env,
163+
}: {
164+
udid: IosSimulatorUuid;
165+
env: NodeJS.ProcessEnv;
166+
}): Promise<void> {
167+
await spawn(
168+
'xcrun',
169+
['simctl', 'spawn', udid, 'launchctl', 'disable', 'system/com.apple.apsd'],
170+
{ env }
171+
);
172+
await spawn(
173+
'xcrun',
174+
['simctl', 'spawn', udid, 'launchctl', 'bootout', 'system/com.apple.apsd'],
175+
{ env }
176+
);
177+
}
178+
160179
export async function collectLogsAsync({
161180
deviceIdentifier,
162181
env,

packages/build-tools/src/utils/__tests__/IosSimulatorUtils.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,24 @@ describe('IosSimulatorUtils', () => {
4141
);
4242
});
4343
});
44+
45+
describe(IosSimulatorUtils.disableApsdAsync, () => {
46+
it('disables and boots out apsd in the simulator', async () => {
47+
await IosSimulatorUtils.disableApsdAsync({
48+
udid: 'test-udid' as any,
49+
env: process.env,
50+
});
51+
52+
expect(mockedSpawn).toHaveBeenCalledWith(
53+
'xcrun',
54+
['simctl', 'spawn', 'test-udid', 'launchctl', 'disable', 'system/com.apple.apsd'],
55+
{ env: process.env }
56+
);
57+
expect(mockedSpawn).toHaveBeenCalledWith(
58+
'xcrun',
59+
['simctl', 'spawn', 'test-udid', 'launchctl', 'bootout', 'system/com.apple.apsd'],
60+
{ env: process.env }
61+
);
62+
});
63+
});
4464
});

0 commit comments

Comments
 (0)