Software Architecture and Larger System Design Issues Lecture 6: Advanced state modeling/analysis Topics: – Modeling/analyzing concurrent behaviors using UML state diagrams – Chapter 6 in Blaha and Rumbaugh – Additional topics not in the Blaha/Rumbaugh book CSE 335: Software Design E. Kraemer Outline of course topics Foundational OO concepts Synthetic concepts Software architecture and larger design issues: – Strategic design decisions that influence a host of smaller, more tactical design decisions • E.g., policy for persistence of data in long-running system • E.g., allocating functionality to a single, centralized system vs. distributing functionality among a collection of communicating hosts – – – – Often involve a major capital investment Source of both risk and opportunity Require lots of a priori modeling and analysis Focus: Design issues related to concurrent and distributed systems Software process issues CSE 335: Software Design E. Kraemer Analytical models of behavior Thus far, the models we have employed have proved useful for – documentation/explanation – “roughing out” a design prior to implementation Still, they are not very rigorous: – E.g., sequence diagrams depict only one scenario of interaction among objects – Not good for reasoning about space of possible behaviors Such reasoning requires more formal and complete models of behavior CSE 335: Software Design E. Kraemer State diagrams Useful for modeling “space of behaviors” of an object or a system of interacting objects Requires: – Identifying and naming the conceptual “states” that an object might be in, and – Conceivable transitions among those states and the events (and/or conditions) that trigger (and/or guard) these transitions Concurrent compositions of state diagrams can be “executed” to expose anomalous behaviors CSE 335: Software Design E. Kraemer Communication among concurrent state machines More interesting applications involve interaction (explicit communication) among state machines Examples: – Active client objects interacting with a shared queue – Sensor object notifying a software controller – Perhaps even an object invoking a method on another UML provides send actions by which one machine may signal another CSE 335: Software Design E. Kraemer Simple example: Client using a queue c1 : Client q : Queue System comprises two objects Each object modeled by a state machine System state at any time is the pair (state of c1, state of q) CSE 335: Software Design E. Kraemer Modeling method invocations Given state machines for two objects C and S, where C is the client and S the supplier of a method m Model call and return of m as separate signals C sends the call signal to S and then enters a waiting state, which is exited upon reception of a return signal from S CSE 335: Software Design E. Kraemer Example Client ... / send S.mCall(this, ...) Waiting mRet(...) Supplier mCall(caller, ...) CSE 335: Software Design Processing Body of M ... / send caller.mRet(...) E. Kraemer Exercise Develop state models for a simple client and a queue, assuming: – class Client does nothing but repeatedly pull items off of the queue – class Queue provides an operation pull, which is implemented by calling empty, back, and pop on a private data member of type queue<string> CSE 335: Software Design E. Kraemer Example: Client model Client Initializing / send q.pullCall(this) Waiting pullReturn(b,s) do / processString(b,s) CSE 335: Software Design E. Kraemer Example: Shared queue Queue ProcessingPullCall Idle pullCall(caller) Checking [!q.empty()] [q.empty()] / send caller.pullRet(false,empty) / send caller.pullRet(true,s) CSE 335: Software Design Empty NotEmpty do / s := q.back E. Kraemer Recall: Two active clients sharing a queue c1 : Client c2 : Client q q q : Queue CSE 335: Software Design E. Kraemer Question Do our state diagrams for classes Client and Queue accurately model the behaviors of active clients acting on a shared queue? CSE 335: Software Design E. Kraemer Question Do our state diagrams for classes Client and Queue accurately model the behaviors of active clients acting on a shared queue? Answer really depends upon the “semantics” of event handling among concurrent state machines CSE 335: Software Design E. Kraemer Semantics of parallel composition Multiple interpretations: – Concurrent regions execute independently • What happens if transitions in different regions are triggered by same event? • Do both execute simultaneously? Does one “consume” the event to the exclusion of the other? Does each get a separate “copy” of the event? – Concurrent regions communicate with one another, synchronizing on common events • Regions can only proceed when all are ready to proceed • Regions transfer data values during a concurrent transition – Do we distinguish internal and external events? CSE 335: Software Design E. Kraemer UML 2.0 Interpretation: Asynchronous events run to completion Run-to-completion semantics: – State machine processes one event at a time and finishes all consequences of that event before processing another event – Events do not interact with one another during processing Event pool: – Where new events for an object are stored until object is ready to process them – No event ordering assumed in the pool CSE 335: Software Design E. Kraemer c1’s pool: c2’s pool: Client Client / send q.pullCall(this) Initializing / send q.pullCall(this) Waiting Initializing pullReturn(b,s) pullReturn(b,s) do / processString(b,s) q’s pool: Waiting do / processString(b,s) Queue ProcessingPullCall pullCall(caller) Idle Checking [!q.empty()] [q.empty()] / send caller.pullRet(false,empty) Empty / send caller.pullRet(true,s) CSE 335: Software Design NotEmpty do / s := q.back E. Kraemer c1’s pool: c2’s pool: Client Client / send q.pullCall(this) Initializing / send q.pullCall(this) Waiting Initializing pullReturn(b,s) pullReturn(b,s) do / processString(b,s) q’s pool: Waiting do / processString(b,s) Queue ProcessingPullCall pullCall(caller) Idle Checking [!q.empty()] [q.empty()] / send caller.pullRet(false,empty) Empty / send caller.pullRet(true,s) CSE 335: Software Design NotEmpty do / s := q.back E. Kraemer c1’s pool: c2’s pool: Client Client / send q.pullCall(this) Initializing / send q.pullCall(this) Waiting Initializing pullReturn(b,s) pullReturn(b,s) do / processString(b,s) q’s pool: Waiting do / processString(b,s) Queue ProcessingPullCall pullCall(caller) Idle Checking [!q.empty()] [q.empty()] / send caller.pullRet(false,empty) Empty / send caller.pullRet(true,s) CSE 335: Software Design NotEmpty do / s := q.back E. Kraemer c1’s pool: c2’s pool: Client Client / send q.pullCall(this) Initializing / send q.pullCall(this) Waiting Initializing pullReturn(b,s) pullReturn(b,s) do / processString(b,s) q’s pool: pullCall(c1) Waiting do / processString(b,s) Queue ProcessingPullCall pullCall(caller) Idle Checking [!q.empty()] [q.empty()] / send caller.pullRet(false,empty) Empty / send caller.pullRet(true,s) CSE 335: Software Design NotEmpty do / s := q.back E. Kraemer c1’s pool: c2’s pool: Client Client / send q.pullCall(this) Initializing / send q.pullCall(this) Waiting Initializing pullReturn(b,s) pullReturn(b,s) do / processString(b,s) q’s pool: Waiting do / processString(b,s) Queue ProcessingPullCall pullCall(caller) Idle Checking [!q.empty()] [q.empty()] / send caller.pullRet(false,empty) Empty / send caller.pullRet(true,s) CSE 335: Software Design NotEmpty do / s := q.back E. Kraemer c1’s pool: c2’s pool: Client Client / send q.pullCall(this) Initializing / send q.pullCall(this) Waiting Initializing pullReturn(b,s) pullReturn(b,s) do / processString(b,s) q’s pool: pullCall(c2) Waiting do / processString(b,s) Queue ProcessingPullCall pullCall(caller) Idle Checking [!q.empty()] [q.empty()] / send caller.pullRet(false,empty) Empty / send caller.pullRet(true,s) CSE 335: Software Design NotEmpty do / s := q.back E. Kraemer c1’s pool: c2’s pool: Client Client / send q.pullCall(this) Initializing / send q.pullCall(this) Waiting Initializing pullReturn(b,s) pullReturn(b,s) do / processString(b,s) q’s pool: pullCall(c2) Waiting do / processString(b,s) Queue ProcessingPullCall pullCall(caller) Idle Checking [!q.empty()] [q.empty()] / send caller.pullRet(false,empty) Empty / send caller.pullRet(true,s) CSE 335: Software Design NotEmpty do / s := q.back E. Kraemer c1’s pool: c2’s pool: pullRet(false, empty) Client Client / send q.pullCall(this) Initializing / send q.pullCall(this) Waiting Initializing pullReturn(b,s) pullReturn(b,s) do / processString(b,s) q’s pool: pullCall(c2) Waiting do / processString(b,s) Queue ProcessingPullCall pullCall(caller) Idle Checking [!q.empty()] [q.empty()] / send caller.pullRet(false,empty) Empty / send caller.pullRet(true,s) CSE 335: Software Design NotEmpty do / s := q.back E. Kraemer Observations Modeling method invocations as asynchronous events which are placed in a pool: – Requests for service on an object: • “buffered up” on arrival • dispatched when the object is ready to handle them – Natural interpretation for how an active object can be invoked – Makes passive objects appear to execute with “monitor semantics” CSE 335: Software Design E. Kraemer Observations (continued) In real programs, not every passive object is (or should be) a monitor: – There is some expense associated with acquiring more locks than are needed to synchronize threads – We often want to analyze a system to choose which passive objects should be monitors. How could we use state-machine models for this purpose? CSE 335: Software Design E. Kraemer More precisely… How could we model the shared queue as a state machine that admits the behaviors on the following slide? CSE 335: Software Design E. Kraemer Example c1 : … q :Queue c2 : … pull empty pull empty back pop back pop CSE 335: Software Design E. Kraemer Answer Duplicate an object’s state model with one copy for each system thread Note: This will need to be done for each passive object, and it will potentially cause the number of states in the system to grow out of control CSE 335: Software Design E. Kraemer Example: Shared queue pullCall(caller) Idle ProcessingPullCall pullCall(caller) [!q.empty()] Checking Idle [q.empty()] [!q.empty()] Checking [q.empty()] Empty / send caller.pullRet(…) ProcessingPullCall Empty / send caller.pullRet(…) NotEmpty do / s := q.back / send caller.pullRet(true,s) CSE 335: Software Design NotEmpty do / s := q.back / send caller.pullRet(true,s) E. Kraemer Initial state of shared queue pullCall(caller) Idle ProcessingPullCall pullCall(caller) [!q.empty()] Checking Idle [q.empty()] [!q.empty()] Checking [q.empty()] Empty / send caller.pullRet(…) ProcessingPullCall Empty / send caller.pullRet(…) NotEmpty do / s := q.back / send caller.pullRet(true,s) CSE 335: Software Design NotEmpty do / s := q.back / send caller.pullRet(true,s) E. Kraemer Client c1 invokes pull… pullCall(caller) Idle ProcessingPullCall pullCall(caller) [!q.empty()] Checking Idle [q.empty()] [!q.empty()] Checking [q.empty()] Empty / send caller.pullRet(…) ProcessingPullCall Empty / send caller.pullRet(…) NotEmpty do / s := q.back / send caller.pullRet(true,s) CSE 335: Software Design NotEmpty do / s := q.back / send caller.pullRet(true,s) E. Kraemer Queue is not empty… pullCall(caller) Idle ProcessingPullCall pullCall(caller) [!q.empty()] Checking Idle [q.empty()] [!q.empty()] Checking [q.empty()] Empty / send caller.pullRet(…) ProcessingPullCall Empty / send caller.pullRet(…) NotEmpty do / s := q.back / send caller.pullRet(true,s) CSE 335: Software Design NotEmpty do / s := q.back / send caller.pullRet(true,s) E. Kraemer At this point, context switch and client c2 invokes pull… pullCall(caller) Idle ProcessingPullCall pullCall(caller) [!q.empty()] Checking Idle [q.empty()] [!q.empty()] Checking [q.empty()] Empty / send caller.pullRet(…) ProcessingPullCall Empty / send caller.pullRet(…) NotEmpty do / s := q.back / send caller.pullRet(true,s) CSE 335: Software Design NotEmpty do / s := q.back / send caller.pullRet(true,s) E. Kraemer C1 has yet to pop queue; so c2’s check for empty fails pullCall(caller) Idle ProcessingPullCall pullCall(caller) [!q.empty()] Checking Idle [q.empty()] [!q.empty()] Checking [q.empty()] Empty / send caller.pullRet(…) ProcessingPullCall Empty / send caller.pullRet(…) NotEmpty do / s := q.back / send caller.pullRet(true,s) CSE 335: Software Design NotEmpty do / s := q.back / send caller.pullRet(true,s) E. Kraemer Bad state! If queue contained only one element… pullCall(caller) Idle ProcessingPullCall pullCall(caller) [!q.empty()] Checking Idle [q.empty()] [!q.empty()] Checking [q.empty()] Empty / send caller.pullRet(…) ProcessingPullCall Empty / send caller.pullRet(…) NotEmpty do / s := q.back / send caller.pullRet(true,s) CSE 335: Software Design NotEmpty do / s := q.back / send caller.pullRet(true,s) E. Kraemer Question Assuming this interpretation of passive objects, how would we model the promotion of shared queue to a monitor? CSE 335: Software Design E. Kraemer Question Assuming this interpretation of passive objects, how would we model the promotion of shared queue to a monitor? Answer: Two ways – Model the lock explicitly as another state machine – Use only a single state machine model for queue rather than replicating per thread CSE 335: Software Design E. Kraemer Model checking Technique for exhaustively and automatically analyzing finite-state models to find “bad states” – Bad states are specified in a temporal logic or some other declarative notation Lots of tools that can be used for this purpose: – FSP, SMV, Spin, etc If you are designing concurrent software, want to learn how to use these tools CSE 335: Software Design E. Kraemer Wrap-up: Use of models In this course, we have now used models for many purposes: – Documentation and demonstration of characteristics of a complex design – Means for understanding the requirements of a system to be built – Analysis for tricky concurrency properties CSE 335: Software Design E. Kraemer Question What does it mean to transition out of a concurrent composite state? CSE 335: Software Design E. Kraemer Question What does it mean to transition out of a concurrent composite state? Two possible answers: – transition awaits completion of all concurrent activities – transition acts immediately, terminating all concurrent activities CSE 335: Software Design E. Kraemer Example: Bridge game PlayingRubber ns-game Not Vulnerable Vulnerable ns-game N-S wins rubber ew-game Not Vulnerable Vulnerable ew-game E-W wins rubber Note: Transition out of PlayingRubber by one concurrent activity terminates the other CSE 335: Software Design E. Kraemer Example: Cash dispenser Emitting SetUp ready do/ dispenseCash Complete do/ ejectCard Note: Will not transition out of Emitting until after completion of all concurrent activities CSE 335: Software Design E. Kraemer Recall: State explosion problem Number of states in a system with multiple, orthogonal, behaviors grows exponentially (product of number of states of each feature) Major impediment to understanding: – Impossible to visualize in any meaningful way – Requires the use of analysis tools to verify properties Managing state explosion: – Concurrent state machines • Each object in a system modeled as a state machine • Object state machine runs concurrently with those of other objects – Concurrent composite states • Separates one machine can into orthogonal submachines CSE 335: Software Design E. Kraemer Example: Concurrent composite state Automobile Temperature control TempOn pushAir Cooling TempOff pushTCOff pushHeat Rear defroster pushHeat pushAir Heating Radio control pushRD RDOff pushRad RDOn pushRD CSE 335: Software Design RadOff RadOn pushRad E. Kraemer