2 using System.Collections.Generic;
4 using System.Diagnostics;
5 using System.Threading;
7 namespace Sasa.Concurrency
10 /// Encapsulates instance-specific thread-local data.
12 /// <typeparam name="T">The type of data.</typeparam>
13 public abstract class ThreadScoped<T> : IRef<T>, IDisposable
15 internal ThreadScoped<T> next; // track refs in a list for reuse
16 internal uint current; // the current version number of the data
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>();
25 /// Destroy this thread-local variable.
32 /// Ensure others can't inherit from this class.
38 /// The value of the thread-local.
40 public abstract T Value { get; set; }
42 /// Dispose of this thread-local instance.
44 public abstract void Dispose();
47 /// Allocate a variable.
49 /// <returns>Either a new instance, or an old instance being reused.</returns>
50 internal abstract ThreadScoped<T> Allocate();
53 /// A unique reference generated by the phantom type <typeparamref name="TIndex"/>.
55 /// <typeparam name="TIndex">The phantom type used to generate unique instances of thread-local data.</typeparam>
56 sealed class Ref<TIndex> : ThreadScoped<T>
59 static T scoped; // the unique thread-local slot
61 static uint version; // the version number of the thread-local data
63 /// The thread-local data.
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
71 public override T Value
73 get { return current == version ? scoped : Update(); }
74 set { scoped = value; }
78 if (next != this) throw new ObjectDisposedException("Transacted<T> has been disposed.");
80 return scoped = default(T);
82 public override void Dispose()
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
87 var self = Interlocked.CompareExchange(ref next, null, this);
90 // increment outer version number to ensure old thread-local data expires
92 // push this ref onto the list of free refs
96 } while (Atomics.SetFailed(ref free, this, next));
98 // update the thread-local version number and clear the value
99 // for every thread that calls Dispose()
103 internal override ThreadScoped<T> Allocate()
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'
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
117 /// Extensions to <seealso cref="ThreadScoped{T}"/>.
119 public static class ThreadScoped
122 /// Construct a <seealso cref="ThreadScoped{T}"/> with a default value.
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)
134 /// Construct a <seealso cref="ThreadScoped{T}"/>.
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>()
140 // pop the head of the static reference list in a thread-safe way
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()