Reliability and Robustness
Learning Objectives
Preserve invariants under failure; choose fail-fast vs graceful degradation.
Apply timeouts, cancellation, cleanup, idempotency and safe retries.
Isolate faults and recover without noisy defensive code.
Prerequisites: Modules 14, 51, 61.
Intuition and Definition
Reliability is continuing to deliver correct service; robustness is behaving sensibly when inputs or the environment are wrong.
Fail fast: detect programming errors and corrupted state immediately and stop (assertions, exceptions) — before damage spreads.
Graceful degradation: for *environmental* failures (a sensor offline, a service unreachable), keep a reduced but safe service (show "--" instead of a stale speed).
Idempotency: performing an operation twice has the same effect as once — prerequisite for safe retries.
Fault isolation: a failure in one component doesn't take down others (bulkheads, separate processes, supervisors).
Why It Matters
Everything fails eventually: networks, disks, sensors, dependencies. Robust objects keep their invariants and make failure states explicit and safe.
Syntax and Language Notes
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2)); // timeout as cancellation
await client.SendAsync(request, cts.Token);Complete Example
A fuel-level display that degrades gracefully on sensor faults, and an idempotent command handler that makes retries safe:
#include <iostream>
#include <optional>
#include <set>
#include <string>
#include <vector>
// graceful degradation with plausibility checks
class FuelGauge {
std::optional<double> lastGood_;
int faultyReadings_ = 0;
public:
std::string update(std::optional<double> raw) {
if (!raw || *raw < 0 || *raw > 100) { // missing or implausible
if (++faultyReadings_ >= 3) { lastGood_.reset(); return "-- (sensor fault, see service)"; }
return lastGood_ ? std::to_string(static_cast<int>(*lastGood_)) + "% (holding last value)" : "--";
}
faultyReadings_ = 0;
lastGood_ = raw;
return std::to_string(static_cast<int>(*raw)) + "%";
}
};
// idempotent handler: duplicate deliveries (retries) are harmless
class ChargeHandler {
std::set<std::string> processed_;
long long balance_ = 0;
public:
std::string handle(const std::string& requestId, long long amount) {
if (!processed_.insert(requestId).second) return "duplicate " + requestId + " ignored";
balance_ += amount;
return "charged " + std::to_string(amount) + " (balance " + std::to_string(balance_) + ")";
}
};
int mai
…fuel: 42%
fuel: 42% (holding last value)
fuel: 41%
fuel: 41% (holding last value)
fuel: 41% (holding last value)
fuel: -- (sensor fault, see service)
fuel: 40%
charged 500 (balance 500)
charged 200 (balance 700)
duplicate req-1 ignoredStep-by-Step Walkthrough
A single missing or implausible reading holds the last good value (labelled as such) — the driver isn't misled by noise.
Three faulty readings in a row switch to an explicit fault display instead of a stale number.
A good reading resets the fault counter.
The charge handler records request ids; the retried req-1 doesn't double-charge.
Common Mistakes
Swallowing errors and continuing with corrupted state ("defensive" catch (...) {}).
Showing stale data as if it were current. Retrying non-idempotent operations.
Noise: null checks and try/catch everywhere instead of validating at boundaries and trusting invariants inside.
No cleanup on failure paths (use RAII / finally / using).
Trade-offs and Alternatives
Fail fast for bugs, degrade gracefully for environmental failures. Supervisors (Erlang/Akka) restart failed components; watchdogs reset stuck ECUs. Safety-critical systems define safe states formally (ISO 26262) — the examples here illustrate the idea, not a certified design.
Real-World Use
Instrument clusters showing "--" for invalid signals, telematics units buffering data offline, payment APIs with idempotency keys (Stripe), Kubernetes restarting crashed containers, circuit breakers (Module 51).
Practice
Q1. (Easy) What does idempotent mean?
Answer: Doing it twice has the same effect as doing it once.
Q2. (Medium) Fail fast or degrade gracefully when an internal invariant is violated?
Answer: Fail fast — it indicates a bug; continuing risks corrupting more state.
Q3. (Advanced) How should the processed-id set be bounded in a long-running service?
Answer: Store ids with timestamps and expire them after the maximum retry window (or persist them with a TTL in a database/cache), so memory doesn't grow forever while duplicates within the window are still detected.
Interview Question
Q1. "How do you make an operation safe to retry?" Answer: Make it idempotent: give each logical operation a unique id (idempotency key) chosen by the client, record processed ids with their results, and return the stored result for duplicates; or design operations as absolute state changes ("set balance to X at version V" with optimistic concurrency) instead of relative ones ("add X"). Combine with timeouts, bounded exponential back-off with jitter, and a retry budget.
Quick Quiz
Q1. Graceful degradation is appropriate for…?
Answer: Environmental/external failures (sensor offline, dependency unavailable).
Q2. What makes duplicated messages harmless?
Answer: Idempotent handling (e.g. deduplication by message/request id).
Q3. What should replace scattered null checks?
Answer: Validation at boundaries and strong invariants inside objects.
Summary
Keep invariants; fail fast on bugs, degrade gracefully on environmental faults.
Timeouts, cancellation, cleanup, idempotency and isolation make systems robust.
Related: Modules 14, 51, 61, 76.