390 likes | 401 Views
This lecture discusses distributed model checking using explicit state models. Topics covered include distributed reachability, partition functions, termination conditions, and the creation of dependency structures.
E N D
Distributed Explicit State Model Checking Lecture 4 – 30.4.02 Lecturer: Karen Yorav
References • “Distributed-Memory Model Checking with SPIN” • Flavio Lerda and Riccardo Sisto • “Distributed LTL Model-Checking in SPIN” • J. Barnat, L. Brim, and J. Stribrna
Explicit State Sequential Reachability • Safety properties are verified by a reachability algorithm • Each state is a bit-vector • States are kept in a hash table • Reachability is done using a DFS algorithm • Recursion is simulated by a user defined stack
Procedure Start(start_state) { V := {}; DFS(start_state); } Procedure DFS(state) { if (state not in V) then { if (error(state)) then report and terminate; V := V + state; for each st = successor of state do DFS(st); }; }
Distributed Reachability • The state space is statically partitioned • partition(state) gives the node that owns state • When a node encounters a state that does not belong to it - the state is sent to the owner • Received states are kept in a FIFO queue • Verification ends when all nodes are idle and all queues are empty
Procedure Start(i, start_state) { V[i] := {}; U[i] := {}; j := Partition(start_state); if (i=j) then U[i] := U[i] + start_state; Visit(i); } Procedure Visit(i) { while true do { while U[i] = {} do {} state := get(U[i]); DFV(i, S); } }
Procedure DFV(i, start_state) { if (state not in V[i]) then { if (error(state)) then report and terminate; V[i] := V[i] + state; for each st = successor of state do { if (partition(st) = i) then DFV (i, st); else U[j] := U[j] + st; } }
Choosing the partition function • Must depend only on the state • Should divide the space evenly • Should minimize cross-transitions • First solution - partition the space of the SPIN hash function • cannot be implemented on a heterogeneous network • even distribution • does not minimize cross transitions
SPIN programs - PROMELA • Parallel composition of asynchronous processes • Uses “guarded commands” • Synchronized communication commands • Translation for model-checking: • each guarded command creates a “transition” • a transition is a (nd) function between states • a transition can be enabled or disabled in a state • a transition changes the local state of a process, or executes a communication command
A better partition function Define the partition function according to the value of the component of a designated process • a global state contains a state component for each concurrent process • a transition generally involves one or two processes • minimizes cross-transitions • distributes the state-space evenly
Termination • Processes inform manager when they become idle or busy • When all are idle - manager asks for confirmation • If a process became busy it sends a negative confirmation • Positive confirmation includes total number of messages sent & received • Termination - when numbers are equal
Why it won’t work for LTL • When 2 nested-DFS run in parallel S1 S2 S3 S4
Node A S1 S4 Node B S3 S2 S5 • The order matters A nested DFS should start from S iff all seeds below S have finished their nested DFS
Definitions • Seed - an accepting state, from which a nested DFS should start • Border state - a state that causes the computation to move node • it belongs to a different node than its predecessor on the DFS
Dependency Structure • Each node maintains its own DepS • Dynamic • Vertices are border states and seeds • Edges represent reachability • Underlying graph is a forest • A vertex contains: • a state • index - set of node names
The creation of DepS • The primary DFS algorithm creates vertices on the way down • A state received from another node becomes a root • labeled with the sending node • Every accepting state becomes a vertex • Every border node becomes a leaf
S1 S4 S3 S2 S5 Examples Node A Node B
15 5 1 2 3 4 6 7 8 9 10 12 14 13 11 Node A Node B Node C
Deleting vertices from DepS • A vertex is deleted only if: • the primary DFS has already backtracked through the state • all of its successors were deleted • When a seed is deleted it is sent to the global Seed_queue • Manager process dequeues states and runs nDFS • the next seed is dequeued only after the nDFS of the previous one has finished
The Algorithm • States have an extra bit to tell whether they are processed by DFV or NDFV • PV - queue of permanently visited states (seeds and border states) • V - queue of states visited while processing a single root. • <state in DepS> - the DepS node that holds state
Vertices in DepS have the fields: • parent, successors • state • DFS_gone • index • CREATE_IN_DepS(s, came_from) - adds a root vertex to DepS and puts came_from in the index • ADD_TO_DepS(s1, s2) - adds s2 to DepS, and an edge from s1 to s2
START: • sends start_state to the appropriate queue • runs the manager on node 0 and VISIT on all other nodes • VISIT: • receive a state, call DFS or NDFS • REMOVE: • sends seeds to the manager (to run the nested DFS) • removes vertex from DepS • recursively removes predecessors
DFS: • primary DFS algorithm • builds DepS on the way down • destructs DepS on the way up • NDFS: • nested DFS algorithm • finds cycles through accepting states
S1 S4 S3 S2 S5 Another example Node A Node B
Termination detection • Each node sends a message to the manager when changing state: • DFS status : busy / idle-empty-DepS / idle-nonempty-DepS • nDFS status: busy / idle • sent: # of sent messages overall • received: # of received messages overall
Termination (cont.) • Termination is detected when: • for all nodes DFS status = idle-empty-DepS and nDFS status = idle • sum of #sent = sum of #received • Manager also detects situations in which: • for all nodes DFS statusbusy and nDFS status = idle • sum of #sent = sum of #received • some nodes have DFS status = idle-nonempty-DepS
u • Manager requests DepS info from nodes • Each node sends a set of edges Xz --->Y where: • X is a vertex with nonempty index Z • Y is a border state that x is waiting for • u = 1 if there is a seed on the way, 0 otherwise • Manager builds a graph from these edges • Manager looks for a SCC with no outgoing edges • if there is a 1 edge -> counter example! • if not - send ACK to an arbitrary edge on the SCC
Procedure START(i, start_state) { Initialize local variables if (partition(start_state) = i) then U[i] := U[i] + {start_state}; if (i=0) then MANAGER_PROCESS(); else VISIT(i); }
Procedure VISIT(i) { while (PROCESS_INCOMING_PACKETS()) do { if (U[i] <> {}) then { get (state, came_from) from U[i]; toplevel := true; V[i] := {}; if (nested(state)) then { Seed := state.seed; NDFV(i, state); }
} else { if (state not in PV[i]) then { DFV(i, state); if (<state in DepS>.successors = {}) then REMOVE(<state in DepS>); } else { if (state in DepS) then <state in DepS>.index := <state in DepS>.index + {came_from}; else ACK (<state in DepS>); }; }; }; }; }
Procedure REMOVE(vertex) { if Accepting(vertex.state) then Seed_queue := Seed_queue + {vertex.state}; ACK(vertex); tmp := vertex.predecessors; forall (i in tmp) do i.successors := i.successors - {vertex}; free(vertex); forall (i in tmp) do if ((i.successors = {}) and (i <> nil) and (i.DFS_gone)) then REMOVE(i); }
Procedure DFV(i,state) { if (partition(state) <> i) then { U[partition(state)] := U[partition(state)] + {state}; ADD_TO_DepS (last_visited, state); return; }
} else { if (toplevel) then { PV[i] := PV[i] + state; CREATE_IN_DepS(state, came_from); last_visited := state; }; if (Accepting(state) & not(toplevel)) then { ADD_TO_DepS(last_visited, state); last_visited := state; PV[i] := PV[i] + state; }; toplevel := false; V[i] := V[i] + {state};
} else { if (toplevel) then { PV[i] := PV[i] + state; CREATE_IN_DepS(state, came_from); last_visited := state; }; if (Accepting(state) & not(toplevel)) then { ADD_TO_DepS(last_visited, state); last_visited := state; PV[i] := PV[i] + state; }; toplevel := false; V[i] := V[i] + {state};
forall (newstate in successors of state) do { if (newstate not in (V[i] + DepS + PV[i])) then DFV(i, newstate); else if ((newstate in DepS) and (not in stack)) then ADD_TO_DepS (last_visited, newstate); }; if (Accepting(state)) then { last_visited := <last_visited in DepS>.parent; <state in DepS>.DFS_gone := true; if (<state in DepS>.successors = {}) then REMOVE(<state in DepS>); }; }; }
forall (newstate in successors of state) do { if (newstate not in (V[i] + DepS + PV[i])) then DFV(i, newstate); else if ((newstate in DepS) and (not in stack)) then ADD_TO_DepS (last_visited, newstate); }; if (Accepting(state)) then { last_visited := <last_visited in DepS>.parent; <state in DepS>.DFS_gone := true; if (<state in DepS>.successors = {}) then REMOVE(<state in DepS>); }; }; }
Procedure NDFV(i, state) { if (partition(state) <> i) then { U[partition(state)] := U[partition(state)] + {state}; return; } else { PV[i] := PV[i] + state; forall (newstate in successors of state) do { if (Seed=newstate) then CYCLE_FOUND(); if (newstate not in (V[i] + PV[i])) then NDFV(i, newstate); }; }; }
Procedure NDFV(i, state) { if (partition(state) <> i) then { U[partition(state)] := U[partition(state)] + {state}; return; } else { PV[i] := PV[i] + state; forall (newstate in successors of state) do { if (Seed=newstate) then CYCLE_FOUND(); if (newstate not in (V[i] + PV[i])) then NDFV(i, newstate); }; }; }