Modular Verification of Message Passing Programs Actor Services Modular Verification of Message Passing Programs ↝ 20th May 2016 Dagstuhl Seminar 16201 Alexander J. Summers Peter Müller ETH Zurich, Switzerland
Passing messages for the next ESOP ! ESOP 2017 Prof. Alice
Passing messages for the next ESOP alice.tell(conf) ↝ conf.submit(_) ESOP 2017 Prof. Alice idea idea + plan Dr. Bob (PhD student) this.exps(_) ↝ this.mentor.results(_) running experiments…
Passing messages for the next ESOP alice.tell(conf) ↝ conf.submit(_) ESOP 2017 Prof. Alice idea idea + plan Dr. Bob (PhD student) bob.propose(idea) ↝ this.exps(_) ↝ this.mentor.results(_) running experiments…
Passing messages for the next ESOP alice.tell(conf) ↝ conf.submit(_) ESOP 2017 Prof. Alice idea idea + plan Dr. Bob (PhD student) bob.propose(idea) ↝ alice.response(idea) this.exps(_) ↝ this.mentor.results(_) running experiments…
Passing messages for the next ESOP alice.tell(conf) ↝ conf.submit(_) ESOP 2017 Prof. Alice idea idea + plan Dr. Bob (PhD student) bob.propose(idea) ↝ alice.response(idea) where idea.name == old(idea.name) && idea.plan != null this.exps(_) ↝ this.mentor.results(_) running experiments…
Actor Services Novel assertions specifying response properties trigger message responsemessage bob.propose(idea) ↝ alice.response(idea) where idea.name == old(idea.name) … Novel assertions specifying response properties May depend on actor instance, program state Abstract over how property is guaranteed number of intervening messages / actors involved but, some finite sequence of messages Where-clauses express functional properties concerning message arguments and program heap two-state assertions relate heap@response to heap@trigger
Actor Services: Intuitive Semantics trigger message responsemessage bob.propose(idea) ↝ alice.response(idea) where idea.name == old(idea.name) … Related to LTL property: ⃞(trigger ⇒ ⃟response) We incorporate stateful properties, incl. shared heap Our semantics avoids explicit traces (non-modular) trigger response where condition
Mnesia distributed database protocol (Ericsson)
Mnesia distributed database protocol (Ericsson)
Mnesia distributed database protocol (Ericsson)
Mnesia distributed database protocol (Ericsson)
Mnesia distributed database protocol (Ericsson) query_setup
Mnesia distributed database protocol (Ericsson) query_setup
Mnesia distributed database protocol (Ericsson) query_setup next
Mnesia distributed database protocol (Ericsson) query_setup next next
Mnesia distributed database protocol (Ericsson) query_setup next next next
Mnesia distributed database protocol (Ericsson) next next query_setup next next next
Mnesia distributed database protocol (Ericsson) next next query_setup ready next next next
Mnesia distributed database protocol (Ericsson) next next query_setup ready req next next next
Mnesia distributed database protocol (Ericsson) sols next query_setup ready req next next next
Mnesia distributed database protocol (Ericsson) sols sols query_setup ready req next next next
Mnesia distributed database protocol (Ericsson) sols sols query_setup ready req sols sols sols
Mnesia distributed database protocol (Ericsson) sols sols query_setup enough? ready req sols sols sols
Mnesia distributed database protocol (Ericsson) sols query_setup ready req
Mnesia distributed database protocol (Ericsson) sols sols query_setup ready req
Mnesia distributed database protocol (Ericsson) sols sols query_setup ready req sols sols sols
Mnesia distributed database protocol (Ericsson) sols sols query_setup enough? ready req sols sols sols
Mnesia distributed database protocol (Ericsson) sols sols query_setup ready req sols response sols sols Want to prove: a request will eventually get a response (Ericsson verification case study [Arts & Dam ‘99])
Programming Language handler query_setup(Query query, User u) { val subqueries := // break down query - non-empty var n := this; for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } Actors have implicit mailboxes, and receive loops (active objects)
Programming Language handler query_setup(Query query, User u) { val subqueries := // break down query - non-empty var n := this; for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } Actors have implicit mailboxes, and receive loops (active objects) Actor classes declare handlers to execute on message receive
Programming Language handler query_setup(Query query, User u) { val subqueries := // break down query - non-empty var n := this; for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } Actors have implicit mailboxes, and receive loops (active objects) Actor classes declare handlers to execute on message receive Handler code may send messages, no blocking operations
Local Actor Services handler query_setup(Query query, User u) { val subqueries := // break down query - non-empty var n := this; for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } M.query_setup(Q,U) ↝ U.ready(M) Local actor service: provable using code of single message handler
Deriving actor services Local services: response sent directly by message handler the base-cases for deriving actor services Derivation rules prescribe when an actor service holds e.g. actor services can be composed : Where-conditions must also be combined (three states) M.query_setup(Q,U) ↝ U.ready(M) (x.query_setup(Q,user) ↝ user.ready(x)) && (user.ready(x) ↝ m.req(…)) ⊨ x.query_setup(Q,user) ↝ m.req(…) see the paper!
Composition and heap expressions e.g. query workers (another local actor service): we interpret W.next in state when response is sent Suppose we have instances x, y with x.next == y Similar composition? … Only sound if x.next will not be modified in future W.sols(S,P) ↝ W.next.sols(S,P) (x.sols(S,P) ↝ x.next.sols(S,P)) && (y.sols(S,P) ↝ y.next.sols(S,P)) ⊨ x.sols(S,P) ↝ y.next.sols(S,P)
Permissions, Immutability, Future States Two kinds of permissions to heap locations Exclusive permission, denoted acc(e.f) ownership, sendable via messages (preconditions) Immutable permission, denoted immut(e.f) only allows reading, freely duplicable Can freeze locations: transmute acc into immut Define future states relation: Σ1 ≼ Σ2 iff any immut location in Σ1 is immut in Σ2 and the value of that location is the same in Σ1,Σ2 Assertions depending only on immutable locations can be projected to future states (x.next == y on prev. slide)
Mnesia distributed database protocol (Ericsson) query_setup
Mnesia distributed database protocol (Ericsson) query_setup next
Mnesia distributed database protocol (Ericsson) query_setup next next
Mnesia distributed database protocol (Ericsson) query_setup next next next
Deriving Actor Services handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); }
Deriving Actor Services handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } (i=0 ⇒ n = this) && (i>0 ⇒
Deriving Actor Services handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } (i=0 ⇒ n = this) && (i>0 ⇒ n.sols(S,P) ↝ this.sols(_,P))
Deriving Actor Services handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } (i=0 ⇒ n = this) && (i>0 ⇒ n.sols(S,P) ↝ this.sols(_,P))
Deriving Actor Services handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } (i=0 ⇒ n’= this) && (i>0 ⇒ n’.sols(S,P) ↝ this.sols(_,P))
Deriving Actor Services handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } (i=0 ⇒ n’= this) && (i>0 ⇒ n’.sols(S,P) ↝ this.sols(_,P)) && (n.sols(S,P) ↝ n.next.sols(_,P))
Deriving Actor Services handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } (i=0 ⇒ n’= this) && (i>0 ⇒ n’.sols(S,P) ↝ this.sols(_,P)) && (n.sols(S,P) ↝ n.next.sols(_,P)) && immut(n.next) && n.next = n’
Deriving Actor Services handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } (i=0 ⇒ n’= this) && (i>0 ⇒ n’.sols(S,P) ↝ this.sols(_,P)) && (n.sols(S,P) ↝ n.next.sols(_,P)) && immut(n.next) && n.next = n’
Deriving Actor Services handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } (i=0 ⇒ n’= this) && (i>0 ⇒ n’.sols(S,P) ↝ this.sols(_,P)) && (n.sols(S,P) ↝ n’.sols(_,P)) && immut(n.next) && n.next = n’
Deriving Actor Services handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } (i=0 ⇒ n’= this) && (i>0 ⇒ n’.sols(S,P) ↝ this.sols(_,P)) && (n.sols(S,P) ↝ n’.sols(_,P))
Deriving Actor Services handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } (i=0 ⇒ n’= this) && (i>0 ⇒ n.sols(S,P) ↝ this.sols(_,P))
Deriving Actor Services handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } n.sols(S,P) ↝ this.sols(_,P)
Deriving Actor Services handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } n.sols(S,P) ↝ this.sols(_,P)
Deriving Actor Services handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } n.sols(S,P) ↝ this.sols(_,P) ⊨ this.next.sols(S,P) ↝ this.sols(_,P)
Deriving Actor Services handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } n.sols(S,P) ↝ this.sols(_,P) ⊨ this.next.sols(S,P) ↝ this.sols(_,P)
Deriving Actor Services handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } n.sols(S,P) ↝ this.sols(_,P) ⊨ immut(this.next) && this.next.sols(S,P) ↝ this.sols(_,P)
Alternative responses sols sols query_setup ready req sols response sols sols
Alternative Response Messages message sols(seq<Solution> solutions, int packetSize) { if(this.next != null && this.user != null) { seq<Solution> newStore := solutions ++ this.store; if(|solutions| = 0 || |newStore| >= this.nrSolutions) { this.user.response(newStore); } else { this.next.sols([], packetSize); this.store := newStore; } } }
Alternative Response Messages message sols(seq<Solution> solutions, int packetSize) { if(this.next != null && this.user != null) { seq<Solution> newStore := solutions ++ this.store; if(|solutions| = 0 || |newStore| >= this.nrSolutions) { this.user.response(newStore); } else { this.next.sols([], packetSize); this.store := newStore; } } } this.sols(S,P) ↝ this.user.response(_)
Alternative Response Messages message sols(seq<Solution> solutions, int packetSize) { if(this.next != null && this.user != null) { seq<Solution> newStore := solutions ++ this.store; if(|solutions| = 0 || |newStore| >= this.nrSolutions) { this.user.response(newStore); } else { this.next.sols([], packetSize); this.store := newStore; } } } this.sols(S,P) ↝ this.user.response(_)
Alternative Response Messages message sols(seq<Solution> solutions, int packetSize) { if(this.next != null && this.user != null) { seq<Solution> newStore := solutions ++ this.store; if(|solutions| = 0 || |newStore| >= this.nrSolutions) { this.user.response(newStore); } else { this.next.sols([], packetSize); this.store := newStore; } } } this.sols(S,P) ↝ this.user.response(_)
Alternative Response Messages message sols(seq<Solution> solutions, int packetSize) { if(this.next != null && this.user != null) { seq<Solution> newStore := solutions ++ this.store; if(|solutions| = 0 || |newStore| >= this.nrSolutions) { this.user.response(newStore); } else { this.next.sols([], packetSize); this.store := newStore; } } } this.sols(S,P) ↝ this.user.response(_) |
Alternative Response Messages message sols(seq<Solution> solutions, int packetSize) { if(this.next != null && this.user != null) { seq<Solution> newStore := solutions ++ this.store; if(|solutions| = 0 || |newStore| >= this.nrSolutions) { this.user.response(newStore); } else { this.next.sols([], packetSize); this.store := newStore; } } } this.sols(S,P) ↝ this.user.response(_) | this.next.sols(_,P)
Alternative Response Messages message sols(seq<Solution> solutions, int packetSize) { if(this.next != null && this.user != null) { seq<Solution> newStore := solutions ++ this.store; if(|solutions| = 0 || |newStore| >= this.nrSolutions) { this.user.response(newStore); } else { this.next.sols([], packetSize); this.store := newStore; } } } IDEA: introduce this if-condition this.sols(S,P) ↝ this.user.response(_) | this.next.sols(_,P)
Alternative Response Messages message sols(seq<Solution> solutions, int packetSize) { if(this.next != null && this.user != null) { seq<Solution> newStore := solutions ++ this.store; if(|solutions| = 0 || |newStore| >= this.nrSolutions) { this.user.response(newStore); } else { this.next.sols([], packetSize); this.store := newStore; } } } IDEA: introduce this if-condition this.sols(S,P) ↝ this.user.response(_) | this.next.sols(_,P) | ε
Alternative Response Messages message sols(seq<Solution> solutions, int packetSize) { if(this.next != null && this.user != null) { seq<Solution> newStore := solutions ++ this.store; if(|solutions| = 0 || |newStore| >= this.nrSolutions) { this.user.response(newStore); } else { this.next.sols([], packetSize); this.store := newStore; } } } IDEA: introduce this if-condition this.sols(S,P) ↝ this.user.response(_) | this.next.sols(_,P) | ε where old(this.next = null ∨ this.user = null)
Alternative Response Messages message sols(seq<Solution> solutions, int packetSize) { if(this.next != null && this.user != null) { seq<Solution> newStore := solutions ++ this.store; if(|solutions| = 0 || |newStore| >= this.nrSolutions) { this.user.response(newStore); } else { this.next.sols([], packetSize); this.store := newStore; } } } this.sols(S,P) ↝ this.user.response(_) | this.next.sols(_,P) | ε where old(this.next = null ∨ this.user = null) (another local actor service)
Recall: proof of query setup handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } n.sols(S,P) ↝ this.sols(_,P) ⊨ immut(this.next) && this.next.sols(S,P) ↝ this.sols(_,P)
Eliminating alternative responses sols query_setup ready req sols response this.sols(S,P) ↝ this.user.response(_) | this.sols(_,P) | ε where old(this.next = null ∨ this.user = null)
Eliminating alternative responses sols query_setup ready req sols response this.sols(S,P) ↝ this.user.response(_) | this.sols(_,P) | ε where old(this.next = null ∨ this.user = null)
Eliminating alternative responses sols query_setup ready req sols response this.sols(S,P) ↝ this.user.response(_) | this.sols(_,P) | ε where old(this.user = null)
Eliminating alternative responses sols query_setup ready req sols response this.sols(S,P) ↝ this.user.response(_) | this.sols(_,P) | ε where old(this.user = null)
Eliminating alternative responses sols query_setup enough? ready req sols response this.sols(S,P) ↝ this.user.response(_) | this.sols(_,P) | ε where old(this.user = null)
Eliminating alternative responses sols query_setup enough? ready req sols response see the paper! this.sols(S,P) ↝ this.user.response(_) | this.sols(_,P) where localVariant(this) | ε where old(this.user = null)
Eliminating alternative responses sols query_setup enough? ready req sols response see the paper! this.sols(S,P) ↝ this.user.response(_) | this.sols(_,P) where localVariant(this) | ε where old(this.user = null)
Eliminating alternative responses sols query_setup ready req sols response this.sols(S,P) ↝ this.user.response(_) | ε where old(this.user = null)
Eliminating alternative responses sols query_setup ready req sols response this.sols(S,P) ↝ this.user.response(_) | ε where old(this.user = null) ?
Eliminating alternative responses sols query_setup ready req sols response this.req(U,P) ↝ this.sols(_,P) where immut(this.user) && this.user = U && U ≠ null this.sols(S,P) ↝ this.user.response(_) | ε where old(this.user = null)
Eliminating alternative responses sols query_setup ready req sols response this.req(U,P) ↝ this.sols(_,P) where immut(this.user) && this.user = U && U ≠ null this.sols(S,P) ↝ this.user.response(_) | ε where old(this.user = null)
Eliminating alternative responses sols query_setup ready req sols response this.req(U,P) ↝ this.sols(_,P) where immut(this.user) && this.user = U && U ≠ null this.sols(S,P) ↝ this.user.response(_) | ε where old(this.user = null)
Eliminating alternative responses sols query_setup ready req sols response this.req(U,P) ↝ this.sols(_,P) where immut(this.user) && this.user = U && U ≠ null this.sols(S,P) ↝ this.user.response(_) | ε where old(this.user = null)
Eliminating alternative responses sols query_setup ready req sols response this.req(U,P) ↝ U.response(_) | ε where old(U = null && U ≠ null)
Eliminating alternative responses sols query_setup ready req sols response this.req(U,P) ↝ U.response(_) | ε where old(U = null && U ≠ null)
Eliminating alternative responses sols query_setup ready req sols response this.req(U,P) ↝ U.response(_) | ε where false
Eliminating alternative responses sols query_setup ready req sols response this.req(U,P) ↝ U.response(_) | ε where false
Eliminating alternative responses sols query_setup ready req sols response this.req(U,P) ↝ U.response(_)
Eliminating alternative responses sols query_setup ready req sols response this.req(U,P) ↝ U.response(_)
Back to the query setup… handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } this.req(U,P) ↝ U.response(_) derivable for this query manager, at this program point
Back to the query setup… handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } this.req(U,P) ↝ U.response(_) derivable for this query manager, at this program point In particular, by the time of the ready response message …
Back to the query setup… handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } this.req(U,P) ↝ U.response(_) derivable for this query manager, at this program point In particular, by the time of the ready response message … M.query_setup(Q,U’) ↝ U’.ready(M)
Back to the query setup… handler query_setup(Query query, User u) { var n := this; seq<Query> subqueries := // break down query - non-empty for(int i := 0; i < |subqueries|; i := i + 1) { n := spawn QueryWorker(next := n, localQuery := subqueries[i], store := []); } this.next := n; u.ready(this); } this.req(U,P) ↝ U.response(_) derivable for this query manager, at this program point In particular, by the time of the ready response message … M.query_setup(Q,U’) ↝ U’.ready(M) where (M.req(U,P) ↝ U.response(_))
So we’re done (almost…) Derived an actor service characterising responsiveness: Abstracts over workings of protocol, worker actors Combined with user which promises ready… ↝ req… … the property that Ericsson wanted We built our proof up modularly workers don’t need to know about managers clients don’t need to know about anything (more) M.query_setup(Q,U’) ↝ U’.ready(M) where (M.req(U,P) ↝ U.response(_)) M.query_setup(Q,user) ↝ user.response(_)
Conclusions Actor services: novel specification and proof technique composition, where-clauses, alternatives, … semantics enabling modular proofs Local actor services: base-cases of actor services Lots more in the paper: Hoare Logic for proofs against implementation Obligation assertions as a tool for liveness properties Formal semantics for actor service assertionsInvariants and variants for actor-local state Hierarchical proofs (actor service assumptions) Soundness argument sketch Related and (lots of) Future Work – please ask
Any questions? sols sols query_setup ready req sols response sols sols M.query_setup(Q,U’) ↝ U’.ready(M) where (M.req(U,P) ↝ U.response(_))