Skip to content

Verification of Reactive Systems — Safety, Liveness and Real-Time Properties

DodaTech Updated 2026-06-23 7 min read

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
â„šī¸ Info

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

### What is the synchronous hypothesis?

The assumption that outputs are produced synchronously with inputs in zero logical time, enabling deterministic reactive system design.

### Can reactive verification be fully automated?

For finite-state reactive systems with real-time constraints, tools like UPPAAL provide automated verification. Infinite-state systems may require Theorem Proving.

### What is SCADE?

SCADE (Safety-Critical Application Development Environment) is an industrial tool based on Lustre, used for certifying avionics software to DO-178C Level A.

### How does reactive verification apply to web browsers?

Browser rendering pipelines are reactive: they process user input events, network responses, and timer events while maintaining responsiveness and security properties.

### What is the most common bug in reactive systems?

Unhandled environmental inputs that cause the system to enter an undefined state, violating safety properties.


Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro