Repository navigation
[worker][build-tools] Stop warmup in the background for jobs that do not use a warm Simulator #4721
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
+6,444
−1
Open
Changes from all commits
Commits
Show all changes
159 commits
Select commit
Hold shift + click to select a range
f309f9b
[vm-tools] Run guest startup scripts
gwdp d074bf7
[vm-tools] Use the shared warm Simulator terms in the changeset
gwdp 76ccc7d
[vm-tools] Keep the startup process handle explicit
codex b473652
[vm-tools] Describe the VM startup script loader
codex e42fa74
[vm-tools] Refresh checks against the current base
codex 50a78f3
[simulator-launcher] Prepare Simulator services at guest startup
gwdp 93fc358
[simulator-launcher] Claim warm sessions once serve-sim is ready
gwdp 1fad3cf
[simulator-launcher] Disable apsd during warmup
gwdp 0cba53c
[simulator-launcher] Stop installing ffmpeg during warmup
gwdp f9ee3cb
[simulator-launcher] Start replacement services without waiting for s…
gwdp 6c38a43
[simulator-launcher] Report the preinstalled Expo Go in the claim record
gwdp 5c600e3
[simulator-launcher] Handle a failed serve-sim exit while waiting for…
gwdp 64cddb0
[simulator-launcher] Read serve-sim readiness from its configured state
gwdp ee9ef41
[ci] Check the pull-request source for changesets
codex f8f672b
[simulator-launcher] Settle every old stop before failing a replacement
gwdp 7bd9f73
[simulator-launcher] Treat an already shut down Simulator as cleaned up
gwdp 8fef6f5
[simulator-launcher] Simplify warm Simulator cleanup
gwdp c9196e1
[simulator-launcher] Update the README for warm claim and cleanup
gwdp 2fce2ff
[simulator-launcher] Simplify apsd disabling and the claim result
gwdp 96e169d
[simulator-launcher] Start Simulator tools after serve-sim is ready
gwdp 81a3bec
[simulator-launcher] Treat exited processes awaiting reap as stopped
gwdp 026dc78
[simulator-launcher] Probe readiness only at the local serve-sim address
gwdp 2854146
[simulator-launcher] Split the warm session into simulator and tool m…
gwdp 3b0db0c
[simulator-launcher] Use the shared warm Simulator terms in the chang…
gwdp 6b6ff97
[simulator-launcher] Use the shared warm Simulator terms in the README
gwdp 2d01d9b
[simulator-launcher] Name the warm session handle type
codex 5455ea0
[simulator-launcher] Use the package version instead of a copied bundle
codex e4ae4c4
[simulator-launcher] Accept a list of warm Simulator devices
codex b0e40d9
[simulator-launcher] Warm more devices after the default is ready
codex 0779aed
[simulator-launcher] Claim either warm device and release the others
codex cca8819
[simulator-launcher] Prime the launcher cache before ready
codex dc6977b
[simulator-launcher] Keep unused-device cleanup from stopping the job
codex fe7c627
[simulator-launcher] Keep default warmup after an additional device f…
codex 29fd654
[simulator-launcher] Keep the default device for blank configuration
codex 28f4e83
[simulator-launcher] Bound cache priming and retry once
codex c1aad59
[simulator-launcher] Shut owned devices down in parallel at terminal …
codex bf44a35
[simulator-launcher] Finish default warmup before sharing Expo Go wit…
codex 6646061
[simulator-launcher] Start warm serve-sim with explicit orphan ownership
codex 395d51a
[worker] Manage warm Simulator services
gwdp 3326bbd
[simulator-launcher] Start the selected Simulator before returning it…
codex 6341fa7
[worker] Use the shared warm Simulator terms in the changeset
gwdp 6e60a81
[simulator-launcher] Keep Expo Go failures scoped to their device
codex f6a81d1
[worker] Stop the recorded Simulator launcher version
codex 86873db
[worker] Skip Simulator stop on images without a startup script
codex 4feb3aa
[worker] Stop current-boot warmup when the script is absent
codex 510a911
[worker] Skip launcher resolution after confirmed cleanup
codex 2f23a9b
[worker] Recognize Bun cache misses alongside warning output
codex afe47a9
[worker] Honor the package manager for versioned launcher stop
codex f1f6ed6
[worker] Ignore invalid launcher state when no startup script exists
codex c71d91a
[stack] Integrate current main while preserving existing commits
codex 68ec03a
[ci] Compare changes with the current base branch
codex 1218140
[stack] Integrate current main while preserving existing commits
codex abf447d
[stack] Integrate current main while preserving existing commits
codex 2d39f0f
[stack] Keep existing changes above Simulator prewarming
gwdp 3d8868a
[simulator-launcher] Keep the base launcher focused on one device
gwdp 163c8c9
[simulator-launcher] Preinstall Expo Go and warm additional Simulators
gwdp 2aea4e4
[vm-tools] Read startup scripts from the local shared directory
gwdp 7064c70
[stack] Keep existing changes above the launcher state cleanup
gwdp 1f214df
[simulator-launcher] Keep one selected device in session state
gwdp 5d5f78d
[simulator-launcher] Keep warmup ownership internal
gwdp c8d2281
[stack] Keep existing changes above explicit launcher lifetimes
gwdp 2b471ce
[simulator-launcher] Make session startup and cleanup explicit
gwdp 1de9a1d
[simulator-launcher] Keep extra warmup and device retirement owned
gwdp d2cba1c
[stack] Keep existing changes above launcher helpers
gwdp 682a387
[stack] Keep launcher commands above service helpers
gwdp 45c9499
[stack] Keep existing changes above launcher helpers
gwdp 7f6a88c
[simulator-launcher] Add Simulator state and service helpers
gwdp 30c4e47
[worker] Use existing package-manager helpers directly
gwdp 29c2bd5
[stack] Keep existing changes above public launcher metadata
gwdp 07a54f0
[stack] Keep existing changes above public launcher metadata
gwdp daafd74
[simulator-launcher] Keep the package public and versioned
gwdp 9b4eb00
[stack] Keep existing changes above public launcher metadata
gwdp bae24fc
[stack] Retain the launcher foundation release entry
gwdp 2122228
[stack] Retain the launcher foundation release entry
gwdp 90ba211
[stack] Retain the launcher foundation release entry
gwdp fe455b7
[worker] Keep the warmup stop changeset scoped to worker
gwdp ada191c
[worker] Let the launcher handle Simulator stop state
gwdp 91cc4f3
[worker] Stop warm Simulator services in the background
gwdp 2cd52ab
[worker] Wait for warm cleanup before Simulator use
gwdp 935aeb6
[simulator-launcher] Adopt the selected device on the warm server
gwdp c147c5e
[simulator-launcher] Share device preinstalls and reuse installed Exp…
gwdp f3ed3d2
[simulator-launcher] Verify readiness and drain intentional tool stops
gwdp b3b0cb4
[vm-tools] Run guest startup scripts
gwdp 3aa2b25
[vm-tools] Use the shared warm Simulator terms in the changeset
gwdp cbd42cf
[vm-tools] Keep the startup process handle explicit
codex 602b8b3
[vm-tools] Describe the VM startup script loader
codex 64ef782
[vm-tools] Refresh checks against the current base
codex f5b60f3
[ci] Check the pull-request source for changesets
codex a549bce
[ci] Compare changes with the current base branch
codex 23f653c
[vm-tools] Read startup scripts from the local shared directory
gwdp 1e50360
[stack] Update #4721 onto current main
gwdp 45c8a27
[stack] Update #4722 onto current main
gwdp ceafd4f
[stack] Update #4764 onto current main
gwdp 3cb2178
[stack] Update #4766 onto current main
gwdp cb987fe
[worker] Fix warm stop job fixtures
gwdp f1edc83
[simulator-launcher] Add braces to the single-device selection guard
gwdp 14bd55a
[stack] Include core launcher lint correction
gwdp 6bfe01f
[stack] Include core launcher lint correction
gwdp eb02996
[stack] Restack after launcher changeset cleanup
gwdp 0de9314
[stack] Restack after launcher changeset cleanup
gwdp b71a01f
[stack] Restack after launcher changeset cleanup
gwdp ffd7fc2
[simulator-launcher] Consolidate package changeset
gwdp 753b8de
[simulator-launcher] Remove redundant launcher checks
gwdp 9afd051
[simulator-launcher] Keep prewarming code in its layer
gwdp cadf6e9
[stack] Restack launcher cleanup
gwdp 9dca86c
[simulator-launcher] Name process options and remove unused boot helper
gwdp dfcf515
[simulator-launcher] Merge the launcher changesets into one
gwdp 7c8aaa5
[stack] Restack after merging the launcher changesets
gwdp 561ac82
[simulator-launcher] Describe the shared serve-sim port in the change…
gwdp 45aca05
[stack] Restack after merging the launcher changesets
gwdp 400d108
[simulator-launcher] Keep the prewarming layer to the launcher
gwdp 81cb88f
[stack] Restack after keeping the prewarming layer to the launcher
gwdp 5a0cef3
[worker] Let build-tools wait on the launcher state for warm Simulato…
gwdp 8f4b9b6
[vm-tools] Start at the fixed-group version
gwdp 9d5b20c
[stack] Restack after the vm-tools version change
gwdp 1f5e154
[simulator-launcher] Keep the launcher private until it has a CLI
gwdp 1438cfc
[stack] Restack after the launcher release changes
gwdp 8bd24a7
[stack] Restack after the launcher release changes
gwdp 00d50f5
[stack] Restack after the launcher release changes
gwdp f001bcf
[simulator-launcher] Set up every warm device with one function
gwdp 20978d4
[stack] Restack after the single warm device setup
gwdp 794948a
[stack] Restack after shortening the launcher changeset
gwdp b47132b
[stack] Restack after shortening the launcher changeset
gwdp e86e181
[simulator-launcher] Shorten the launcher changeset
gwdp d82a6a6
[simulator-launcher] Set up all warm devices from one list
gwdp 33b491d
[stack] Restack after setting up warm devices from one list
gwdp 6e65ec2
[simulator-launcher] Wait for the default device once before the othe…
gwdp 514d692
[stack] Restack after the simpler extra-device wait
gwdp 4f75954
[simulator-launcher] Start device setup without waiting for it
gwdp 4253f1a
[stack] Restack after starting device setup without waiting
gwdp 4a714ba
[simulator-launcher] Set up all warm devices with one call
gwdp b5eba9b
[stack] Restack after setting up all warm devices with one call
gwdp 75ae771
[simulator-launcher] Run apsd and the Expo Go preinstall in parallel …
gwdp d649245
[stack] Restack after the parallel device setup
gwdp b947807
[simulator-launcher] Check the claimed device and bound stop commands
gwdp 638db05
[stack] Restack after the claim device check
gwdp c1bb572
[simulator-launcher] Keep the claimed Simulator when an unused tool s…
gwdp c29baa9
[stack] Restack after the tool stop fix
gwdp ad5b357
[simulator-launcher] Finish the default device apsd step before the c…
gwdp fffa313
[stack] Restack after the claim and tool stop fixes
gwdp 21ef56e
[simulator-launcher] Attach the claim rejection checks before the cla…
gwdp 5c57d8f
[stack] Restack after the claim test fix
gwdp 00141eb
[stack] Restack after the claim test fix
gwdp 8dc4bf9
[stack] Restack after the claim test fix
gwdp c33d766
[simulator-launcher] Prove the stop retries an unused tool
gwdp ea1fb5b
[stack] Restack after the retry test
gwdp 0a27df2
[simulator-launcher] Say which claim waits for the apsd step
gwdp 8611027
[stack] Restack after the retry test and README fix
gwdp be7e864
[simulator-launcher] Keep warmup running after a claim
gwdp bac4acf
[stack] Restack after keeping warmup running after a claim
gwdp a11b46d
[simulator-launcher] Return the claim without waiting for warmup
gwdp ccdbdad
[stack] Restack after the quick claim
gwdp a074e53
[simulator-launcher] Describe installs that adoption can stop
gwdp d7c9fd9
[stack] Restack after the launcher text fixes
gwdp f0e423d
[simulator-launcher] Remove non-null assertions
gwdp ca33299
[stack] Include launcher assertion cleanup
gwdp e03e30d
[simulator-launcher] Return the validated Argent service
gwdp dd033c0
[worker][build-tools] Wait for warm shutdown before device setup
gwdp 7eb4abd
[build-tools] Require cleanup confirmation from the current boot
gwdp File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| --- | ||
| '@expo/worker': patch | ||
| '@expo/build-tools': patch | ||
| --- | ||
|
|
||
| Stop warmup in the background for jobs that do not use the warm Simulator, so job setup does not wait for it. Before build-tools boots or clones a Simulator, it waits until the launcher's state file records that cleanup finished, and fails if cleanup does not finish in time. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@expo/simulator-launcher': none | ||
| --- | ||
|
|
||
| Add the state, request command and service helpers for the warm Simulator launcher. The package stays private until #4722 adds its CLI entry point. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@expo/simulator-launcher': minor | ||
| --- | ||
|
|
||
| Accept a comma-separated list of warm Simulators in `EXPO_SIMULATOR_DEVICE`; the first is the default. One serve-sim serves every listed device on one port. After the default device is ready, warmup preinstalls the latest stable Expo Go from one download and tries to disable apsd on the other devices, until a job claims one. A claim can select any listed device and reports its preinstalled Expo Go. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@expo/simulator-launcher': minor | ||
| --- | ||
|
|
||
| Add `@expo/simulator-launcher`, which warms an iOS Simulator at VM boot and hands it to one EAS job (`start`, `claim`, `prepare-tools`, `stop`). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@expo/vm-tools': minor | ||
| --- | ||
|
|
||
| Add a macOS startup script loader that runs `/usr/local/share/vm-tools/startup.sh` once per VM boot. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ import { setTimeout } from 'node:timers/promises'; | |
| import { z } from 'zod'; | ||
|
|
||
| import { retryAsync } from './retry'; | ||
| import { waitForWarmSimulatorCleanupAsync } from './warmSimulatorCleanup'; | ||
|
|
||
| export type IosSimulatorUuid = string & z.BRAND<'IosSimulatorUuid'>; | ||
| export type IosSimulatorName = string & z.BRAND<'IosSimulatorName'>; | ||
|
|
@@ -119,6 +120,7 @@ export namespace IosSimulatorUtils { | |
| destinationDeviceName: IosSimulatorName; | ||
| env: NodeJS.ProcessEnv; | ||
| }): Promise<void> { | ||
| await waitForWarmSimulatorCleanupAsync(env); | ||
| await spawn('xcrun', ['simctl', 'clone', sourceDeviceIdentifier, destinationDeviceName], { | ||
| env, | ||
| }); | ||
|
|
@@ -131,6 +133,7 @@ export namespace IosSimulatorUtils { | |
| deviceIdentifier: IosSimulatorUuid | IosSimulatorName; | ||
| env: NodeJS.ProcessEnv; | ||
| }): Promise<void> { | ||
| await waitForWarmSimulatorCleanupAsync(env); | ||
| try { | ||
| const devices = await getAvailableDevicesAsync({ env, filter: 'available' }); | ||
| const device = devices.find( | ||
|
|
@@ -231,6 +234,7 @@ export namespace IosSimulatorUtils { | |
| env: NodeJS.ProcessEnv; | ||
| launchdEnvironment?: Record<string, string>; | ||
| }): Promise<void> { | ||
| await waitForWarmSimulatorCleanupAsync(env); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| const bootEnv = { ...env }; | ||
| for (const [name, value] of Object.entries(launchdEnvironment)) { | ||
| bootEnv[`SIMCTL_CHILD_${name}`] = value; | ||
|
|
@@ -253,6 +257,7 @@ export namespace IosSimulatorUtils { | |
| deviceIdentifier: IosSimulatorUuid | IosSimulatorName; | ||
| env: NodeJS.ProcessEnv; | ||
| }): Promise<{ udid: IosSimulatorUuid }> { | ||
| await waitForWarmSimulatorCleanupAsync(env); | ||
| const bootstatusResult = await spawn( | ||
| 'xcrun', | ||
| ['simctl', 'bootstatus', deviceIdentifier, '-b'], | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
125 changes: 125 additions & 0 deletions
125
packages/build-tools/src/utils/__tests__/warmSimulatorCleanup.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| import spawn from '@expo/turtle-spawn'; | ||
| import fs from 'node:fs/promises'; | ||
|
|
||
| import { waitForWarmSimulatorCleanupAsync } from '../warmSimulatorCleanup'; | ||
|
|
||
| const STATE_FILE = '/tmp/expo-simulator-startup.json'; | ||
| const STARTUP_SCRIPT = '/usr/local/share/vm-tools/startup.sh'; | ||
| const platform = Object.getOwnPropertyDescriptor(process, 'platform')!; | ||
| jest.mock('@expo/turtle-spawn'); | ||
|
|
||
| async function writeStateAsync(state: object): Promise<void> { | ||
| await fs.mkdir('/tmp', { recursive: true }); | ||
| await fs.writeFile(STATE_FILE, JSON.stringify({ bootUuid: 'current-boot', ...state })); | ||
| } | ||
|
|
||
| beforeEach(() => { | ||
| Object.defineProperty(process, 'platform', { value: 'darwin' }); | ||
| jest.mocked(spawn).mockResolvedValue({ stdout: 'current-boot\n' } as never); | ||
| }); | ||
| afterEach(() => { | ||
| Object.defineProperty(process, 'platform', platform); | ||
| }); | ||
|
|
||
| it('continues at once on a VM without warmup', async () => { | ||
| await expect(waitForWarmSimulatorCleanupAsync({})).resolves.toBeUndefined(); | ||
| expect(spawn).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('ignores a previous boot cleanup record and reads the current boot only once', async () => { | ||
| await writeStateAsync({ status: 'disabled', bootUuid: 'old-boot' }); | ||
| let finished = false; | ||
| const waiting = waitForWarmSimulatorCleanupAsync({}, { pollMs: 5 }).then(() => { | ||
| finished = true; | ||
| }); | ||
| await new Promise(resolve => setTimeout(resolve, 30)); | ||
| expect(finished).toBe(false); | ||
| await writeStateAsync({ status: 'disabled' }); | ||
| await waiting; | ||
| expect(spawn).toHaveBeenCalledTimes(1); | ||
| }); | ||
|
|
||
| it('does not accept a cleanup record without a boot ID', async () => { | ||
| await writeStateAsync({ status: 'disabled', bootUuid: undefined }); | ||
| await expect(waitForWarmSimulatorCleanupAsync({}, { timeoutMs: 20, pollMs: 5 })).rejects.toThrow( | ||
| 'Warm Simulator cleanup did not finish' | ||
| ); | ||
| }); | ||
|
|
||
| it.each(['failed', 'empty'])('fails closed when the current boot ID is %s', async mode => { | ||
| await writeStateAsync({ status: 'disabled' }); | ||
| if (mode === 'failed') { | ||
| jest.mocked(spawn).mockRejectedValueOnce(new Error('sysctl failed')); | ||
| } else { | ||
| jest.mocked(spawn).mockResolvedValueOnce({ stdout: '' } as never); | ||
| } | ||
| await expect(waitForWarmSimulatorCleanupAsync({})).rejects.toThrow(); | ||
| }); | ||
|
|
||
| it('waits for cleanup when warmup is installed but has not written state yet', async () => { | ||
| await fs.mkdir('/usr/local/share/vm-tools', { recursive: true }); | ||
| await fs.writeFile(STARTUP_SCRIPT, ''); | ||
| let finished = false; | ||
| const waiting = waitForWarmSimulatorCleanupAsync({}, { pollMs: 5 }).then(() => { | ||
| finished = true; | ||
| }); | ||
| await new Promise(resolve => setTimeout(resolve, 30)); | ||
| expect(finished).toBe(false); | ||
| await writeStateAsync({ status: 'starting' }); | ||
| await new Promise(resolve => setTimeout(resolve, 30)); | ||
| expect(finished).toBe(false); | ||
| await writeStateAsync({ status: 'disabled' }); | ||
| await waiting; | ||
| }); | ||
|
|
||
| it('times out when installed warmup never writes state', async () => { | ||
| await fs.mkdir('/usr/local/share/vm-tools', { recursive: true }); | ||
| await fs.writeFile(STARTUP_SCRIPT, ''); | ||
| await expect(waitForWarmSimulatorCleanupAsync({}, { timeoutMs: 20, pollMs: 5 })).rejects.toThrow( | ||
| 'Warm Simulator cleanup did not finish; the Simulator cannot be used.' | ||
| ); | ||
| }); | ||
|
|
||
| it('waits until the launcher records that cleanup finished', async () => { | ||
| await writeStateAsync({ status: 'ready' }); | ||
| let finished = false; | ||
| const waiting = waitForWarmSimulatorCleanupAsync({}, { pollMs: 5 }).then(() => { | ||
| finished = true; | ||
| }); | ||
| await new Promise(resolve => setTimeout(resolve, 30)); | ||
| expect(finished).toBe(false); | ||
| await writeStateAsync({ status: 'stopping' }); | ||
| await new Promise(resolve => setTimeout(resolve, 30)); | ||
| expect(finished).toBe(false); | ||
| await writeStateAsync({ status: 'disabled' }); | ||
| await waiting; | ||
| expect(finished).toBe(true); | ||
| }); | ||
|
|
||
| it('fails with the recorded error when cleanup does not finish in time', async () => { | ||
| await writeStateAsync({ status: 'stopping', error: 'serve-sim did not exit' }); | ||
| await expect(waitForWarmSimulatorCleanupAsync({}, { timeoutMs: 20, pollMs: 5 })).rejects.toThrow( | ||
| 'Warm Simulator cleanup did not finish (serve-sim did not exit); the Simulator cannot be used.' | ||
| ); | ||
| }); | ||
|
|
||
| it('stops waiting when the job is cancelled', async () => { | ||
| await writeStateAsync({ status: 'stopping' }); | ||
| const controller = new AbortController(); | ||
| const waiting = waitForWarmSimulatorCleanupAsync({}, { pollMs: 5, signal: controller.signal }); | ||
| controller.abort(new Error('cancelled')); | ||
| await expect(waiting).rejects.toThrow('cancelled'); | ||
| }); | ||
|
|
||
| it('does not wait for a job that uses the warm Simulator', async () => { | ||
| await writeStateAsync({ status: 'claimed' }); | ||
| await expect( | ||
| waitForWarmSimulatorCleanupAsync({ EAS_USE_WARM_SIMULATOR: '1' }, { timeoutMs: 0 }) | ||
| ).resolves.toBeUndefined(); | ||
| }); | ||
|
|
||
| it('does nothing outside macOS', async () => { | ||
| Object.defineProperty(process, 'platform', { value: 'linux' }); | ||
| await writeStateAsync({ status: 'ready' }); | ||
| await expect(waitForWarmSimulatorCleanupAsync({}, { timeoutMs: 0 })).resolves.toBeUndefined(); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| import { SystemError } from '@expo/eas-build-job'; | ||
| import spawn from '@expo/turtle-spawn'; | ||
| import { existsSync } from 'node:fs'; | ||
| import fs from 'node:fs/promises'; | ||
| import { setTimeout as sleepAsync } from 'node:timers/promises'; | ||
|
|
||
| const LAUNCHER_STATE_FILE = '/tmp/expo-simulator-startup.json'; | ||
| const STARTUP_SCRIPT = '/usr/local/share/vm-tools/startup.sh'; | ||
|
|
||
| /** | ||
| * The worker stops warmup in the background for a job that does not use the warm Simulator. Wait | ||
| * here, before the job changes a Simulator, until the launcher records that its cleanup finished. | ||
| */ | ||
| export async function waitForWarmSimulatorCleanupAsync( | ||
| env: NodeJS.ProcessEnv, | ||
| { | ||
| timeoutMs = 150_000, | ||
| pollMs = 250, | ||
| signal, | ||
| }: { timeoutMs?: number; pollMs?: number; signal?: AbortSignal } = {} | ||
| ): Promise<void> { | ||
| if (process.platform !== 'darwin' || env.EAS_USE_WARM_SIMULATOR === '1') { | ||
| return; | ||
| } | ||
|
gwdp marked this conversation as resolved.
|
||
| const deadline = Date.now() + timeoutMs; | ||
| let bootUuid: string | undefined; | ||
| for (;;) { | ||
| signal?.throwIfAborted(); | ||
| let state: { status?: unknown; error?: unknown; bootUuid?: unknown }; | ||
| try { | ||
| state = JSON.parse(await fs.readFile(LAUNCHER_STATE_FILE, 'utf8')); | ||
| } catch (error) { | ||
| if ((error as NodeJS.ErrnoException).code === 'ENOENT' && !existsSync(STARTUP_SCRIPT)) { | ||
| return; | ||
| } | ||
| // Warmup may still be loading; only disabled confirms cleanup on an enabled VM. | ||
| state = {}; | ||
| } | ||
| if (state.status === 'disabled' && typeof state.bootUuid === 'string') { | ||
| bootUuid ??= ( | ||
| await spawn('/usr/sbin/sysctl', ['-n', 'kern.bootsessionuuid'], { | ||
| signal, | ||
| timeout: Math.max(1, deadline - Date.now()), | ||
| }) | ||
| ).stdout.trim(); | ||
| if (!bootUuid) { | ||
| throw new SystemError('Could not read the current VM boot ID.'); | ||
| } | ||
| if (state.bootUuid === bootUuid) { | ||
| return; | ||
| } | ||
| } | ||
| if (Date.now() >= deadline) { | ||
| throw new SystemError( | ||
| `Warm Simulator cleanup did not finish${typeof state.error === 'string' ? ` (${state.error})` : ''}; the Simulator cannot be used.` | ||
| ); | ||
| } | ||
| // The signal is checked at each poll, so a cancelled job stops within one interval. | ||
| await sleepAsync(pollMs); | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.