Ampel / ampel (push) Successful in 1m33s
Die eigentliche Regressionsfrage fehlte noch: Ein normales Kind muss auch dann sterben, wenn die Arbeitsgruppe das Ausklinken ERLAUBT. Sonst waere Paragraph 3.3 aufgeweicht und makemkvcon koennte als Waise das Laufwerk festhalten (der rc10-Fund). 1) Leine wie bisher, Kind normal -> stirbt WIE ERWARTET 2) Leine erlaubt Ausklinken, Kind normal -> stirbt WIE ERWARTET 3) Leine erlaubt Ausklinken, Kind klinkt sich aus -> lebt WIE ERWARTET Das blosse Erlauben aendert also nichts; nur wer CREATE_BREAKAWAY_FROM_JOB verlangt, kommt raus. Genau das tut allein der Update-Installer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
115 lines
5.4 KiB
JavaScript
115 lines
5.4 KiB
JavaScript
// MESSUNG: Überlebt ein ausdrücklich ausgeklinktes Kind den Tod seines
|
|
// Elternprozesses — und stirbt ein normales Kind weiterhin mit?
|
|
//
|
|
// Anlass (01.09.2026): Das Update auf 5.1.3 lief nicht. Rippy beendete
|
|
// sauber, aber der Installer startete nie. Verdacht: Die Prozess-Leine
|
|
// (§ 3.3, KILL_ON_JOB_CLOSE) erschlägt den Installer, weil
|
|
// electron-updater ihn per spawn(…, { detached: true }) startet und
|
|
// `detached` unter Windows KEIN Breakaway setzt.
|
|
//
|
|
// Aufbau (drei Läufe, der äußere ruft die inneren):
|
|
// node beweise/leine-breakaway.js
|
|
// -> startet zweimal sich selbst als „innen":
|
|
// innen-ohne : Arbeitsgruppe NUR mit KILL_ON_JOB_CLOSE, Enkel normal
|
|
// innen-mit : Arbeitsgruppe zusätzlich mit BREAKAWAY_OK,
|
|
// Enkel mit CREATE_BREAKAWAY_FROM_JOB
|
|
// Beide beenden sich sofort. Danach schaut der äußere Lauf nach,
|
|
// welcher Enkel noch lebt.
|
|
//
|
|
// Erwartung: ohne Ausklinken stirbt der Enkel, mit Ausklinken lebt er.
|
|
//
|
|
// Ausführen: node beweise/leine-breakaway.js
|
|
const { execFileSync, spawnSync } = require('node:child_process')
|
|
const path = require('node:path')
|
|
|
|
const koffi = require(path.join(__dirname, '..', 'rippy-windows', 'node_modules', 'koffi'))
|
|
|
|
const JOB_KLASSE = 9 // JobObjectExtendedLimitInformation
|
|
const KILL_ON_JOB_CLOSE = 0x2000
|
|
const BREAKAWAY_OK = 0x0800
|
|
const CREATE_BREAKAWAY_FROM_JOB = 0x01000000
|
|
const CREATE_NO_WINDOW = 0x08000000
|
|
const STRUKTUR_GROESSE = 144
|
|
const LIMIT_FLAGS_OFFSET = 16
|
|
const STARTUPINFO_GROESSE = 104
|
|
const PROCESS_INFORMATION_GROESSE = 24
|
|
|
|
const kernel32 = koffi.load('kernel32.dll')
|
|
const CreateJobObjectW = kernel32.func('void* __stdcall CreateJobObjectW(void *a, str16 n)')
|
|
const SetInformationJobObject = kernel32.func(
|
|
'bool __stdcall SetInformationJobObject(void *j, int k, void *i, uint32_t l)',
|
|
)
|
|
const AssignProcessToJobObject = kernel32.func('bool __stdcall AssignProcessToJobObject(void *j, void *p)')
|
|
const GetCurrentProcess = kernel32.func('void* __stdcall GetCurrentProcess()')
|
|
const GetLastError = kernel32.func('uint32_t __stdcall GetLastError()')
|
|
const CreateProcessW = kernel32.func(
|
|
'bool __stdcall CreateProcessW(str16 an, void *cl, void *pa, void *ta, bool ih, uint32_t cf, void *env, str16 cd, void *si, void *pi)',
|
|
)
|
|
|
|
/** Ein Enkel, der 40 Sekunden lebt — lange genug zum Nachsehen. */
|
|
const ENKEL = 'C:\\Windows\\System32\\cmd.exe'
|
|
const ENKEL_ZEILE = '"C:\\Windows\\System32\\cmd.exe" /c ping -n 40 127.0.0.1'
|
|
|
|
function innen(jobErlaubtAusklinken, kindKlinktSichAus) {
|
|
const job = CreateJobObjectW(null, null)
|
|
const info = Buffer.alloc(STRUKTUR_GROESSE)
|
|
info.writeUInt32LE(KILL_ON_JOB_CLOSE | (jobErlaubtAusklinken ? BREAKAWAY_OK : 0), LIMIT_FLAGS_OFFSET)
|
|
SetInformationJobObject(job, JOB_KLASSE, info, STRUKTUR_GROESSE)
|
|
AssignProcessToJobObject(job, GetCurrentProcess())
|
|
|
|
const si = Buffer.alloc(STARTUPINFO_GROESSE)
|
|
si.writeUInt32LE(STARTUPINFO_GROESSE, 0)
|
|
const pi = Buffer.alloc(PROCESS_INFORMATION_GROESSE)
|
|
const zeile = Buffer.from(ENKEL_ZEILE + '\0', 'utf16le')
|
|
// CREATE_NO_WINDOW gibt dem Enkel eine EIGENE (unsichtbare) Konsole.
|
|
// Ohne das teilt er sich die Konsole des Elternprozesses und stirbt mit
|
|
// IHR, nicht mit der Arbeitsgruppe — der erste Messlauf am 01.09.2026
|
|
// lief genau in diese Falle und zeigte fälschlich „Ausklinken wirkt
|
|
// nicht". Beide Fälle bekommen es, damit der Vergleich fair bleibt.
|
|
const flags = CREATE_NO_WINDOW | (kindKlinktSichAus ? CREATE_BREAKAWAY_FROM_JOB : 0)
|
|
|
|
const ok = CreateProcessW(ENKEL, zeile, null, null, false, flags, null, null, si, pi)
|
|
if (!ok) {
|
|
console.log(`PID=0 FEHLER=${GetLastError()}`)
|
|
return
|
|
}
|
|
// dwProcessId steht in PROCESS_INFORMATION an Offset 16.
|
|
console.log(`PID=${pi.readUInt32LE(16)} FEHLER=0`)
|
|
}
|
|
|
|
function lebt(pid) {
|
|
const ergebnis = spawnSync('tasklist', ['/FI', `PID eq ${pid}`, '/NH'], { encoding: 'utf8' })
|
|
return (ergebnis.stdout || '').includes(String(pid))
|
|
}
|
|
|
|
function aussen() {
|
|
const faelle = [
|
|
['1) Leine wie bisher, Kind normal — die Ausgangslage', 'alt', false],
|
|
// DIE Regressionsfrage: Weicht das blosse ERLAUBEN die Leine auf?
|
|
// Wenn hier JA stuende, waere Paragraph 3.3 kaputt und makemkvcon
|
|
// koennte als Waise das Laufwerk festhalten (der rc10-Fund).
|
|
['2) Leine erlaubt Ausklinken, Kind normal — MUSS weiter sterben', 'erlaubt-normal', false],
|
|
['3) Leine erlaubt Ausklinken, Kind klinkt sich aus — der Installer', 'erlaubt-aus', true],
|
|
]
|
|
for (const [name, modus, erwartetLebend] of faelle) {
|
|
const ausgabe = execFileSync(process.execPath, [__filename, modus], { encoding: 'utf8' }).trim()
|
|
const pid = Number(/PID=(\d+)/.exec(ausgabe)?.[1] ?? 0)
|
|
const fehler = Number(/FEHLER=(\d+)/.exec(ausgabe)?.[1] ?? 0)
|
|
// Der innere Lauf ist hier schon beendet — die Arbeitsgruppe ist also
|
|
// zugeschnappt, falls sie zuschnappt.
|
|
const nochDa = pid > 0 ? lebt(pid) : false
|
|
const urteil = nochDa === erwartetLebend ? 'WIE ERWARTET' : 'ABWEICHUNG'
|
|
console.log(
|
|
`${name}\n Enkel-PID ${pid || '—'}${fehler ? ` (Win32-Fehler ${fehler})` : ''}` +
|
|
`\n lebt nach dem Tod des Elternprozesses: ${nochDa ? 'JA' : 'NEIN'} -> ${urteil}\n`,
|
|
)
|
|
if (nochDa) spawnSync('taskkill', ['/PID', String(pid), '/F', '/T'])
|
|
}
|
|
}
|
|
|
|
const modus = process.argv[2] ?? ''
|
|
if (modus === 'alt') innen(false, false)
|
|
else if (modus === 'erlaubt-normal') innen(true, false)
|
|
else if (modus === 'erlaubt-aus') innen(true, true)
|
|
else aussen()
|