Activities: The COM+ Innovation

The task for COM+ was not only to solve the classic OOP problems but also to address the classic COM concurrency model deficiencies and maintain backward compatibility. Imagine a client calling a method on a component. The component can be in the same context as the client, in another apartment or a process on the same machine, or in a process on another machine. The called component may in turn call other components, and so on, creating a string of nested calls. Even though you cannot point to a single thread that carries out the calls, the components involved do share a logical thread of execution.

Despite the fact that the logical thread can span multiple threads, processes, and machines, there is only one root client. There is also only one thread at a time executing in the logical thread, but not necessarily the same physical thread at all times.

The idea behind the COM+ concurrency model is simple, but powerful: instead of achieving synchronization through physical thread affinity, COM+ achieves synchronization through logical thread affinity. Because in a logical thread there is just one physical thread executing in any given point in time, logical thread affinity implies physical threads synchronization as well. If a component is guaranteed not to be accessed by multiple logical threads at the same time, then synchronization to that component is guaranteed. Note that there is no need to guarantee that a component is always accessed by the same logical thread. All COM+ provides is a guarantee that the component is not accessed by more than one logical thread at a time.

A logical thread is also called a causality, a name that emphasizes the fact that all of the nested calls triggered by the root client share the same cause—the root client’s request on the topmost object. Due to the fact that most of the COM+ documentation refers to a logical thread as causality, the rest of this chapter uses causality too. COM+ tags each causality with its own unique ID—a GUID called the causality ID .

To prevent concurrent access to an object by multiple causalities, COM+ must associate the object with some sort of a lock, called a causality lock . However, should COM+ assign a causality lock per object? Doing so may be a waste of resources and processing time, if by design the components are all meant to participate in the same activity on behalf of a client. As a result, it is up to the component developer to decide how the object is associated with causality-based locks: whether the object needs a lock at all, whether it can share a lock with other objects, or whether it requires a new lock. COM+ groups together components than can share a causality-based lock. This grouping is called an activity.

It is important to understand that an activity is only a logical term and is independent of process, apartment, and context: objects from different contexts, apartments, or processes can all share the same activity (see Figure 5-3).

Activities (indicated by dashed lines) are independent of contexts, apartments, and processes

Figure 5-3.  Activities (indicated by dashed lines) are independent of contexts, apartments, and processes

Within an activity, concurrent calls from multiple causalities are not allowed and COM+ enforces this requirement. Activities are very useful for MTA objects and for neutral threaded apartment (NTA) objects, a new threading model discussed at the end of the chapter; these objects may require synchronization, but not physical thread affinity with all its limitations. STA objects are synchronized by virtue of thread affinity and do not benefit from activities.

Causality-Based Lock

To achieve causality-based synchronization for objects that take part in an activity, COM+ maintains a causality-based lock for each activity. The activity lock can be owned by at most one causality at a time. The activity lock keeps track of the causality that currently owns it by tracking that causality’s ID. The causality ID is used as an identifying key to access the lock. When a causality enters an activity, it must try to acquire the activity lock first by presenting the lock with its ID. If the lock is already owned by a different causality (it will have a different ID), the lock blocks the new causality that tries to enter the activity. If the lock is free (no causality owns it or the lock has no causality ID associated with it), the new causality will own it. If the causality already owns that lock, it will not be blocked, which allows for callbacks. The lock has no timeout associated with it; as a result, a call from outside the activity is blocked until the current causality exits the activity. In the case of more than one causality trying to enter the activity, COM+ places all pending causalities in a queue and lets them enter in the activity in order.

The activity lock is effective process-wide only. When an activity flows from Process 1 to Process 2, COM+ allocates a new lock in Process 2 for that activity, so that attempts to access the local objects in Process 2 will not have to pay for expensive cross-process or cross-machine lookups.

An interesting observation is that a causality-based lock is unlike any other Win32 API-provided locks. Normal locks (critical sections, mutexes, and semaphores) are all based on a physical thread ID. A normal physical thread-based lock records the physical thread ID that owns it, blocking any other physical thread that tries to access it, all based on physical thread IDs. The causality-based lock lets all the physical threads that take part in the same logical thread (same causality) go through; it only blocks threads that call from different causalities. There is no documented API for the causality lock. Activity-based synchronization solves the classic COM deadlock of cyclic calling—if Object 1 calls Object 2, which then calls Object 3, which then calls Object 1, the call back to Object 1 would go through because it shares the same causality, even if all the objects execute on different threads.

Activities and Contexts

So how does COM+ know which activity a given object belongs to? What propagates the activity across contexts, apartments, and processes? Like almost everything else in COM+, the proxy and stub pair does the trick.

COM+ maintains an identifying GUID called the activity ID for every activity. When a client creates a COM+ object that wants to take part in an activity and the client has no activity associated with it, COM+ generates an activity ID and stores it as a property of the context object (discussed in Chapter 2). A COM+ context belongs to at most one activity at any given time, and maybe none at all.

The object that created the activity ID is called the root of the activity. When the root object creates another object in a different context—say Object 2—the proxy to Object 2 grabs the activity ID from the context object and passes it to the stub of Object 2, potentially across processes and machines. If Object 2 requires synchronization, its context uses the activity ID of the root.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset