mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-01 06:22:44 -08:00
fix(startup): replace fixed overlay sleeps with readiness retries
This commit is contained in:
@@ -4,7 +4,8 @@ import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import net from 'node:net';
|
||||
import { EventEmitter } from 'node:events';
|
||||
import { waitForUnixSocketReady } from './mpv';
|
||||
import type { Args } from './types';
|
||||
import { startOverlay, state, waitForUnixSocketReady } from './mpv';
|
||||
import * as mpvModule from './mpv';
|
||||
|
||||
function createTempSocketPath(): { dir: string; socketPath: string } {
|
||||
@@ -59,3 +60,82 @@ test('waitForUnixSocketReady returns true when socket becomes connectable before
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
function makeArgs(overrides: Partial<Args> = {}): Args {
|
||||
return {
|
||||
backend: 'x11',
|
||||
directory: '.',
|
||||
recursive: false,
|
||||
profile: '',
|
||||
startOverlay: false,
|
||||
youtubeSubgenMode: 'off',
|
||||
whisperBin: '',
|
||||
whisperModel: '',
|
||||
youtubeSubgenOutDir: '',
|
||||
youtubeSubgenAudioFormat: 'wav',
|
||||
youtubeSubgenKeepTemp: false,
|
||||
youtubePrimarySubLangs: [],
|
||||
youtubeSecondarySubLangs: [],
|
||||
youtubeAudioLangs: [],
|
||||
youtubeWhisperSourceLanguage: 'ja',
|
||||
useTexthooker: false,
|
||||
autoStartOverlay: false,
|
||||
texthookerOnly: false,
|
||||
useRofi: false,
|
||||
logLevel: 'error',
|
||||
passwordStore: '',
|
||||
target: '',
|
||||
targetKind: '',
|
||||
jimakuApiKey: '',
|
||||
jimakuApiKeyCommand: '',
|
||||
jimakuApiBaseUrl: '',
|
||||
jimakuLanguagePreference: 'none',
|
||||
jimakuMaxEntryResults: 10,
|
||||
jellyfin: false,
|
||||
jellyfinLogin: false,
|
||||
jellyfinLogout: false,
|
||||
jellyfinPlay: false,
|
||||
jellyfinDiscovery: false,
|
||||
doctor: false,
|
||||
configPath: false,
|
||||
configShow: false,
|
||||
mpvIdle: false,
|
||||
mpvSocket: false,
|
||||
mpvStatus: false,
|
||||
appPassthrough: false,
|
||||
appArgs: [],
|
||||
jellyfinServer: '',
|
||||
jellyfinUsername: '',
|
||||
jellyfinPassword: '',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
test('startOverlay resolves without fixed 2s sleep when readiness signals arrive quickly', async () => {
|
||||
const { dir, socketPath } = createTempSocketPath();
|
||||
const appPath = path.join(dir, 'fake-subminer.sh');
|
||||
fs.writeFileSync(appPath, '#!/bin/sh\nexit 0\n');
|
||||
fs.chmodSync(appPath, 0o755);
|
||||
fs.writeFileSync(socketPath, '');
|
||||
const originalCreateConnection = net.createConnection;
|
||||
try {
|
||||
net.createConnection = (() => {
|
||||
const socket = new EventEmitter() as net.Socket;
|
||||
socket.destroy = (() => socket) as net.Socket['destroy'];
|
||||
socket.setTimeout = (() => socket) as net.Socket['setTimeout'];
|
||||
setTimeout(() => socket.emit('connect'), 10);
|
||||
return socket;
|
||||
}) as typeof net.createConnection;
|
||||
|
||||
const startedAt = Date.now();
|
||||
await startOverlay(appPath, makeArgs(), socketPath);
|
||||
const elapsedMs = Date.now() - startedAt;
|
||||
|
||||
assert.ok(elapsedMs < 1200, `expected startOverlay <1200ms, got ${elapsedMs}ms`);
|
||||
} finally {
|
||||
net.createConnection = originalCreateConnection;
|
||||
state.overlayProc = null;
|
||||
state.overlayManagedByLauncher = false;
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -28,6 +28,8 @@ export const state = {
|
||||
};
|
||||
|
||||
const DETACHED_IDLE_MPV_PID_FILE = path.join(os.tmpdir(), 'subminer-idle-mpv.pid');
|
||||
const OVERLAY_START_SOCKET_READY_TIMEOUT_MS = 900;
|
||||
const OVERLAY_START_COMMAND_SETTLE_TIMEOUT_MS = 700;
|
||||
|
||||
function readTrackedDetachedMpvPid(): number | null {
|
||||
try {
|
||||
@@ -498,7 +500,47 @@ export function startMpv(
|
||||
state.mpvProc = spawn('mpv', mpvArgs, { stdio: 'inherit' });
|
||||
}
|
||||
|
||||
export function startOverlay(appPath: string, args: Args, socketPath: string): Promise<void> {
|
||||
async function waitForOverlayStartCommandSettled(
|
||||
proc: ReturnType<typeof spawn>,
|
||||
logLevel: LogLevel,
|
||||
timeoutMs: number,
|
||||
): Promise<void> {
|
||||
await new Promise<void>((resolve) => {
|
||||
let settled = false;
|
||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
const finish = () => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
if (timer) clearTimeout(timer);
|
||||
proc.off('exit', onExit);
|
||||
proc.off('error', onError);
|
||||
resolve();
|
||||
};
|
||||
|
||||
const onExit = (code: number | null) => {
|
||||
if (typeof code === 'number' && code !== 0) {
|
||||
log('warn', logLevel, `Overlay start command exited with status ${code}`);
|
||||
}
|
||||
finish();
|
||||
};
|
||||
|
||||
const onError = (error: Error) => {
|
||||
log('warn', logLevel, `Overlay start command failed: ${error.message}`);
|
||||
finish();
|
||||
};
|
||||
|
||||
proc.once('exit', onExit);
|
||||
proc.once('error', onError);
|
||||
timer = setTimeout(finish, timeoutMs);
|
||||
|
||||
if (proc.exitCode !== null && proc.exitCode !== undefined) {
|
||||
onExit(proc.exitCode);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function startOverlay(appPath: string, args: Args, socketPath: string): Promise<void> {
|
||||
const backend = detectBackend(args.backend);
|
||||
log('info', args.logLevel, `Starting SubMiner overlay (backend: ${backend})...`);
|
||||
|
||||
@@ -512,9 +554,22 @@ export function startOverlay(appPath: string, args: Args, socketPath: string): P
|
||||
});
|
||||
state.overlayManagedByLauncher = true;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, 2000);
|
||||
});
|
||||
const [socketReady] = await Promise.all([
|
||||
waitForUnixSocketReady(socketPath, OVERLAY_START_SOCKET_READY_TIMEOUT_MS),
|
||||
waitForOverlayStartCommandSettled(
|
||||
state.overlayProc,
|
||||
args.logLevel,
|
||||
OVERLAY_START_COMMAND_SETTLE_TIMEOUT_MS,
|
||||
),
|
||||
]);
|
||||
|
||||
if (!socketReady) {
|
||||
log(
|
||||
'debug',
|
||||
args.logLevel,
|
||||
'Overlay start continuing before mpv socket readiness was confirmed',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function launchTexthookerOnly(appPath: string, args: Args): never {
|
||||
|
||||
Reference in New Issue
Block a user