mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
fix(linux): a supervised serve run from the AppImage stays in place instead of re-exec'ing (#28270)
Since a direct `Orca.AppImage serve --supervise` now runs the CLI supervisor (#28227), the supervisor's relocation re-exec ran from the AppImage mount. An exec from node mode closes every inherited descriptor, including the AppImage runtime's keepalive pipe, so the runtime unmounted the AppImage during the exec and the new image failed with 'libffmpeg.so: cannot open shared object file' (exit 127) on every start. Relocation exists so an update cannot swap the app under a running supervisor; an AppImage's mount cannot change during the run, so it stays in place. Co-authored-by: m4air <m4air@Mac.localdomain>
This commit is contained in:
@@ -89,6 +89,24 @@ describe('relocateServeSupervisor', () => {
|
||||
}
|
||||
})
|
||||
|
||||
// Why: re-exec from node mode closes the AppImage runtime's keepalive pipe, so the mount is
|
||||
// torn down under the exec and the new image fails to load its libraries (real-host BUG-3).
|
||||
it('stays in place when running from an AppImage, whose files cannot change under it', async () => {
|
||||
const d = deps({
|
||||
env: {
|
||||
ELECTRON_RUN_AS_NODE: '1',
|
||||
APPDIR: resources.replace(/\/Resources$/, ''),
|
||||
APPIMAGE: '/a/Orca.AppImage'
|
||||
}
|
||||
})
|
||||
await expect(relocateServeSupervisor(d)).resolves.toBe('in-place')
|
||||
expect(d.execve).not.toHaveBeenCalled()
|
||||
expect(existsSync(serveSupervisorSnapshotRoot(userData))).toBe(false)
|
||||
|
||||
const elsewhere = deps({ env: { ELECTRON_RUN_AS_NODE: '1', APPDIR: join(root, 'other') } })
|
||||
await expect(relocateServeSupervisor(elsewhere)).resolves.toBe('relocated')
|
||||
})
|
||||
|
||||
it('keeps running in place when the copy or the re-exec fails', async () => {
|
||||
await writeFile(join(root, 'blocker'), '')
|
||||
const blocked = deps({ userDataPath: join(root, 'blocker') })
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import { constants, readFileSync, statSync } from 'node:fs'
|
||||
import { cp, mkdir, readdir, rename, rm, stat, writeFile } from 'node:fs/promises'
|
||||
import { basename, dirname, join } from 'node:path'
|
||||
import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path'
|
||||
import { isProcessAlive } from '../../shared/serve-bundle-coordination'
|
||||
|
||||
/** Set on the re-run supervisor: the app root its own `__dirname` no longer points at. */
|
||||
@@ -132,6 +132,20 @@ async function removeUnusedSnapshots(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An AppImage's files are its own mount (or extracted copy) for the whole run, so no update can
|
||||
* swap them. Re-exec from node mode would also close the runtime's keepalive pipe, which unmounts
|
||||
* the AppImage under the exec and leaves the new image unable to load its libraries.
|
||||
*/
|
||||
function runsFromAppImage(appRoot: string, env: NodeJS.ProcessEnv): boolean {
|
||||
const appDir = env.APPDIR
|
||||
if (!appDir || !isAbsolute(appDir)) {
|
||||
return false
|
||||
}
|
||||
const inside = relative(resolve(appDir), resolve(appRoot))
|
||||
return inside !== '' && !inside.startsWith('..') && !isAbsolute(inside)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves only when the supervisor keeps running from where it is; a successful re-run replaces
|
||||
* this process (same pid, so the service manager keeps tracking it) and never returns.
|
||||
@@ -147,7 +161,8 @@ export async function relocateServeSupervisor(
|
||||
if (
|
||||
deps.env.ELECTRON_RUN_AS_NODE !== '1' ||
|
||||
(typeof override === 'string' && override.trim().length > 0) ||
|
||||
basename(deps.appRoot) !== PACKAGED_APP_ROOT_NAME
|
||||
basename(deps.appRoot) !== PACKAGED_APP_ROOT_NAME ||
|
||||
runsFromAppImage(deps.appRoot, deps.env)
|
||||
) {
|
||||
return 'in-place'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user