Entwicklung & Code

Aus Softwarefehlern lernen – Teil 3: Ein Marssonde gerät außer Kontrolle


close notice

This article is also available in
English.

It was translated with technical assistance and editorially reviewed before publication.

In der modernen Softwareentwicklung ist Nebenläufigkeit allgegenwärtig. Selbst kleine Anwendungen laufen oft auf Systemen mit mehreren Kernen, interagieren mit Datenbanken, warten auf Netzwerkantworten oder teilen sich Ressourcen wie Dateien und Speicherbereiche. In verteilten Systemen und Embedded-Software kommt noch hinzu, dass verschiedene Prozesse aufeinander reagieren müssen, oft unter Echtzeitbedingungen. Die Praxis zeigt: Sobald mehrere Dinge gleichzeitig passieren können, entstehen neue Fehlerklassen, die sich in seriellen Programmen nie gezeigt hätten.




Golo Roden ist Gründer und CTO von the native web GmbH. Er beschäftigt sich mit der Konzeption und Entwicklung von Web- und Cloud-Anwendungen sowie -APIs, mit einem Schwerpunkt auf Event-getriebenen und Service-basierten verteilten Architekturen. Sein Leitsatz lautet, dass Softwareentwicklung kein Selbstzweck ist, sondern immer einer zugrundeliegenden Fachlichkeit folgen muss.

Die Teile der Serie „Aus Softwarefehlern lernen“:

Ein berühmtes Beispiel ist der Mars Pathfinder, eine NASA-Mission aus dem Jahr 1997. Die Landung selbst war ein spektakulärer Erfolg – die Sonde setzte sanft auf dem Mars auf und begann, Daten zu senden. Doch kurz darauf kam es zu sporadischen Systemabstürzen und automatischen Resets, die das Team am Boden in Alarmbereitschaft versetzten.

Die Ursache war eine Priority Inversion, ein klassisches Concurrency-Problem. In einem Echtzeitbetriebssystem gibt es Aufgaben mit unterschiedlicher Priorität. Hohe Priorität bedeutet: Diese Aufgabe soll möglichst sofort laufen, sobald sie bereit ist. Niedrige Priorität darf sie nicht blockieren.

Auf dem Pathfinder lief eine solche hochpriorisierte Aufgabe, die Daten vom Wettersensor verarbeitete. Sie benötigte jedoch Zugriff auf eine gemeinsame Ressource – in diesem Fall einen Mutex, der von einer niedrig priorisierten Aufgabe gehalten wurde. Diese niedrig priorisierte Aufgabe wurde wiederum von einer mittel priorisierten Aufgabe ständig verdrängt. Das Ergebnis: Die hochpriorisierte Aufgabe wartete indirekt auf eine niedrige, die nie zum Zuge kam.

Dieses Phänomen der „Umkehrung der Prioritäten“ führte dazu, dass das System in bestimmten Lastsituationen hängen blieb und schließlich neu startete. Die Lösung war im Prinzip einfach: Die Entwicklerinnen und Entwickler aktivierten Priority-Inheritance im Echtzeitbetriebssystem VxWorks. Dadurch erbte die blockierende, niedrig priorisierte Aufgabe vorübergehend die hohe Priorität, sobald eine höherwertige Aufgabe auf sie wartete. Der Knoten löste sich, und die Abstürze verschwanden.

Dieses Beispiel ist lehrreich, weil es gleich mehrere typische Muster verdeutlicht:

  • Nebenläufigkeitsfehler sind schwer zu reproduzieren: Sie treten oft nur unter bestimmten Lastprofilen auf.
  • Redundanz oder Wiederholungen helfen nicht automatisch: Wenn der Fehler im Design liegt, trifft er alle Instanzen gleichermaßen.
  • Kleinste Details im Scheduling können den Unterschied machen: Die Software kann tausendmal korrekt laufen und beim tausend-und-ersten Mal ausfallen.

In modernen Anwendungen können ähnliche Probleme in Form von Deadlocks, Race Conditions oder Livelocks auftreten. Diese zeigen sich meist nicht im lokalen Testlauf, sondern erst in der Produktion, wenn reale Last und reale Parallelität wirken. Doch wie lassen sich solche Fehler vermeiden?

  1. Klare Lock-Hierarchien: Wenn mehrere Ressourcen gesperrt werden, sollte immer in derselben Reihenfolge gelockt werden.
  2. Prioritätsprotokolle nutzen: Mechanismen wie Priority Inheritance oder Priority Ceiling sind in vielen Echtzeitbetriebssystemen und sogar in modernen Frameworks verfügbar.
  3. Nebenläufigkeit entkoppeln: Statt gemeinsame Zustände direkt zu sperren, können Architekturen mit Message Passing oder Actor-Modellen Race Conditions vermeiden.
  4. Deterministische Tests und Simulationen: Spezielle Testframeworks können Prozesse gezielt verzögern oder Scheduler manipulieren, um seltene Konflikte reproduzierbar zu machen.
  5. Telemetrie und Monitoring: Auch im Betrieb sollte sichtbar sein, wenn Locks ungewöhnlich lange gehalten werden.

Für Teams, die Web-Backends oder Cloud-Services entwickeln, zeigt sich übrigens dieselbe Gefahr, nur in geringfügig anderer Form: Datenbanktransaktionen, verteilte Caches oder konkurrierende API-Requests können ähnliche Effekte haben. Ein langsamer Hintergrundprozess blockiert einen Lock, während eine Flut von parallelen Requests diesen Zustand eskalieren lässt.

Die Lehre aus dem Pathfinder-Vorfall ist daher zeitlos: Nebenläufigkeit ist kein kostenloser Performance-Booster, sondern ein komplexes System, das Entwicklerinnen und Entwickler explizit entwerfen und überwachen müssen. Wer Concurrency als Randthema behandelt, wird früher oder später auf schwer reproduzierbare und potenziell katastrophale Fehler stoßen.

Diese Artikelserie stellt neun typische Fehlerklassen vor, die in der Praxis immer wieder auftauchen – unabhängig von Branche oder Technologie. In jeder Kategorie wird die Serie ein konkretes Beispiel vorstellen, dessen Ursachen analysieren und daraus ableiten, was Softwareentwicklerinnen und Softwareentwickler langfristig lernen können.

Im nächsten Teil lesen Sie: Zeit, Kalender und Geografie: Wenn die Uhr nicht das misst, was man denkt.


(who)



Source link

Beliebt

Die mobile Version verlassen