Skip to content

Commit 8aa4cae

Browse files
authored
[build-tools] Reset Gradle oom score before launch (#3898)
## Summary Replace the delayed Gradle descendant `oom_score_adj` scan with an immediate shell-level reset before launching Gradle. The worker service is protected with `OOMScoreAdjust=-999`, so Gradle inherits that value unless we reset it. This changes the Gradle launch wrapper to write `0` to `/proc/$$/oom_score_adj` before `exec ./gradlew ...`, which makes Gradle and its child processes inherit the default OOM score instead of the worker's protected score. ## Validation - `yarn --cwd packages/build-tools typecheck` - `yarn oxfmt --check packages/build-tools/src/android/gradle.ts packages/build-tools/src/steps/utils/android/gradle.ts` - `yarn oxlint --type-aware --import-plugin --tsconfig ./tsconfig.oxlint.json packages/build-tools/src/android/gradle.ts packages/build-tools/src/steps/utils/android/gradle.ts`
1 parent 8a66d38 commit 8aa4cae

2 files changed

Lines changed: 47 additions & 108 deletions

File tree

packages/build-tools/src/android/gradle.ts

Lines changed: 24 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import { Android, Env, Job, Platform } from '@expo/eas-build-job';
22
import { bunyan } from '@expo/logger';
3-
import spawn, { SpawnPromise, SpawnResult } from '@expo/turtle-spawn';
4-
import assert from 'assert';
3+
import spawn from '@expo/turtle-spawn';
54
import fs from 'fs-extra';
65
import path from 'path';
76

87
import { BuildContext } from '../context';
9-
import { getParentAndDescendantProcessPidsAsync } from '../utils/processes';
108

119
export async function ensureLFLineEndingsInGradlewScript<TJob extends Job>(
1210
ctx: BuildContext<TJob>
@@ -31,10 +29,19 @@ export async function runGradleCommand(
3129
logger.info(`Running 'gradlew ${gradleCommand}' in ${androidDir}`);
3230
await fs.chmod(path.join(androidDir, 'gradlew'), 0o755);
3331
const verboseFlag = ctx.env['EAS_VERBOSE'] === '1' ? '--info' : '';
32+
const shouldResetOOMScore =
33+
ctx.env.EAS_BUILD_RUNNER === 'eas-build' && process.platform === 'linux';
3434

35-
const spawnPromise = spawn(
35+
await spawn(
3636
'bash',
37-
['-c', `./gradlew ${gradleCommand} --profile ${verboseFlag}`],
37+
[
38+
'-c',
39+
getGradleShellCommand({
40+
gradleCommand,
41+
oomScoreAdj: shouldResetOOMScore ? 0 : undefined,
42+
verboseFlag,
43+
}),
44+
],
3845
{
3946
cwd: androidDir,
4047
logger,
@@ -53,42 +60,20 @@ export async function runGradleCommand(
5360
},
5461
}
5562
);
56-
if (ctx.env.EAS_BUILD_RUNNER === 'eas-build' && process.platform === 'linux') {
57-
adjustOOMScore(spawnPromise, logger);
58-
}
59-
60-
await spawnPromise;
6163
}
6264

63-
/**
64-
* OOM Killer sometimes kills worker server while build is exceeding memory limits.
65-
* `oom_score_adj` is a value between -1000 and 1000 and it defaults to 0.
66-
* It defines which process is more likely to get killed (higher value more likely).
67-
*
68-
* This function sets oom_score_adj for Gradle process and all its child processes.
69-
*/
70-
function adjustOOMScore(spawnPromise: SpawnPromise<SpawnResult>, logger: bunyan): void {
71-
setTimeout(
72-
async () => {
73-
try {
74-
assert(spawnPromise.child.pid);
75-
const pids = await getParentAndDescendantProcessPidsAsync(spawnPromise.child.pid);
76-
await Promise.all(
77-
pids.map(async (pid: number) => {
78-
// Value 800 is just a guess here. It's probably higher than most other
79-
// process. I didn't want to set it any higher, because I'm not sure if OOM Killer
80-
// can start killing processes when there is still enough memory left.
81-
const oomScoreOverride = 800;
82-
await fs.writeFile(`/proc/${pid}/oom_score_adj`, `${oomScoreOverride}\n`);
83-
})
84-
);
85-
} catch (err: any) {
86-
logger.debug({ err, stderr: err?.stderr }, 'Failed to override oom_score_adj');
87-
}
88-
},
89-
// Wait 20 seconds to make sure all child processes are started
90-
20000
91-
);
65+
export function getGradleShellCommand({
66+
gradleCommand,
67+
oomScoreAdj,
68+
verboseFlag,
69+
}: {
70+
gradleCommand: string;
71+
oomScoreAdj?: number;
72+
verboseFlag: string;
73+
}): string {
74+
const oomScoreAdjPrefix =
75+
oomScoreAdj === undefined ? '' : `echo ${oomScoreAdj} > /proc/$$/oom_score_adj || true; `;
76+
return `${oomScoreAdjPrefix}exec ./gradlew ${gradleCommand} --profile ${verboseFlag}`;
9277
}
9378

9479
// Version envs should be set at the beginning of the build, but when building

packages/build-tools/src/steps/utils/android/gradle.ts

Lines changed: 23 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { Android } from '@expo/eas-build-job';
22
import { bunyan } from '@expo/logger';
33
import { BuildStepEnv } from '@expo/steps';
4-
import spawn, { SpawnPromise, SpawnResult } from '@expo/turtle-spawn';
5-
import assert from 'assert';
4+
import spawn from '@expo/turtle-spawn';
65
import fs from 'fs-extra';
76
import path from 'path';
87

@@ -23,10 +22,18 @@ export async function runGradleCommand({
2322

2423
logger.info(`Running 'gradlew ${gradleCommand} ${verboseFlag}' in ${androidDir}`);
2524
await fs.chmod(path.join(androidDir, 'gradlew'), 0o755);
25+
const shouldResetOOMScore = env.EAS_BUILD_RUNNER === 'eas-build' && process.platform === 'linux';
2626

27-
const spawnPromise = spawn(
27+
await spawn(
2828
'bash',
29-
['-c', `./gradlew ${gradleCommand} --profile ${verboseFlag}`],
29+
[
30+
'-c',
31+
getGradleShellCommand({
32+
gradleCommand,
33+
oomScoreAdj: shouldResetOOMScore ? 0 : undefined,
34+
verboseFlag,
35+
}),
36+
],
3037
{
3138
cwd: androidDir,
3239
logger,
@@ -44,73 +51,20 @@ export async function runGradleCommand({
4451
},
4552
}
4653
);
47-
if (env.EAS_BUILD_RUNNER === 'eas-build' && process.platform === 'linux') {
48-
adjustOOMScore(spawnPromise, logger);
49-
}
50-
51-
await spawnPromise;
5254
}
5355

54-
/**
55-
* OOM Killer sometimes kills worker server while build is exceeding memory limits.
56-
* `oom_score_adj` is a value between -1000 and 1000 and it defaults to 0.
57-
* It defines which process is more likely to get killed (higher value more likely).
58-
*
59-
* This function sets oom_score_adj for Gradle process and all its child processes.
60-
*/
61-
function adjustOOMScore(spawnPromise: SpawnPromise<SpawnResult>, logger: bunyan): void {
62-
setTimeout(
63-
async () => {
64-
try {
65-
assert(spawnPromise.child.pid);
66-
const pids = await getParentAndDescendantProcessPidsAsync(spawnPromise.child.pid);
67-
await Promise.all(
68-
pids.map(async (pid: number) => {
69-
// Value 800 is just a guess here. It's probably higher than most other
70-
// process. I didn't want to set it any higher, because I'm not sure if OOM Killer
71-
// can start killing processes when there is still enough memory left.
72-
const oomScoreOverride = 800;
73-
await fs.writeFile(`/proc/${pid}/oom_score_adj`, `${oomScoreOverride}\n`);
74-
})
75-
);
76-
} catch (err: any) {
77-
logger.debug({ err, stderr: err?.stderr }, 'Failed to override oom_score_adj');
78-
}
79-
},
80-
// Wait 20 seconds to make sure all child processes are started
81-
20000
82-
);
83-
}
84-
85-
async function getChildrenPidsAsync(parentPids: number[]): Promise<number[]> {
86-
try {
87-
const result = await spawn('pgrep', ['-P', parentPids.join(',')], {
88-
stdio: 'pipe',
89-
});
90-
return result.stdout
91-
.toString()
92-
.split('\n')
93-
.map(i => Number(i.trim()))
94-
.filter(i => i);
95-
} catch {
96-
return [];
97-
}
98-
}
99-
100-
async function getParentAndDescendantProcessPidsAsync(ppid: number): Promise<number[]> {
101-
const children = new Set<number>([ppid]);
102-
let shouldCheckAgain = true;
103-
while (shouldCheckAgain) {
104-
const pids = await getChildrenPidsAsync([...children]);
105-
shouldCheckAgain = false;
106-
for (const pid of pids) {
107-
if (!children.has(pid)) {
108-
shouldCheckAgain = true;
109-
children.add(pid);
110-
}
111-
}
112-
}
113-
return [...children];
56+
export function getGradleShellCommand({
57+
gradleCommand,
58+
oomScoreAdj,
59+
verboseFlag,
60+
}: {
61+
gradleCommand: string;
62+
oomScoreAdj?: number;
63+
verboseFlag: string;
64+
}): string {
65+
const oomScoreAdjPrefix =
66+
oomScoreAdj === undefined ? '' : `echo ${oomScoreAdj} > /proc/$$/oom_score_adj || true; `;
67+
return `${oomScoreAdjPrefix}exec ./gradlew ${gradleCommand} --profile ${verboseFlag}`;
11468
}
11569

11670
export function resolveGradleCommand(job: Android.Job, command?: string): string {

0 commit comments

Comments
 (0)