Architecture-Based Runtime Software Evolution

Transcription

Appeared in the Proceedings of the International Conference on Software Engineering 1998 (ICSE'98). Kyoto, Japan, April 19-25, 1998.http://www.ics.uci.edu/ peymano/Architecture-Based Runtime Software EvolutionPeyman OreizyNenad MedvidovicRichard N. TaylorInformation and Computer ScienceUniversity of California, IrvineIrvine, CA 92697-3425 USA 1 714 824 8438{peymano, neno, taylor}@ics.uci.eduABSTRACTContinuous availability is a critical requirement for animportant class of software systems. For these systems,runtime system evolution can mitigate the costs and risksassociated with shutting down and restarting the system foran update. We present an architecture-based approach toruntime software evolution and highlight the role of softwareconnectors in supporting runtime change. An initialimplementation of a tool suite for supporting the runtimemodification of software architectures, called ArchStudio, ispresented.effectively utilizing mechanisms for runtime change. Changemanagement is a principal aspect of runtime systemevolution that: helps identify what must be changed, provides context for reasoning about, specifying, andimplementing change, and controls change to preserve system integrity.1 INTRODUCTIONAn important class of safety- and mission-critical softwaresystems, such as air traffic control, telephone switching, andhigh availability public information systems, shutting downand restarting the system for upgrades incurs unacceptabledelays, increased cost, and risk. Support for runtimemodification is a key aspect of these systems. Existingsoftware systems that require dynamic update generallyadopt ad-hoc, application-specific approaches. Such systemswould benefit from a systematic, principled approach toruntime change supported by a reusable infrastructure.Software architectures [26, 34] can provide a foundation forsystematic runtime software evolution. Architecture shiftsdeveloper focus away from lines-of-code to coarse-grainedcomponents and their overall interconnection structure. Thisenables designers to abstract away fine-grained details thatobscure understanding and focus on the “big picture:”system structure, the interactions between components, theassignment of components to processing elements, and,potentially, runtime change. A distinctive feature of softwarearchitectures is the explicit modeling of connectors.Connectors mediate and govern interactions amongcomponents, and thereby separate computation fromcommunication, minimize component interdependencies,and facilitate system understanding, analysis, and evolution.The benefits of runtime evolution are not restricted to safetyintensive, mission-critical systems. A growing class ofcommercial software applications exhibit similar propertiesin an effort to provide end-user customizability andextensibility. Runtime extension facilities have becomereadily available in popular operating systems (e.g., dynamiclink libraries in UNIX and Microsoft Windows) andcomponent object models (e.g., dynamic object bindingservices in CORBA [24] and COM [6]). These facilitiesenable system evolution without recompilation by allowingnew components to be located, loaded, and executed duringruntime.The facilities for runtime modification found in currentoperating systems, distributed object technologies, andprogramming languages, have a major shortcoming. They donot ensure the consistency, correctness, or desired propertiesof runtime change. Change management is critical toWithout change management, risks introduced by runtimemodifications may outweigh those associated with shuttingdown and restarting a system.This paper presents an architecture-based approach toruntime software evolution. Several unique elements of ourapproach are (a) an explicit architectural model, which isdeployed with the system and used as a basis for change,(b) preservation of explicit software connectors in the systemimplementation, and (c) an imperative language formodifying architectures. We also present our initialprototype of a tool suite that supports runtime softwareevolution at the architectural level.The paper is organized as follows. Section 2 describes keyaspects of effective change management. Section 3summarizes previous approaches to runtime softwarechange. Section 4 advocates a generic architecture-basedapproach to runtime change management and demonstrateshow different kinds of software evolution are supported atthe architectural level. Section 5 describes the rolecomponents and connectors play in supporting architecturalchange. Section 6 describes the particular architectural stylethat our tool suite, described in Section 7, supports.Section 8 identifies related research areas and Section 9summarizes the contributions of the paper.

2 MANAGING RUNTIME CHANGEThere are several critical aspects to change management.These determine the degree to which change can be reasonedabout, specified, implemented, and governed. Change application policy controls how a change isapplied to a running system. A policy, for example, mayinstantaneously replace old functionality with new functionality. Another policy may gradually introduce changeby binding invocations subsequent to the change to thenew functionality, while preserving bindings previouslyestablished to the old functionality. Ideally, change application policy decisions should be made by the designerbased on application requirements. Approaches that dictate a particular policy may force designers to “designaround” the restrictions to attain desired effects. Change scope is the extent to which different parts of asystem are affected by a change. A particular approach,for example, may stall the entire system during thecourse of a change. The designer’s ability to localize theeffects of runtime change by controlling its scope facilitates change management. The designer’s ability toascertain change scope helps reason about change. Separation of concerns captures the degree to whichissues concerning a system’s functional behavior are distinguished from those regarding runtime change. Thegreater the separation, the easier it becomes to alter onewithout adversely affecting the other. The level of abstraction at which changes are describedimpacts the complexity and quantity of information thatmust be effectively managed.We refer to these aspects in subsequent sections of the paperwhen comparing and contrasting different approaches toruntime change.We also distinguish between two types of change:(1) changes to system requirements, and (2) changes tosystem implementation that do not alter requirements.When the requirements change, it is the responsibility of thedesigner to determine what to change, how to change it, andwhether or not the change preserves application integrity.Once a change has been designed, implemented, and tested,it is executed on the running system. It is unrealistic toassume that any preconceived measures for maintainingsystem integrity would support this type of unpredictableand unrestricted change.system, for example, relinquishes computer control to aperson during system maintenance. If around-the-clocksystem availability is not required, system updates arepostponed until the next scheduled downtime. Somedistributed systems employ functional redundancy orclustering as a mechanism to circumvent the need forruntime change. Web servers, for example, are upgraded byredirecting incoming network traffic to a redundant host,reconfiguring the original host in a traditional manner, andredirecting network traffic back to the original host.However, these approaches are not feasible or desirable in allcases due to the increased risk and costs they impose. Ourgoal is to reduce the costs and risks designers typicallyassociate with runtime change, making it a more attractivedesign alternative.When changes are confined to the implementation, apreconceived set of application invariants may serve as abasis for preserving system integrity. Designers can specifythese invariants as a part of the deployed system and preventchanges that violate these invariants.Peterson et al. [27] present an approach to module-levelruntime change based on Haskel, a higher-order, typedprogramming language. Their technique requiresprogrammers to anticipate portions of the program likely tochange during runtime, and structure the program aroundfunctions that encapsulate such changes. Developers encodedecisions regarding change application policy and changescope in the application source code. This technique permitsfine-grained control over runtime change since designers canimplement change policies tailored to the application.However, because change policies are not isolated in theapplication source code, they can be difficult to alterindependent of application behavior. As a result, managingchange in large systems becomes complex.The inherent difficulty of predicting likely changes duringthe initial software design phase necessitates that anapproach to runtime software evolution support both types ofchange.3 PREVIOUS APPROACHES TO RUNTIMECHANGETraditionally, designers have sought alternatives to runtimechange altogether. A manual override in a safety criticalSeveral approaches to runtime software evolution have beenproposed in the literature [13, 15, 18, 27, 32]. In thefollowing paragraphs, we describe some representativeapproaches and evaluate them with respect to the aspects ofchange management presented in Section 2. We start bydiscussing techniques for statement- and procedure-levelruntime change and move up levels of abstraction.Gupta et al. [15] describe an approach to modeling changesat the statement- and procedure-level for a simple theoreticalimperative programming language. The technique is basedon locating the program control points at which all variablesaffected by a change are guaranteed to be redefined beforeuse. They show that in the general case locating all suchcontrol points is undecidable, and approximate techniquesbased on source code data-flow analysis and developerknowledge are required. Scaling up this approach to managechange in large systems written in complex programminglanguages is still an open research problem. Dynamicprogramming languages, such as Lisp and Smalltalk, supportstatement- and procedure- level runtime change. Thisflexibility is gained at the expense of heterogeneity andperformance. Applications must be written entirely in thedynamic language to benefit from dynamism. This incursperformance overhead because every function invocationmust be bound during runtime. Furthermore, applicationbehavior and dynamism are not explicitly separated orlocalized. As a result, concerns regarding dynamic changepermeate system design, making change managementexceedingly difficult.

Gorlick et al. [13, 14] present a data flow based approach toruntime change called Weaves. A weave is an arbitrarynetwork of tool fragments connected together by transportservices. Tool fragments communicate asynchronously bypassing object references (i.e., pointers). A tool fragment is asmall software component, on the order of a procedure, thatperforms a single, well-defined function and may retainstate. Each tool fragment executes in its own thread ofcontrol. Transport services buffer and synchronize datacommunication between tool fragments. The Weave runtimesystem guarantees the atomicity of data transfer between toolfragments and queues; if any problem occurs duringcommunication, the tool fragment initiating thecommunication is notified and may retry the operation at itsdiscretion. This enables the runtime reconfiguration of aweave without disturbing the flow of objects. Designers usean interactive, graphical editor to visualize and directlyreconfigure a weave during runtime. Weaves does notcurrently provide a mechanism to check the consistency ofruntime changes and no explicit support is provided forrepresenting change policies. The designer is solelyresponsible for change management.Kramer and Magee [18] present a structural-based approachto runtime change of a distributed system’s configuration. Intheir approach, a configuration consists of processing nodesinterconnected using bidirectional communication links.When a runtime change is required, a reconfigurationmanager orders processing nodes directly affected by thechange and nodes directly adjacent to them to enter into a“quiescent” state. While in the quiescent state, a node isexpected not to initiate communication with peers. Thisensures that nodes directly affected by a change will notreceive service requests during the course of the change.Changes, specified in a declarative language, are induced tothe running system by the reconfiguration manager. Thereconfiguration manager is responsible for making decisionsregarding the change application policy and its scope. Itmust do so based on a limited model of the applicationconsisting of the system’s structural configuration andwhether or not its nodes are in quiescent states. As a result,designers must consider the reconfiguration manager’s rolein runtime change, and structure the system to attain desiredeffects.4 RUNTIME ARCHITECTURAL CHANGEWe advocate an approach that operates at the architecturallevel. Four immediate benefits result from managing changeat the architectural level. First, software engineers use asystem’s architecture as a tool to describe, understand, andreason about overall system behavior [26, 34]. Leveragingthe engineer’s knowledge at this level of system design holdspromise in helping manage runtime change. Second, if norestrictions are placed on component internals it becomesfeasible to accommodate off-the-shelf (OTS) components.Third, decisions regarding change application policy andscope are naturally encapsulated within connectors andseparated from application-specific behavior. This facilitatesthe task of changing policies independent of functionalbehavior. Fourth, control over change application policy andscope is placed in the hands of the architect, where decisionscan be made based on an understanding of applicationrequirements and semantics. Previous approaches to runtimechange either dictate a single policy that all systems mustadopt or fail to separate application-specific functionalityfrom runtime change considerations. As a result, concernsover runtime change permeate system design.In the following subsections, we demonstrate howarchitectures can support different types of softwareevolution, and the circumstances under which changes maybe performed. We refer to three characteristic types ofevolution: corrective, perfective, and adaptive [12].Corrective evolution removes software faults. Perfectiveevolution enhances product functionality to meet changinguser needs. Adaptive evolution changes the software to runin a new environment.4.1 Runtime Component AdditionComponent addition supports perfective evolution byaugmenting system functionality. Some design styles aremore readily amenable to component addition than others.For example, the observer design pattern [9] separates dataproviders from its observers, facilitating the addition of newobservers with minimal impact on the rest of the system. Inthe mediator design approach [35], new mediators may beintroduced to maintain relationships between independentcomponents. Design approaches that utilize implicitinvocation mechanisms [11] are generally more amenable toruntime component addition since the invoking componentis unaware of the number of components actually invoked.In order for a component to function properly when added toa running system, it must not assume that the system is in itsinitial state. Typically, a component added during runtimemust discover the state of the system and perform necessaryactions to synchronize its internal state with that of thesystem.Architectural change specifications typically specifystructural changes necessary to incorporate new components.In some cases, the structural configuration changes may beimplicit to the architectural style or application-domain, orderivable from externally visible properties of thecomponent. For example, Adobe Photoshop plug-incomponents export a “plug-in type” property, whose value isselected from a fixed list [1]. Photoshop uses these values todetermine how to interact with the plug-in.4.2 Runtime Component RemovalComponent removal enables a designer to remove unneededbehavior, potentially as a result of recent additionssupplanting original behavior. Appropriate conditionsgoverning component removal are application-specific. Forexample, a system’s runtime environment may prohibitcomponent removal if any of its functions are on theexecution stack. Some systems, especially distributedsystems communicating over inherently undependableconnections, are specifically designed to tolerate sudden lossof functionality or state. As with component addition, certaindesign approaches and styles are more amenable to runtimeremoval than others.

4.3 Runtime Component ReplacementWe consider component replacement as a special case ofaddition followed by removal when two additionalproperties are required: (1) the state of the executingcomponent must be transferred to the new component, and(2) both components must not be simultaneously activeduring the change. Corrective and adaptive evolution arecharacteristic of such changes.Component replacement is simple when components lackstate or belong to systems specifically designed to toleratestate loss. Such systems typically detect state loss and switchto a degraded mode of operation while recovering. Anotherapproach, exemplified by the Simplex architectural style[32], incorporates an “operational model” in theimplementation. The model rejects upgraded componentswhen they do not satisfy explicit performance and accuracyrequirements.In systems not specifically designed to tolerate state loss,component replacement requires additional considerationsbeyond those discussed for component addition andremoval. Several approaches for preserving component stateand preventing communication loss during runtime changehave been proposed [5, 8, 17]. Hofmeister’s approach [17]requires each component to provide two interface methods:one for divulging state information, and the other forperforming initialization when replacing another component.These approaches are applicable only when the newcomponent’s externally visible interface is a strict superset ofthe component being replaced. Approaches not restricted insuch a manner are an open research topic.4.4 Runtime ReconfigurationStructural reconfiguration of the architecture supportsrecombining existing functionality to modify overall systembehavior. Data-flow architectures, such as UNIX’s pipe andfilter style and Weaves [13], provide substantial flexibilitythrough static reconfiguration of existing behaviors. Forexample, UNIX’s pipe-and-filter style enables constructionof a rich set of behaviors through the recombination ofexisting behavior.Runtime reconfiguration can be performed by alteringconnector bindings since connectors mediate all componentcommunication. As with component replacement, ifcomponents assume reliable communication, it is necessaryto prevent communication loss.4.5 SummaryIt is important to note that with any type of architecturalchange, concerns regarding the mechanics of change must beseparated from the semantic effects of change on theparticular application. The injudicious application ofarchitectural changes can compromise system integrity. As aresult, such changes must be verified before being applied toa running system. The use of architectural modeling andanalysis tools is crucial in this regard.5 ENABLING RUNTIME ARCHITECTURAL CHANGEThis section outlines the roles components and connectorsshould play in supporting the architectural changes describedin the previous section. The following subsections describethe specific roles components and connectors must fulfill tosupport runtime change.5.1 ComponentsComponents are responsible for implementing applicationbehavior. We treat their internal structure as a black box. Acomponent encapsulates functionality of arbitrarycomplexity, maintains internal state information, potentiallyutilizes multiple threads of control, and may be implementedin any programming language. Treating components as blackboxes significantly increases the opportunity for reusingOTS components. However, OTS component may not beable to participate in runtime change if it lacks certainfunctionality. For example, the inability to extractcomponent state from a component prevents componentreplacement. We cannot circumvent these problems withoutmodifying the component.Components should not communicate by directlyreferencing one another. Instead, they should utilize aconnector, which localizes and encapsulates componentinterfacing decisions. This minimizes coupling betweencomponents, enabling binding decisions to change withoutrequiring component modification [29].Each component must provide a minimal amount offunctional behavior to participate in runtime change. Tosupport runtime addition and removal, components must bepackaged in a form that the underlying runtime environmentcan dynamically load. Most popular operating systemsprovide a dynamic linking capability. Dynamic linkingprovides a language-independent mechanism for loadingnew modules during runtime and invoking the services theyexport. Higher level mechanisms, such as CORBA [24] andCOM [6], provide similar functionality. To support runtimereconfiguration, components must be able to alter theirconnector bindings. These additional behaviors can typicallybe provided in the form of reusable code libraries which actas a wrapper or proxy to the actual component (seeSection 7). This alleviates the burden of implementing suchfunctionality for every component.5.2 ConnectorsConnectors are explicit architectural entities that bindcomponents together and act as mediators betweenthem [34]. In this way, connectors separate a nalrequirements [29]. Connectors encapsulate componentinteractions and localize decisions regarding communicationpolicy and mechanism. As a result, connectors have beenused for a wide variety of purposes, including: ensuring aparticular interaction protocol between components [3];specifying communication mechanism independent offunctional behavior, thereby enabling components written indifferent programming languages and executing on differentprocessors to transparently interoperate [29]; visualizing anddebugging system behavior by monitoring messagesbetween components [28]; and integrating tools by using aconnector to broadcast messages between them [30].Although connectors are explicit entities during design, they

have traditionally been implemented as indiscrete entities inthe implementation. In UniCon, for example, procedure calland data access connectors are reified as linker instructionsduring system generation [33]. Similarly, component bindingdecisions, while malleable during design, are typically fixedduring system generation. As a result, modifying bindingdecisions during runtime becomes difficult.Connectors, like components, must remain discrete entitiesin the implementation to support their runtime addition andremoval. They must also provide a mechanism for addingand modifying component bindings in order to supportreconfiguration.1 Supporting runtime rebinding can degradeperformance in primitive connectors, such as procedurecalls, since an additional level of indirection is introduced.For more complex connectors, such as RPC and softwarebuses (e.g. Field [30]), the functionality we require canusually be integrated without a significant runtimeperformance penalty. Recent approaches to dynamic linkingattempt to reduce or eliminate the runtime overheadassociated with altering binding decisions duringruntime [7]. Ultimately, designers should determine whichconnectors are used based on application requirements. Ifruntime change is not required, connectors without rebindingoverhead may be used.Connectors play a central role in supporting several aspectsof change management. They can implement differentchange policies by altering the conditions under whichnewly added components are invoked. For example, tosupport immediate component replacement, a connector candirect all communication after a certain point in time awayfrom the old component to the new one. To support a moregradual component replacement policy, a connector candirect new service requests to the new component, whiledirecting previously established requests to the originalcomponent. To support a policy based on replication, servicerequests can be directed to any member of a known set offunctionally redundant components. Connectors can also beused as a means of localizing change. For example, if acomponent becomes unavailable during the course of aruntime change, the connectors mediating its communicationcan queue service requests until the component becomesavailable. As a result, other components are insulated fromthe change. Using connectors to encapsulate changeapplication policy and scope decisions lets designers selectthe most appropriate policy based on applicationrequirements.6 APPLYING CONCEPTS TO A SPECIFIC ARCHITECTURAL STYLEWe are developing general techniques for runtimearchitecture evolution that are applicable across applicationdomains, architectural styles, and architecture modelingnotations. We are also investigating a general formal basis1. Runtime rebinding can be supported without explicit connectorsby essentially replacing relevant machine language instructionsduring runtime. This technique is highly dependent on the execution environment (memory protection, restrictions on self-modifying code, etc.) and the programming language and compileroptimizations (polymorphic functions, function inlining, etc.).for architectural dynamism. However, the field of softwarearchitectures is still relatively young and largely unexplored.This is particularly true of dynamism: we can learn fromtraditional approaches to dynamism, outlined in Section 3,but some of the issues they raise will be inapplicable toarchitectures; additionally, architectures are likely tointroduce other, unique problems, such as supportingheterogeneity, adhering to architectural styles, andmaintaining compatibility with OTS components.For these reasons, our initial strategy has been to addressconcrete problems and learn from experience. We havefocused on supporting architectures in a layered, event-basedarchitectural style, called C2 [36]. In the C2-style, allcommunication among components occurs via connectors,thus minimizing component interdependencies and strictlyseparating computation from communication. The style alsoimposes topological constraints: every component has a“top” and a “bottom” side, with a single communication porton each side. This restriction greatly simplifies the task ofadding, removing, or reconnecting a component. A C2connector also has a top and a bottom, but the number ofcommunication ports is determined by the componentsattached to it: a connector can accommodate any number ofcomponents or other connectors. This enables C2 connectorsto accommodate runtime rebinding. Finally, allcommunication among components is done asynchronouslyby exchanging messages through connectors.Although the C2-style places several restrictions onarchitectures and architectural building blocks, we believethese restrictions to be permissive enough to allow us tomodel a broad class of applications. Narrowing our focus hasenabled us to construct tools for supporting runtimearchitectural change. As a result, we’ve gained directpractical experience with runtime evolution of architecturesand uncovered important issues in effectively supportingthem.7 TOOLS SUPPORTING ARCHITECTURE-BASEDEVOLUTION OF SOFTWARE SYSTEMSThis section describes ArchStudio, our tool suite thatimplements our architecture-based approach to runtimesoftware evolution. The following subsections describe ourgeneral approach to enabling evolution of software systemsat the architectural level. We then present an implementationbased on this approach and demonstrate its use on a simpleapplication. We conclude by discussing the currentlimitations of our implementation.7.1 ApproachOur general approach to supporting architecture-basedsoftware evolution consists of several interrelatedmechanisms (see Figure 1). The mechanisms are describedbelow. Section 7.2 describes our implementation of thesemechanisms.Explicit Architectural Model. In order to effectivelymodify a system, an accurate model of its architecture mustbe available during runtime. To achieve this, a subset of thesystem’s architecture is deployed as an integral part of thesystem. The deployed architectural model describes the

interconnections between components and connectors, andtheir mappings to implementation modules. The mappingenables changes specified in terms of the architectural modelto effect corresponding changes in the implementation. Theruntimearchitecture infrastructuremaintainsthecorrespondence between the model and the implementation.Describing Runtime Change. Modifications are expressedin terms of the architectural model. A modificationdescription uses operations for adding and removingcomponents and connectors, replacing components andconnectors, and changing the architectural topology.This approach supports a flexible model of system evolutionin which modifications are provided by multipleorganizations (e.g., the application vendor, systemintegrators, site managers) and selectively applied by endusers based on their particular needs. By applying differentsets of modifications, an end-user can effectively create adifferent member of the system family at her site. As a result,the modifications should be robust to variations in thosesystems. Facilities for querying the architectural model andusing the resu

programming languages, have a major shortcoming. They do not ensure the consistency, correctness, or desired properties of runtime change. Change management is critical to effectively utilizing mechanisms for runtime change. Change management is a principal aspect of runtime system evolution that: helps identify what must be changed,