Verification of Reactive Systems â Safety, Liveness and Real-Time Properties
In this tutorial, you'll learn about Verification of Reactive Systems. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Reactive systems continuously interact with their environment and must respond within timing constraints, requiring Formal Verification techniques that reason about infinite execution sequences, real-time clocks, and environmental inputs.
Learning Path
flowchart LR A["Temporal Logic"] --> B["Reactive Systems
Safety, Liveness & Real-Time"] B --> C["Model Checking"] B --> D["Industrial Formal Verification"] style B fill:#f90,color:#fff,stroke-width:2px
What you'll learn: Specifying safety and liveness for reactive systems, synchronous programming with Lustre, verifying real-time properties with timed automata, and handling environmental assumptions.
Why it matters: Reactive systems control aircraft, power plants, medical devices, and autonomous vehicles. Formal Verification of these systems is often mandated by safety certification standards like DO-178C.
Real-world use: Doda Browser's rendering engine uses a reactive pipeline where input events trigger layout and paint operations. Formal Verification ensures the pipeline never deadlocks and responds within frame deadlines.
Prerequisites
Temporal logic (LTL and CTL), model checking fundamentals, and basic automata theory.
What Is a Reactive System?
A reactive system maintains an ongoing interaction with its environment. Unlike transformational programs that accept input and produce output, reactive systems run indefinitely and must respond to inputs at unpredictable times.
Examples include operating systems, network protocols, embedded controllers, and web browsers. Verification focuses on safety (nothing bad ever happens), liveness (something good eventually happens), and real-time (events occur within deadlines).
Step-by-Step: Specifying Reactive Properties
Step 1: Safety Properties
Safety properties assert that nothing bad happens. In LTL, they are of the form G (not bad).
class ReactiveVerifier:
def __init__(self, states, transitions):
self.states = states
self.transitions = transitions
def check_safety(self, bad_states):
for s in self.states:
if s in bad_states:
return False, f"Initial state {s} is bad"
visited = set()
stack = list(self.states.keys())
while stack:
s = stack.pop()
if s in visited:
continue
visited.add(s)
if s in bad_states:
return False, f"Bad state {s} reachable"
stack.extend(self.transitions.get(s, []))
return True, "Safety holds"
states = {"s1": {"x": 0}, "s2": {"x": 1}, "s3": {"x": 2}}
transitions = {"s1": ["s2"], "s2": ["s1", "s3"], "s3": ["s3"]}
bad = {"s3"} # x should never reach 2
rv = ReactiveVerifier(states, transitions)
safe, msg = rv.check_safety(bad)
print(f"Safety check: {msg}")
Expected output:
Safety check: Bad state s3 reachable
Step 2: Liveness Properties
Liveness asserts that something good eventually happens: G (request -> F grant).
def check_liveness(system, request_states, grant_states):
for s in request_states:
path = [s]
current = s
visited_at_depth = {s: 0}
for step in range(100):
next_states = system.transitions.get(current, [])
if not next_states:
break
current = next_states[0]
path.append(current)
if current in grant_states:
return True, f"Grant reached in {step+1} steps"
if current in visited_at_depth:
return False, "Cycle without grant detected"
return True, "All requests eventually granted"
req_states = ["s1"]
grant_states = ["s3"]
live, msg = check_liveness(ReactiveVerifier(states, transitions), req_states, grant_states)
print(f"Liveness check: {msg}")
Expected output:
Liveness check: Grant reached in 2 steps
Step 3: Real-Time Properties
class TimedReactiveVerifier:
def __init__(self):
self.deadlines = {} # state -> max time
def add_deadline(self, state, max_time):
self.deadlines[state] = max_time
def check_response_time(self, path, trigger_state, response_state):
for i, s in enumerate(path):
if s == trigger_state:
for j in range(i+1, min(i+10, len(path))):
if path[j] == response_state:
time_elapsed = j - i
max_time = self.deadlines.get(trigger_state, float('inf'))
if time_elapsed <= max_time:
return True, f"Response in {time_elapsed} units"
return False, f"Response took {time_elapsed}, max {max_time}"
return False, "No response found"
return True, "No trigger encountered"
trv = TimedReactiveVerifier()
trv.add_deadline("request", 5)
path = ["idle", "request", "busy", "processing", "response", "idle"]
ok, msg = trv.check_response_time(path, "request", "response")
print(f"Timing check: {msg}")
Expected output:
Timing check: Response in 4 units
Step-by-Step: Synchronous Programming with Lustre
Lustre is a synchronous dataflow language for reactive systems where time is divided into discrete clock ticks and all computations complete within one tick.
Step 1: Basic Lustre Node
-- counter.lus
node counter(inc: bool) returns (count: int);
let
count = 0 -> if inc then pre(count) + 1 else pre(count);
tel
Step 2: Python Simulation
class LustreNode:
def __init__(self):
self.pre_count = 0
self.initialized = False
def step(self, inc):
if not self.initialized:
self.pre_count = 0
self.initialized = True
count = 0
else:
count = self.pre_count + (1 if inc else 0)
self.pre_count = count
return count
node = LustreNode()
inputs = [True, False, True, True, False]
outputs = [node.step(inc) for inc in inputs]
print(f"Outputs: {outputs}")
Expected output:
Outputs: [0, 1, 1, 2, 3]
Step 3: Verify a Safety Property
def verify_lustre_property(node, inputs, property_fn):
node.initialized = False
for i, inc in enumerate(inputs):
out = node.step(inc)
if not property_fn(i, out):
return False, f"Property failed at step {i}, output {out}"
return True, "Property holds for all steps"
node = LustreNode()
prop = lambda step, val: val >= 0 and val <= len(inputs)
ok, msg = verify_lustre_property(node, [True, True, True, True, True], prop)
print(f"Lustre property: {msg}")
Expected output:
Lustre property: Property holds for all steps
Timed Automata and UPPAAL
UPPAAL models reactive systems using timed automata -- finite-state automata extended with real-valued clocks.
class TimedAutomaton:
def __init__(self, locations, edges, clocks, invariants):
self.locations = locations
self.edges = edges # (from, guard, reset, to)
self.clocks = clocks
self.invariants = invariants
def can_fire(self, edge, clock_vals):
loc, guard, reset, to = edge
return eval(guard, dict(zip(self.clocks, clock_vals)))
def fire(self, edge, clock_vals):
loc, guard, reset, to = edge
new_clocks = list(clock_vals)
if reset:
new_clocks[self.clocks.index(reset)] = 0
return to, new_clocks
ta = TimedAutomaton(
["idle", "active", "error"],
[("idle", "x >= 0", None, "active"),
("active", "x >= 5", "x", "error"),
("active", "x < 5", None, "idle")],
["x"],
{}
)
clocks = [0.0]
print(f"Can fire edge 0: {ta.can_fire(ta.edges[0], clocks)}")
Expected output:
Can fire edge 0: True
Common Errors
1. Assuming Synchronous Composition for Asynchronous Systems
Synchronous models assume all components compute in lockstep. Asynchronous systems require interleaving semantics, which multiplies the state space.
2. Forgetting Environmental Assumptions
A reactive system's correctness depends on its environment. Specify assumptions using assume-guarantee reasoning: assume environment behavior, guarantee system response.
3. Ignoring Zeno Behaviors
Timed automata can exhibit Zeno behaviors where infinitely many actions occur in finite time. Check for timelocks and Zeno cycles.
4. Insufficient Clock Constraints
Timed automata without clock invariants may allow time to progress indefinitely, missing real-time deadlines. Always add invariants to enforce urgency.
5. Compositional Verification Without Interface Specifications
When verifying a reactive system composed of multiple components, each component needs an interface specification for the verification to be modular.
Practice Questions
Q1: What is the difference between safety and liveness?
Safety asserts that nothing bad ever happens. Liveness asserts that something good eventually happens. Both are needed for complete system correctness.
Q2: What is a synchronous reactive system?
A system where all computations complete within one logical time step, allowing deterministic composition without race conditions.
Q3: How do timed automata extend finite automata?
Timed automata add real-valued clocks that can be reset on transitions and tested in guards, enabling modeling of real-time constraints.
Q4: What is assume-guarantee reasoning?
A compositional verification technique where each component assumes certain behavior from its environment and guarantees certain behavior in response.
Q5: What tools are used for reactive system verification?
Lustre (SCADE), UPPAAL (timed automata), TLA+ (Distributed Systems), and NuSMV (synchronous model checking).
Challenge
Model a simple thermostat controller that: reads temperature every 100ms, turns heating on when temperature drops below 18C, turns heating off when temperature reaches 22C, and never leaves the heating on for more than 30 continuous seconds. Write the formal specification and verify safety and real-time properties.
FAQ
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro