• Nenhum resultado encontrado

Part III: Robusta

6.2 Decoupling component instances

6.2.2 Free and Managed objects

Figure 32 shows how object references from consumer and service component instances can point to the same object in memory. When two components point to the same object, independently of the object s class, the object is considered a shared object. Any parameter sent from a consumer component to a provider component can become a shared object. Any return value sent from provider component to consumer component can become a shared objec t.

Furthermore, a shared object can point to other objects, leading to an object graph being shared.

Figure 32: Invoking a service causes parameters to be passed to the service provider and return values to be sent to the service consumer. Object references from either component can then point to the same object, which is considered shared.

Shared objects, often enough, represent a resource used in common by client and provider components (e.g., a port, a configuration file, a thread, a database connection object), or store shared state (e.g., a session). If, for example, the provider component disappears, the shared object is possibly still valid, in a technical sense, but may not make sense for the new provider component (e.g., the new provider uses a different port or different configuration file). Using the old object with the new component may foul and crash it.

The question we are trying to resolve is: What happens to shared objects if the component

6.2 Decoupling component instances

shows that after a service invocation, there may be shared objects referenced by both components.

When the component that created the object is invalidated, removed or destroyed (in this case this is the service provider component, it is unclear if the shared objects remain in a valid state or not.

Figure 33: Shared objects referenced from two components require a mechanism to establish if the objects may continue to be used even after the component that has created them is removed.

Our approach is to augment the Service Contract concept with metadata to specify the retention policy for objects passed between components in a service interaction. We annotate types in the Service Interface (the initial point of contact between two components) in order to indicate if an object is Managed or Free. This adds a semantic dimension to the otherwise purely syntactic nature of the service interface. Annotations on parameters used in a method indicate the retention policy on objects created by the consumer and sent to the provider component. Annotations on return values are to indicate the retention policy used on objects sent from the provider component to the consumer63,64. This idea is very similar to ownership types [BOYAPATI et al. 2003].

Figure 34: shows two shared objects with different retention policies. Free objects are independent of the creating component’s lifecycle while managed objects become invalid if the creating component is invalid.

63 Our mode l suppose s that parameter objects are created by se rvice consumer components and re turn value objects are created by service provider components. The te rm create is used sparingly; howe ve r, e ve n if the compone nt did not cre ate the obje ct it is re sponsible for passing it in e ithe r a parame te r or re turn value to the othe r compone nt.

64 We do not support in-out or out parame te rs available in language s such as C++ or Ada. Eve n programming language s like Java can subvert the lack of in-out variable s by using container or wrappe r objects, with value s se t by the re ce iving compone nts. The se practice s should be avoide d be cause it bre aks the dire ctional nature of the se rvice .

Figure 34 shows that components can simultaneously reference Free Objects, which do not have retention conditions, and Managed Objects, which components must pay particular attention to releasing when the component that created the object is invalidated.

Handling Managed objects requires releasing the object when the component that provided it in the service interaction becomes invalid. Figure 35 shows an example of a component that is removed. References from the consumer component to the managed object must be released, allowing the managed object to be garbage collected. If the reference is not released, this results in a dangling reference, potentially causing memory leaks, unexpected behavior, or failure. Note that the framework can force the release of managed objects by destroying the component (causing the garbage collector to eventually release the objects); however, it is not possible to easily detect such cases without extensive runtime instrumentation.

Figure 35: An invalid component can cause Managed Objects that to become invalid, leaving components that continue to reference these objects in a potentially corrupt state because of dangling references.

When programming a component that has multiple dependencies to the same service type, handling managed objects can become complicated especially when the component is bound to multiple components simultaneously because it would require tracking Managed objects along with the service dependency. Using dependency injection mechanisms complicates this issue even more given that dependency injection is often transparent to the component s code. Indeed, using method callbacks for binding components is easier because the service dependency reference can be stored in a container object (e.g., a map) with a list of managed objects coming from that particular service. A map of service dependency objects that points to a map of managed per- service objects, or a new wrapper object created by the component to hold both the service dependency and the managed objects, are probably the easiest way to handle this case.

Finally, unlike Managed return value objects, Managed parameters are particularly difficult

6.2 Decoupling component instances

bound the provider) and does not generally distinguish between one consumer and another65. An identification mechanism is needed to distinguish between components and then to notify the component to release all managed objects held using the given identification. In order to avoid polluting the method call with parameters used for identification purposes, our approach uses thread local variables to pass the consumer component s internal identification66 to the provider, which can then use it to construct its managed object reference map. When a consumer component is invalidated or removed, the provider component s callback method is invoked using the consumer components id. The id can be checked and managed objects released.