E. KraemerCSE 335: Software Design
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
E. KraemerCSE 335: Software Design
Outline of course topicsFoundational 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
E. KraemerCSE 335: Software Design
Analytical models of behaviorThus 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
E. KraemerCSE 335: Software Design
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
E. KraemerCSE 335: Software Design
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
anotherUML provides send actions by which one machine
may signal another
E. KraemerCSE 335: Software Design
Simple example: Client using a queue
q : Queuec1 : Client
System comprises two objectsEach object modeled by a state machineSystem state at any time is the pair (state of c1, state of q)
E. KraemerCSE 335: Software Design
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
E. KraemerCSE 335: Software Design
Example
Waiting... / send S.mCall(this, ...) mRet(...)
ProcessingBody of M
... / send caller.mRet(...)mCall(caller, ...)
Client
Supplier
E. KraemerCSE 335: Software Design
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>
E. KraemerCSE 335: Software Design
Example: Client model
do / processString(b,s)
/ send q.pullCall(this) WaitingInitializing
pullReturn(b,s)
Client
E. KraemerCSE 335: Software Design
Example: Shared queue
Idle
ProcessingPullCall
Checking
/ send caller.pullRet(false,empty)
/ send caller.pullRet(true,s)
pullCall(caller)
Empty
NotEmpty do / s := q.back
[q.empty()]
[!q.empty()]
Queue
E. KraemerCSE 335: Software Design
Recall: Two active clients sharing a queue
q : Queue
c1 : Client c2 : Client
q q
E. KraemerCSE 335: Software Design
Question
Do our state diagrams for classes Client and Queue accurately model the behaviors of active clients acting on a shared queue?
E. KraemerCSE 335: Software Design
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
E. KraemerCSE 335: Software Design
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?
E. KraemerCSE 335: Software Design
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
E. KraemerCSE 335: Software Design
Idle
ProcessingPullCall
Checking
/ send caller.pullRet(false,empty)
/ send caller.pullRet(true,s)
pullCall(caller)
Empty
NotEmpty do / s := q.back
[q.empty()]
[!q.empty()]
do / processString(b,s)
/ send q.pullCall(this)
WaitingInitializing
pullReturn(b,s)
Client
do / processString(b,s)
/ send q.pullCall(this)
WaitingInitializing
pullReturn(b,s)
Client
c1’s pool: c2’s pool:
q’s pool:Queue
E. KraemerCSE 335: Software Design
Idle
ProcessingPullCall
Checking
/ send caller.pullRet(false,empty)
/ send caller.pullRet(true,s)
pullCall(caller)
Empty
NotEmpty do / s := q.back
[q.empty()]
[!q.empty()]
do / processString(b,s)
/ send q.pullCall(this)
WaitingInitializing
pullReturn(b,s)
Client
do / processString(b,s)
/ send q.pullCall(this)
WaitingInitializing
pullReturn(b,s)
Client
c1’s pool: c2’s pool:
q’s pool:Queue
E. KraemerCSE 335: Software Design
Idle
ProcessingPullCall
Checking
/ send caller.pullRet(false,empty)
/ send caller.pullRet(true,s)
pullCall(caller)
Empty
NotEmpty do / s := q.back
[q.empty()]
[!q.empty()]
do / processString(b,s)
/ send q.pullCall(this)
WaitingInitializing
pullReturn(b,s)
Client
do / processString(b,s)
/ send q.pullCall(this)
WaitingInitializing
pullReturn(b,s)
Client
c1’s pool: c2’s pool:
q’s pool:Queue
E. KraemerCSE 335: Software Design
Idle
ProcessingPullCall
Checking
/ send caller.pullRet(false,empty)
/ send caller.pullRet(true,s)
pullCall(caller)
Empty
NotEmpty do / s := q.back
[q.empty()]
[!q.empty()]
do / processString(b,s)
/ send q.pullCall(this)
WaitingInitializing
pullReturn(b,s)
Client
do / processString(b,s)
/ send q.pullCall(this)
WaitingInitializing
pullReturn(b,s)
Client
q’s pool: pullCall(c1)
c1’s pool: c2’s pool:
Queue
E. KraemerCSE 335: Software Design
Idle
ProcessingPullCall
Checking
/ send caller.pullRet(false,empty)
/ send caller.pullRet(true,s)
pullCall(caller)
Empty
NotEmpty do / s := q.back
[q.empty()]
[!q.empty()]
do / processString(b,s)
/ send q.pullCall(this)
WaitingInitializing
pullReturn(b,s)
Client
do / processString(b,s)
/ send q.pullCall(this)
WaitingInitializing
pullReturn(b,s)
Client
q’s pool:
c2’s pool:c1’s pool:
Queue
E. KraemerCSE 335: Software Design
Idle
ProcessingPullCall
Checking
/ send caller.pullRet(false,empty)
/ send caller.pullRet(true,s)
pullCall(caller)
Empty
NotEmpty do / s := q.back
[q.empty()]
[!q.empty()]
do / processString(b,s)
/ send q.pullCall(this)
WaitingInitializing
pullReturn(b,s)
Client
do / processString(b,s)
/ send q.pullCall(this)
WaitingInitializing
pullReturn(b,s)
Client
q’s pool: pullCall(c2)
c2’s pool:c1’s pool:
Queue
E. KraemerCSE 335: Software Design
Idle
ProcessingPullCall
Checking
/ send caller.pullRet(false,empty)
/ send caller.pullRet(true,s)
pullCall(caller)
Empty
NotEmpty do / s := q.back
[q.empty()]
[!q.empty()]
do / processString(b,s)
/ send q.pullCall(this)
WaitingInitializing
pullReturn(b,s)
Client
do / processString(b,s)
/ send q.pullCall(this)
WaitingInitializing
pullReturn(b,s)
Client
q’s pool: pullCall(c2)
c2’s pool:c1’s pool:
Queue
E. KraemerCSE 335: Software Design
Idle
ProcessingPullCall
Checking
/ send caller.pullRet(false,empty)
/ send caller.pullRet(true,s)
pullCall(caller)
Empty
NotEmpty do / s := q.back
[q.empty()]
[!q.empty()]
do / processString(b,s)
/ send q.pullCall(this)
WaitingInitializing
pullReturn(b,s)
Client
do / processString(b,s)
/ send q.pullCall(this)
WaitingInitializing
pullReturn(b,s)
Client
c1’s pool: pullRet(false, empty)
q’s pool: pullCall(c2)
c2’s pool:
Queue
E. KraemerCSE 335: Software Design
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”
E. KraemerCSE 335: Software Design
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?
E. KraemerCSE 335: Software Design
More precisely…
How could we model the shared queue as a state machine that admits the behaviors on the following slide?
E. KraemerCSE 335: Software Design
Example
q :Queue c2 : …c1 : …
pull
empty
back
pop
pull
empty
back
pop
E. KraemerCSE 335: Software Design
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
E. KraemerCSE 335: Software Design
Example: Shared queue
Idle
ProcessingPullCall
Checking
/ send caller.pullRet(…)
/ send caller.pullRet(true,s)
pullCall(caller)
Empty
NotEmptydo / s := q.back
[q.empty()]
[!q.empty()]
Idle
ProcessingPullCall
Checking
/ send caller.pullRet(…)
/ send caller.pullRet(true,s)
pullCall(caller)
Empty
NotEmptydo / s := q.back
[q.empty()]
[!q.empty()]
E. KraemerCSE 335: Software Design
Initial state of shared queue
Idle
ProcessingPullCall
Checking
/ send caller.pullRet(…)
/ send caller.pullRet(true,s)
pullCall(caller)
Empty
NotEmptydo / s := q.back
[q.empty()]
[!q.empty()]
Idle
ProcessingPullCall
Checking
/ send caller.pullRet(…)
/ send caller.pullRet(true,s)
pullCall(caller)
Empty
NotEmptydo / s := q.back
[q.empty()]
[!q.empty()]
E. KraemerCSE 335: Software Design
Client c1 invokes pull…
Idle
ProcessingPullCall
Checking
/ send caller.pullRet(…)
/ send caller.pullRet(true,s)
pullCall(caller)
Empty
NotEmptydo / s := q.back
[q.empty()]
[!q.empty()]
Idle
ProcessingPullCall
Checking
/ send caller.pullRet(…)
/ send caller.pullRet(true,s)
pullCall(caller)
Empty
NotEmptydo / s := q.back
[q.empty()]
[!q.empty()]
E. KraemerCSE 335: Software Design
Queue is not empty…
Idle
ProcessingPullCall
Checking
/ send caller.pullRet(…)
/ send caller.pullRet(true,s)
pullCall(caller)
Empty
NotEmptydo / s := q.back
[q.empty()]
[!q.empty()]
Idle
ProcessingPullCall
Checking
/ send caller.pullRet(…)
/ send caller.pullRet(true,s)
pullCall(caller)
Empty
NotEmptydo / s := q.back
[q.empty()]
[!q.empty()]
E. KraemerCSE 335: Software Design
At this point, context switch and client c2 invokes pull…
Idle
ProcessingPullCall
Checking
/ send caller.pullRet(…)
/ send caller.pullRet(true,s)
pullCall(caller)
Empty
NotEmptydo / s := q.back
[q.empty()]
[!q.empty()]
Idle
ProcessingPullCall
Checking
/ send caller.pullRet(…)
/ send caller.pullRet(true,s)
pullCall(caller)
Empty
NotEmptydo / s := q.back
[q.empty()]
[!q.empty()]
E. KraemerCSE 335: Software Design
C1 has yet to pop queue; so c2’s check for empty fails
Idle
ProcessingPullCall
Checking
/ send caller.pullRet(…)
/ send caller.pullRet(true,s)
pullCall(caller)
Empty
NotEmptydo / s := q.back
[q.empty()]
[!q.empty()]
Idle
ProcessingPullCall
Checking
/ send caller.pullRet(…)
/ send caller.pullRet(true,s)
pullCall(caller)
Empty
NotEmptydo / s := q.back
[q.empty()]
[!q.empty()]
E. KraemerCSE 335: Software Design
Bad state! If queue contained only one element…
Idle
ProcessingPullCall
Checking
/ send caller.pullRet(…)
/ send caller.pullRet(true,s)
pullCall(caller)
Empty
NotEmptydo / s := q.back
[q.empty()]
[!q.empty()]
Idle
ProcessingPullCall
Checking
/ send caller.pullRet(…)
/ send caller.pullRet(true,s)
pullCall(caller)
Empty
NotEmptydo / s := q.back
[q.empty()]
[!q.empty()]
E. KraemerCSE 335: Software Design
Question
Assuming this interpretation of passive objects, how would we model the promotion of shared queue to a monitor?
E. KraemerCSE 335: Software Design
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
E. KraemerCSE 335: Software Design
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 notationLots of tools that can be used for this purpose:
– FSP, SMV, Spin, etcIf you are designing concurrent software, want
to learn how to use these tools
E. KraemerCSE 335: Software Design
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
E. KraemerCSE 335: Software Design
Question
What does it mean to transition out of a concurrent composite state?
E. KraemerCSE 335: Software Design
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
E. KraemerCSE 335: Software Design
Example: Bridge game
E-W winsrubber
N-S winsrubberVulnerable
VulnerableNotVulnerable
NotVulnerable
ns-game ns-game
ew-game ew-game
PlayingRubber
Note: Transition out of PlayingRubber by one concurrent activity terminates the other
E. KraemerCSE 335: Software Design
Example: Cash dispenser
SetUp Completedo/ dispenseCash
do/ ejectCard
ready
Emitting
Note: Will not transition out of Emitting until after completion of all concurrent activities
E. KraemerCSE 335: Software Design
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
E. KraemerCSE 335: Software Design
Example: Concurrent composite state
TempOff
Automobile
Cooling
Heating
pushHeat pushAir
TempOn
pushHeat
pushAir
pushTCOff
Temperature control
Rear defroster
RDOff RDOnpushRD
pushRDRadOff RadOn
pushRad
pushRad
Radio control