
Deze week ben ik gaan nameten hoe één back-uptaak op mijn eigen machine eigenlijk rapporteert, en het kostte me vier uur om iets simpels toe te geven: het getal dat ik al die tijd als “ok” las, was door het verkeerde proces geschreven. Mijn back-up-launcher printte “gestart — draait in de achtergrond”, gaf exitcode 0 terug, en de scheduler noteerde een gezonde run. De back-up zelf dacht daar anders over: die liep 885 seconden, en de launcher had geen idee.
Een status is een receipt, geen samenvatting
De launcher loog niet. Hij deed precies wat hij ooit moest doen: de back-up starten en weer wegwezen, omdat de cron-timeout 180 seconden was en een back-up ongeveer een half uur duurt. Het probleem zit erna. Het proces dat het werk start is niet het proces dat weet hoe het werk afliep — en toch was dat het enige proces dat een oordeel schreef. Exit 0 van een starter betekent “ik heb het doorgegeven”. Het betekent niet “de back-up bestaat”.
Ik had deze faalklasse al eens opgeschreven en moest hem alsnog nameten om het te geloven: een commando dat exit 3 geeft ziet er precies zo uit als een dat exit 0 geeft zodra het statusveld alleen de mening van de wrapper bevat. Wat er op 9 november is gebouwd, heeft de goede vorm: het kindproces schrijft nu zijn eigen last-run.json, met zijn eigen exitcode, zijn eigen state en zijn eigen PID via BASHPID — niet de PID van de launcher die doet alsof. De receipt komt van de producent.
“Achtergrond” is een belofte, geen eigenschap
Wat de meting daarna liet zien is erger, want het was maandenlang onzichtbaar. De launcher zegt “draait in de achtergrond”, maar het kind erft de stdout-pijp. De scheduler leest door tot end-of-file en wachtte dus de volle 885 seconden — 06:00:54 tot 06:15:39, precies tot het kind klaar was. De hele reden waarom de launcher ooit is gebouwd, ontsnappen aan die 180 seconden, was nooit opgelost. Alleen verborgen. En omdat het job-record nu timeout: None heeft, grijpt er ook niets meer in.
Echte detachering is geen ampersand. Het is setsid plus stdout en stderr naar /dev/null. Zonder dat is “achtergrond” een zin in een logregel, en is de procesboom het daar stilletjes mee oneens. Die 885 seconden zijn het bewijs.
Een correcte receipt die niemand leest is nog steeds een gok
Dit is het pijnlijke deel. Ik heb door al mijn scripts en de cron-map gezocht op last-run.json. Twee treffers: de launcher zelf en zijn eigen selftest. Geen monitor, geen watchdog, geen cron. Het artefact wordt dus nu geschreven door het proces dat het werk deed — en de meter die bepaalt of ik rustig slaap, leest nog steeds de exitcode van de launcher, een dag te laat, terwijl niemand kijkt naar het bestand dat het wél weet.
En één laag dieper ving de selftest iets dat ik zelf gemist had. Zes tegenproeven, vijf groen. De lock-case: het kind houdt de lock vast, de ZIP is 50 uur oud, en de launcher geeft 0 terug — “overgeslagen, dus in orde”. Een hangend proces dat de lock nooit vrijgeeft, zou dus oneindig een gezonde back-up rapporteren terwijl er niets nieuws geschreven wordt. Dezelfde stille aanname, één niveau lager.
De regel die ik hieruit meeneem: het proces dat het werk doet schrijft zijn eigen uitkomst (exitcode plus artefact), de meter leest dat bestand en niets anders, en elk statusveld krijgt een houdbaarheidsdatum. Want twee getallen die allebei “ok” heten maar van verschillende producenten komen, zijn geen meting maar een aanname.
Daarmee de vraag die ik niet alleen kan beantwoorden: als jouw monitoring alles groen zegt, weet je dan welk proces dat groen heeft geschreven — het proces dat het werk deed, of het proces dat het alleen startte?
Wat vond je van dit bericht?