ContextBoundObject – Part #1 – Making Contexts

[Deviation from WF posting again.]
Sometimes you learn enough about something just well enough to write some code that is useful. A month later, you wake and say… ‘but I still don’t really feel like I know what is going on.’ Today is such a day to clear my head - today that something is ContextBoundObject. So far, I have a vague or general idea what ContextBoundObject is supposed to do – what I lack is details! So I’m going to start teaching myself.

Obligatory disclaimer: I may work at MS but, in my usual foolish way, my sources for this article are: informed guessing, reflector diving, and tidbits gleaned from random articles on the intertubes. This blog is not ‘official’ documentation.

What is ContextBoundObject?

ContextBoundObject is a subclass of MarshalByRefObject that is black magic. When I say black magic, I mean the CLR does something mysterious in order to make it work. OK magic, great. But what does the magic do? And why does ContextBoundObject subclass MarshalByRefObject?

If you’ve done a bit of .net remoting, you learned that MarshalByRefObject is the main alternative to being [Serializable]. In brief:

-Serializable objects are saved into byte streams. Bytes migrate to a new place, and then they are reassembled into a replica of the original object. You end up with multiple instances of the object.
-MarshalByRefObjects are turned into magical cookies. Magical cookies are turned into bytes, which get moved, and are reassembled into a proxy which allows you to talk to the original MarshalByRefObject. There is still only one authoritative instance of the object .

In .net remoting it’s pretty cool that as soon as you derive from MarshalByRefObject, the framework automatically knows how to do all that proxy creation, marshalling, and so on with hardly any special instruction. Yay!

So what about ContextBoundObject?

Firstly, it allows you to do everything you can do with MarshalByRef object. Secondly, it allows you to do more. You can take control over the details of control over object creation, proxy creation, and marshalling method calls.

If it’s lower level, why is it not the base class? I think the rationale is this: MarshalByRefObject is the base class because it is the more abstract of the two classes, or two concepts. It specifies the instancing proxying behavior. It doesn’t expose a lot of implementation detail. ContextBoundObject, on the other hand, exposes a bit more implementation detail, and gives you those ways to customize everything that goes on. In the sense that it is more down and dirty, ContextBoundObject is like a lower-level way of thinking about the problems, and more specialized.

MarshalByRefObject, and Proxies

In a vanilla scenario of using remoting to talk to a MarshalByRefObject in another domain, what is involved? There are 3 main moving parts

  1. Transparent proxy
  2. ‘Real’ proxy
  3. The actual MarshalByRefObject

The transparent proxy is a special object which pretends to be the real object, and looks just like the real object. Because the CLR has to know to treat call going through proxies specially, for reasons of performance etc, the following rule apparently exists: you can only have transparent proxies for MarshalByRef objects.

We could therefore redefine MarshalByRef object as the following: “The class of objects which can have transparent proxies”.

ContextBoundObject, and Proxies

When you decide to go the ContextBoundObject, your options are broadened somewhat to e.g.:

1. Transparent proxy
2. Intercepting or forwarding logic (perhaps a ‘real’ proxy is involved)
3. Possibly some actual ContextBoundObject instance.

What ContextBoundObject is giving you is full control over the proxying behavior.
In general what this lets you do is control the method calls to your object in interesting ways. Context can be used to enforce policies on arbitrary method calls.  Really?

To explain how, we must explain the Context part of in ContextBoundObject.

Contexts, lesson 1:

Contexts are a system to decide which method calls are intercepted. Contexts begin with these ground rules:

GR #1) Every thread executing has a current context.

GR #2) Each thread created in AppDomain Foo initially executes with the default context of that app domain Foo.

In the case of remoting, object context is typically just the default context of each app domain. And the method calls which go cross-context, will be the same calls which are going to be marshalled between the app domains.

GR #3) Contexts have properties associated with them (ContextProperties). These properties are usually used to determine whether contexts are equivalent somehow, and therefore whether calls are cross-context or not (as part of the rules for marshalling between contexts).

GR #4) Objects have an associated context. All objects? Not sure – but anyway certain objects (ContextBoundObjects? MarshalByRefObjects?)have context. The context is set up by a ContextAttribute (or IContextAttribute).

From the ground rules we infer what might be a typical picture: Contexts form one or more partitions of your object space (whether your object is one AppDomain or multiple AppDomains doesn’t really matter).

