3

Think of a network of nodes (update: 'network of nodes' meaning objects in the same application domain, not a network of independent applications) passing objects to each other (and doing some processing on them). Is there a pattern in C# for restricting the access to an object to only the node that is actually processing it?

Main motivation: Ensuring thread-safety (no concurrent access) and object consistency (regarding the data stored in it).

V1: I thought of something like this:

class TransferredObject
    {
        public class AuthLock
        {
            public bool AllowOwnerChange { get; private set; }
            public void Unlock() { AllowOwnerChange = true; }
        }

        private AuthLock currentOwner;

        public AuthLock Own()
        {
            if (currentOwner != null && !currentOwner.AllowOwnerChange)
                throw new Exception("Cannot change owner, current lock is not released.");

            return currentOwner = new AuthLock();
        }

        public void DoSomething(AuthLock authentification)
        {
            if (currentOwner != authentification)
                throw new Exception("Don't you dare!");

            // be sure, that this is only executed by the one holding the lock

            // Do something...
        }
    }

    class ProcessingNode
    {
        public void UseTheObject(TransferredObject  x)
        {
            // take ownership
            var auth = x.Own();

            // do processing
            x.DoSomething(auth);

            // release ownership
            auth.Unlock();
        }
    }

V2: Pretty much overhead - a less 'strict' implementation would perhaps be to ignore the checking and rely on the "lock/unlock" logic:

class TransferredObject
    {
        private bool isLocked;

        public Lock() 
        {
            if(isLocked)
                throw new Exception("Cannot lock, object is already locked.");

            isLocked = true; 
        }
        public Unlock() { isLocked = false; }

        public void DoSomething()
        {
            if (isLocked)
                throw new Exception("Don't you dare!");

            // Do something...
        }
    }

    class ProcessingNode
    {
        public void UseTheObject(TransferredObject  x)
        {
            // take ownership
            x.Lock = true;

            // do processing
            x.DoSomething();

            // release ownership
            x.Unlock = true;
        }
    }

However: This looks a bit unintuitive (and having to pass an the auth instance with ervery call is ugly). Is there a better approach? Or is this a problem 'made by design'?

mvo
  • 1,138
  • 10
  • 18
  • @Adriano: By 'object' the object instance was meant. I don't see how this logic could be bypassed by another node since the auth object is private to the transfered object instance and the currently editing node? – mvo Dec 31 '13 at 12:32
  • BTW when you're taling about "nodes" I assumed you have a network and each node is a network node (not just...another object within same appdomain). – Adriano Repetti Dec 31 '13 at 12:45
  • Ah, I indeed meant objects within the same domain. Sorry for beeing ambiguous. (Then I understand your answer of course.) – mvo Dec 31 '13 at 12:52
  • Well then, what kind of lock you need? I mean: during processing object state is valid and can be read? – Adriano Repetti Dec 31 '13 at 14:16
  • Please see http://english.stackexchange.com/questions/7844 – Eric Lippert Dec 31 '13 at 16:18
  • Yes, if implemented like in V1 this is guaranteed and (forced to be so) while V2 is nicer to use. If there would be a possibility to identfy the caller somehow from inside the object without having to pass the "Lock" thing, I would consider it a much nicer solution. @EricLippert: Allright, thanks :-) I am no native speaker, messed it up with the word german word "Authentifizierung", I guess. – mvo Dec 31 '13 at 16:34
  • It sure sounds like it ought to be the right word, doesn't it? – Eric Lippert Dec 31 '13 at 16:39
  • @mvondano It's generally bad form to throw `Exception` directly. Rather you should throw a custom-derived exception or in this case a standard exception. `InvalidOperationException` is perfect in all of the cases above. See http://msdn.microsoft.com/en-us/library/system.exception.aspx. – Tergiver Dec 31 '13 at 16:48
  • Yes, you are absolutely right. The above is just demo code, but it is for sure better to use good code style in examples, too. – mvo Dec 31 '13 at 17:15

2 Answers2

13

