fix: Container runtime actions (#2723)

Actions where running in a race condition that they sometimes didn't wait for the container to be started and the issue was the exec that was then run after would have an issue.
This commit is contained in:
Jade
2024-08-23 11:19:49 -06:00
committed by GitHub
parent 4defec194f
commit f373abdd14

View File

@@ -3,9 +3,10 @@ import * as T from "../types"
import * as cp from "child_process" import * as cp from "child_process"
import { promisify } from "util" import { promisify } from "util"
import { Buffer } from "node:buffer" import { Buffer } from "node:buffer"
import { once } from "./once"
export const execFile = promisify(cp.execFile) export const execFile = promisify(cp.execFile)
const WORKDIR = (imageId: string) => `/media/startos/images/${imageId}/` const WORKDIR = (imageId: string) => `/media/startos/images/${imageId}/`
const False = () => false
type ExecResults = { type ExecResults = {
exitCode: number | null exitCode: number | null
exitSignal: NodeJS.Signals | null exitSignal: NodeJS.Signals | null
@@ -17,6 +18,8 @@ export type ExecOptions = {
input?: string | Buffer input?: string | Buffer
} }
const TIMES_TO_WAIT_FOR_PROC = 100
/** /**
* This is the type that is going to describe what an subcontainer could do. The main point of the * This is the type that is going to describe what an subcontainer could do. The main point of the
* subcontainer is to have commands that run in a chrooted environment. This is useful for running * subcontainer is to have commands that run in a chrooted environment. This is useful for running
@@ -44,6 +47,7 @@ export interface ExecSpawnable {
export class SubContainer implements ExecSpawnable { export class SubContainer implements ExecSpawnable {
private leader: cp.ChildProcess private leader: cp.ChildProcess
private leaderExited: boolean = false private leaderExited: boolean = false
private waitProc: () => Promise<void>
private constructor( private constructor(
readonly effects: T.Effects, readonly effects: T.Effects,
readonly imageId: T.ImageId, readonly imageId: T.ImageId,
@@ -58,6 +62,26 @@ export class SubContainer implements ExecSpawnable {
this.leader.on("exit", () => { this.leader.on("exit", () => {
this.leaderExited = true this.leaderExited = true
}) })
this.waitProc = once(
() =>
new Promise(async (resolve, reject) => {
let count = 0
while (
!(await fs.stat(`${this.rootfs}/proc/1`).then((x) => !!x, False))
) {
if (count++ > TIMES_TO_WAIT_FOR_PROC) {
console.debug("Failed to start subcontainer", {
guid: this.guid,
imageId: this.imageId,
rootfs: this.rootfs,
})
reject(new Error(`Failed to start subcontainer ${this.imageId}`))
}
await wait(1)
}
resolve()
}),
)
} }
static async of( static async of(
effects: T.Effects, effects: T.Effects,
@@ -184,6 +208,7 @@ export class SubContainer implements ExecSpawnable {
stdout: string | Buffer stdout: string | Buffer
stderr: string | Buffer stderr: string | Buffer
}> { }> {
await this.waitProc()
const imageMeta: T.ImageMetadata = await fs const imageMeta: T.ImageMetadata = await fs
.readFile(`/media/startos/images/${this.imageId}.json`, { .readFile(`/media/startos/images/${this.imageId}.json`, {
encoding: "utf8", encoding: "utf8",
@@ -266,6 +291,7 @@ export class SubContainer implements ExecSpawnable {
command: string[], command: string[],
options?: CommandOptions, options?: CommandOptions,
): Promise<cp.ChildProcessWithoutNullStreams> { ): Promise<cp.ChildProcessWithoutNullStreams> {
await this.waitProc()
const imageMeta: any = await fs const imageMeta: any = await fs
.readFile(`/media/startos/images/${this.imageId}.json`, { .readFile(`/media/startos/images/${this.imageId}.json`, {
encoding: "utf8", encoding: "utf8",
@@ -307,6 +333,7 @@ export class SubContainer implements ExecSpawnable {
command: string[], command: string[],
options?: CommandOptions, options?: CommandOptions,
): Promise<cp.ChildProcessWithoutNullStreams> { ): Promise<cp.ChildProcessWithoutNullStreams> {
await this.waitProc()
const imageMeta: any = await fs const imageMeta: any = await fs
.readFile(`/media/startos/images/${this.imageId}.json`, { .readFile(`/media/startos/images/${this.imageId}.json`, {
encoding: "utf8", encoding: "utf8",
@@ -402,3 +429,6 @@ export type MountOptionsBackup = {
type: "backup" type: "backup"
subpath: string | null subpath: string | null
} }
function wait(time: number) {
return new Promise((resolve) => setTimeout(resolve, time))
}