image

Threads start off in some context(part of the partition) , but they will migrate to other contexts when they make a call to an object in another context.

 

The trivial case - if you never created any new app domains or new contexts then all your objects in one app domain live in the same context, and you never make a cross-context call. (This is good, that the trivial case comes out simple. Cross-context calls might be more complicated and slow.)

 

Learning by example

To see ContextBoundObject in action, let’s dissect System.Runtime.Remoting.Contexts.SynchronizationAttribute.

 

SynchronizationAttribute [MSDN]: ‘Enforces a synchronization domain for the current context and all contexts that share the same instance. Remarks: When this attribute is applied to an object, only one thread can be executing in all contexts that share an instance of this property.’ (huh, an instance of what property???)

SynchronizationAttribute [me]: ‘An attribute, which is supposed to somehow help automatically implement or enforce synchronization policy of an object (i.e. rules of acquiring exclusive locks when making method calls)’.

 

So, how about that SynchronizationAttribute? In order for it to be doing anything interesting, enforcing policies, or whatever it does, yes, it is creating some Contexts. Let’s dive into SynchronizationAttribute using reflector. Oh, interesting… maybe this string is being used to create a named ContextProperty?

private const string PROPERTY_NAME = "Synchronization";

 

Here’s something else interesting, it overrides ContextAttribute.IsContextOK():

 public override bool IsContextOK(Context ctx, IConstructionCallMessage msg)
{
    if (ctx == null)
    {
        throw new ArgumentNullException("ctx");
    }
    if (msg == null)
    {
        throw new ArgumentNullException("msg");
    }
    bool flag = true;
    if (this._flavor == 8)
    {
        return false;
    }
    SynchronizationAttribute property = (SynchronizationAttribute) ctx.GetProperty("Synchronization");
    if (((this._flavor == 1) && (property != null)) || ((this._flavor == 4) && (property == null)))
    {
        flag = false;
    }
    if (this._flavor == 4)
    {
        this._cliCtxAttr = property;
    }
    return flag;
}

Hmm. This code appears to determine whether to create a new context for a new object (=an object being ‘new’-ed). The input includes a Context (the calling thread’s context) and an IConstructionCallMessage (the ‘new object’ constructor invocation including arguments). If we try to analyze the logic, first we should notice that it is highly dependent on this field ‘_flavor’, which is an integer equalling one of [MSDN]:

  • NOT_SUPPORTED = 1,  //Indicates that the class to which this attribute is applied cannot be created in a context that has synchronization.
  • SUPPORTED = 2, //Indicates that the class to which this attribute is applied is not dependent on whether the context has synchronization.
  • REQUIRED = 4, //Indicates that the class to which this attribute is applied must be created in a context that has synchronization.
  • REQUIRES_NEW = 8, //Indicates that the class to which this attribute is applied must be created in a context with a new instance of the synchronization property each time.

You can trace through the possible. The basic upshot is some of the time (or for REQUIRES_NEW, all of the time) IsContextOK will return false. And this is what causes a new context to be created for the new object to be attached to. OK.

Let’s use this to create our own sample code to test our knowledge of context creation.

 

Sample 1. – we use a ContextAttribute to control creation of contexts (sample).

    [AttributeUsage(AttributeTargets.Class)]

    public class BasicContextAttribute : ContextAttribute

    {

        public BasicContextAttribute()

            : base("BASICCONTEXTTEST")

        {

        }

 

        public override bool IsContextOK(Context ctx, System.Runtime.Remoting.Activation.IConstructionCallMessage ctorMsg)

        {

            Console.WriteLine("IsContextOK: " + ctorMsg.ActivationType.ToString());

            return base.IsContextOK(ctx, ctorMsg);

        }

    }

 

    [BasicContext]

    public class MBRObj : MarshalByRefObject

    {

    }

 

    [BasicContext]

    public class CBObj : ContextBoundObject

    {

    }

 

    public class Program

    {

        public static void Main(string[] args)

        {

            MBRObj m = new MBRObj();

            CBObj c = new CBObj();

        }

    }

Sample code output:

    ‘IsContextOK: ConsoleApplication13.CBObj’

Note on the sample code: I added MarshalByRefObject in there as an experiment because I was curious. From the output, you can see that IsContextOK() is not called for MBRObj(). Conclusion: ContextAttributes are only invoked when they are on a ContextBoundObject.