To clarify your question: you seek to implement the rental threading model in C#. A brief explanation of different ways to handle concurrent access to an object would likely be helpful.

  • Single-threaded: all accesses to the object must happen on the main thread.

  • Free-threaded: any access to the object may happen on any thread; the developer of the object is responsible for ensuring the internal consistency of the object. The developer of the code consuming the object is responsible for ensuring that "external consistency" is maintained. (For example, a free-threaded dictionary must maintain its internal state consistently when adds and removes happen on multiple threads. An external caller must recognize that the answer to the question "do you contain this key?" might change due to an edit from another thread.)

  • Apartment threaded: all accesses to a given instance of an object must happen on the thread that created the object, but different instances can be affinitized to different threads. The developer of the object must ensure that internal state which is shared between objects is safe for multithreaded access but state which is associated with a given instance will only ever be read or written from a single thread. Typically UI controls are apartment threaded and must be in the apartment of the UI thread.

  • Rental threaded: access to a given instance of an object must happen from only a single thread at any one time, but which thread that is may change over time

So now let's consider some questions that you should be asking:

  • Is the rental model a reasonable way to simplify my life, as the author of an object?

Possibly.

The point of the rental model is to achieve some of the benefits of multithreading without taking on the cost of implementing and testing a free-threaded model. Whether those increased benefits and lowered costs are a good fit, I don't know. I personally am skeptical of the value of shared memory in multithreaded situations; I think the whole thing is a bad idea. But if you're bought into the crazy idea that multiple threads of control in one program modifying shared memory is goodness, then maybe the rental model is for you.

The code you are writing is essentially an aid to the caller of your object to make it easier for the caller to obey the rules of the rental model and easier to debug the problem when they stray. By providing that aid to them you lower their costs, at some moderate increase to your own costs.

The idea of implementing such an aid is a good one. The original implementations of VBScript and JScript at Microsoft back in the 1990s used a variation on the apartment model, whereby a script engine would transit from a free-threaded mode into an apartment-threaded mode. We wrote a lot of code to detect callers that were violating the rules of our model and produce errors immediately, rather than allowing the violation to produce undefined behaviour at some unspecified point in the future.

  • Is my code correct?

No. It's not threadsafe! The code that enforces the rental model and detects violations of it cannot itself assume that the caller is correctly using the rental model! You need to introduce memory barriers to ensure that the various threads reading and writing your lock bools are not moving those reads and writes around in time. Your Own method is chock full of race conditions. This code needs to be very, very carefully designed and reviewed by an expert.

My recommendation - assuming again that you wish to pursue a shared memory multithreaded solution at all - is to eliminate the redundant bool; if the object is unowned then the owner should be null. I don't usually advocate a low-lock solution, but in this case you might consider looking at Interlocked.CompareExchange to do an atomic compare-and-swap on the field with a new owner. If the compare to null fails then the user of your API has a race condition which violates the rental model. This introduces a memory barrier.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • First of all thanks a lot for your detailed and descriptive answer (I would like to vote it up, but I have to 'earn more reputation' before beeing allowed to do so...). I'll try to get into it, if only because I'm curious if I can produce a stable solution. :-) The important differentiation really is: Do I need support for a process? or: Do I (also) need to enforce a threadsafe behavior (e.g. to preserve data-integrety of the system). In my case I wanted to make sure, that the processesing in the network follows some rules like "every message can only be altered in one place at the same time". – mvo Jan 01 '14 at 13:51
  • If I understand correctly, having something like this would solve some problems: `private object token; public object TryLock() { var newToken = new object(); Interlocked.CompareExchange(ref token, newToken, null); return newToken; }` – mvo Jan 01 '14 at 13:57
0

Maybe your example is too simplified and you really need this complex ownership thing, but the following code should do the job:

class TransferredObject
{
    private object _lockObject = new object();

    public void DoSomething()
    {
        lock(_lockObject)
        {
            // TODO: your code here...
        }
    }
}

Your TransferredObject has an atomic method DoSomething that changes some state(s) and should not run multiple times at the same time. So, just put a lock into it to synchronize the critical section.

See http://msdn.microsoft.com/en-us/library/c5kehkcz%28v=vs.90%29.aspx

Sebastian Negraszus
  • 11,915
  • 7
  • 43
  • 70
  • Thanks, that was my first approach. Unfortunately in most cases its necessary (or at least simplifies things) to access the object multiple times while processing - reading and setting properties calling different methods. But there might be the possibility to centralize the access again, cause this is a lot simpler... – mvo Dec 31 '13 at 12:41
  • I don't know, either. For synchronizing only one method this is a valid solution. – mvo Dec 31 '13 at 16:37