SourceForge: sasa/sasa: Sasa.TM/ThreadScoped.cs@fe7946510903
Sasa.TM/ThreadScoped.cs
author Sandro Magi <naasking@gmail.com>
Mon Sep 26 16:10:18 2011 -0400 (7 months ago)
branchthread-scoped
changeset 704 fe7946510903
parent 703 df19877f1b40
child 708 3a2363a58dbf
permissions -rw-r--r--
-always try to dispose of Transacted<T>.log since it's thread-local data
     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Diagnostics;
     5 using System.Threading;
     6 
     7 namespace Sasa.Concurrency
     8 {
     9     /// <summary>
    10     /// Encapsulates instance-specific thread-local data.
    11     /// </summary>
    12     /// <typeparam name="T">The type of data.</typeparam>
    13     public abstract class ThreadScoped<T> : IRef<T>, IDisposable
    14     {
    15         internal ThreadScoped<T> next; // track refs in a list for reuse
    16         internal uint current;         // the current version number of the data
    17 
    18         // Always start with one ref node, indexed by T at the start. Each ThreadScoped allocation either generates
    19         // a new generic type Ref<Ref<TIndex>>, or returns an existing one that was disposed. Disposal pushes the
    20         // Ref back onto the list at the front so we can reuse existing generic types as they've been deallocated.
    21         // Invariant: the last item in the list is always the highest generic type to have been allocated.
    22         internal static ThreadScoped<T> free = new Ref<T>();
    23 
    24         /// <summary>
    25         /// Destroy this thread-local variable.
    26         /// </summary>
    27         ~ThreadScoped()
    28         {
    29             Dispose();
    30         }
    31         /// <summary>
    32         /// Ensure others can't inherit from this class.
    33         /// </summary>
    34         ThreadScoped()
    35         {
    36         }
    37         /// <summary>
    38         /// The value of the thread-local.
    39         /// </summary>
    40         public abstract T Value { get; set; }
    41         /// <summary>
    42         /// Dispose of this thread-local instance.
    43         /// </summary>
    44         public abstract void Dispose();
    45 
    46         /// <summary>
    47         /// Allocate a variable.
    48         /// </summary>
    49         /// <returns>Either a new instance, or an old instance being reused.</returns>
    50         internal abstract ThreadScoped<T> Allocate();
    51 
    52         /// <summary>
    53         /// A unique reference generated by the phantom type <typeparamref name="TIndex"/>.
    54         /// </summary>
    55         /// <typeparam name="TIndex">The phantom type used to generate unique instances of thread-local data.</typeparam>
    56         sealed class Ref<TIndex> : ThreadScoped<T>
    57         {
    58             [ThreadStatic]
    59             static T scoped;     // the unique thread-local slot
    60             [ThreadStatic]
    61             static uint version; // the version number of the thread-local data
    62             /// <summary>
    63             /// The thread-local data.
    64             /// </summary>
    65             /// <remarks>
    66             /// The thread-local and the outer version number must match to return
    67             /// the stored thread-local value. If the versions do not match, the
    68             /// thread-local value is cleared and the thread-local version number
    69             /// incremented.
    70             /// </remarks>
    71             public override T Value
    72             {
    73                 get { return current == version ? scoped : Update(); }
    74                 set { scoped = value; }
    75             }
    76             T Update()
    77             {
    78                 if (next != this) throw new ObjectDisposedException("Transacted<T> has been disposed.");
    79                 version = current;
    80                 return scoped = default(T);
    81             }
    82             public override void Dispose()
    83             {
    84                 // ensure only one disposal because an allocated ThreadLocal.next references
    85                 // 'this', so update 'next' to null atomically, and only allow the thread that
    86                 // succeeded through
    87                 var self = Interlocked.CompareExchange(ref next, null, this);
    88                 if (self == this)
    89                 {
    90                     // increment outer version number to ensure old thread-local data expires
    91                     ++base.current;
    92                     // push this ref onto the list of free refs
    93                     do
    94                     {
    95                         next = free;
    96                     } while (Atomics.SetFailed(ref free, this, next));
    97                 }
    98                 // update the thread-local version number and clear the value
    99                 // for every thread that calls Dispose()
   100                 version = current;
   101                 scoped = default(T);
   102             }
   103             internal override ThreadScoped<T> Allocate()
   104             {
   105                 // if 'next' is null, we are at the end of the list of free refs,
   106                 // so allocate a new one and enqueue it, then return 'this'
   107                 var x = next;
   108                 if (x != null) return this;
   109                 x = Interlocked.CompareExchange(ref next, new Ref<Ref<TIndex>>(), null);
   110                 // atomic swap failure doesn't matter, since the caller of Acquire()
   111                 // accesses whatever instance is at this.next
   112                 return this;
   113             }
   114         }
   115     }
   116     /// <summary>
   117     /// Extensions to <seealso cref="ThreadScoped{T}"/>.
   118     /// </summary>
   119     public static class ThreadScoped
   120     {
   121         /// <summary>
   122         /// Construct a <seealso cref="ThreadScoped{T}"/> with a default value.
   123         /// </summary>
   124         /// <typeparam name="T">The type of the variable.</typeparam>
   125         /// <param name="value">The default instance value.</param>
   126         /// <returns>A new thread-scoped variable.</returns>
   127         public static ThreadScoped<T> Create<T>(T value)
   128         {
   129             var x = Create<T>();
   130                 x.Value = value;
   131             return x;
   132         }
   133         /// <summary>
   134         /// Construct a <seealso cref="ThreadScoped{T}"/>.
   135         /// </summary>
   136         /// <typeparam name="T">The type of the variable.</typeparam>
   137         /// <returns>A new thread-scoped variable.</returns>
   138         public static ThreadScoped<T> Create<T>()
   139         {
   140             // pop the head of the static reference list in a thread-safe way
   141             ThreadScoped<T> x;
   142             do
   143             {
   144                 x = ThreadScoped<T>.free.Allocate();
   145             } while (Atomics.SetFailed(ref ThreadScoped<T>.free, x.next, x));
   146             // self-reference used to mutually exclude in Dispose()
   147             x.next = x;
   148             return x;
   149         }
   150     }
   151 }