Merge pull request #3262 from lindenlab/add_continuations_test
[mono.git] / mcs / class / Facades / System.Threading.Overlapped / DeferredDisposableLifetime.cs
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4
5 using System.Diagnostics;
6
7 namespace System.Threading
8 {
9     /// <summary>
10     /// Provides callbacks to objects whose lifetime is managed by <see cref="DeferredDisposableLifetime{T}"/>.
11     /// </summary>
12     internal interface IDeferredDisposable
13     {
14         /// <summary>
15         /// Called when the object's refcount reaches zero.
16         /// </summary>
17         /// <param name="disposed">
18         /// Indicates whether the object has been disposed.
19         /// </param>
20         /// <remarks>
21         /// If the refount reaches zero before the object is disposed, this method will be called with
22         /// <paramref name="disposed"/> set to false.  If the object is then disposed, this method will be
23         /// called again, with <paramref name="disposed"/> set to true.  If the refcount reaches zero
24         /// after the object has already been disposed, this will be called a single time, with 
25         /// <paramref name="disposed"/> set to true.
26         /// </remarks>
27         void OnFinalRelease(bool disposed);
28     }
29
30     /// <summary>
31     /// Manages the lifetime of an object which implements IDisposable, but which must defer the actual
32     /// cleanup of state until all existing uses of the object are complete.
33     /// </summary>
34     /// <typeparam name="T">The type of object whose lifetime will be managed.</typeparam>
35     /// <remarks>
36     /// This type maintains a reference count, and tracks whether the object has been disposed.  When
37     /// Callbacks are made to <see cref="IDeferredDisposable.OnFinalRelease(bool)"/> when the refcount
38     /// reaches zero.  Objects that need to defer cleanup until they have been disposed *and* they have
39     /// no more references can do so in <see cref="IDeferredDisposable.OnFinalRelease(bool)"/> when
40     /// 'disposed' is true.
41     /// </remarks>
42     internal struct DeferredDisposableLifetime<T> where T : class, IDeferredDisposable
43     {
44         //
45         // _count is positive until Dispose is called, after which it's (-1 - refcount).
46         //
47         private int _count;
48
49         public bool AddRef(T obj)
50         {
51             while (true)
52             {
53                 int oldCount = Volatile.Read(ref _count);
54
55                 // Have we been disposed?
56                 if (oldCount < 0)
57                     throw new ObjectDisposedException(typeof(T).ToString());
58
59                 int newCount = checked(oldCount + 1);
60
61                 if (Interlocked.CompareExchange(ref _count, newCount, oldCount) == oldCount)
62                     return true;
63             }
64         }
65
66         public void Release(T obj)
67         {
68             while (true)
69             {
70                 int oldCount = Volatile.Read(ref _count);
71                 if (oldCount > 0)
72                 {
73                     // We haven't been disposed.  Decrement _count.
74                     int newCount = oldCount - 1;
75                     if (Interlocked.CompareExchange(ref _count, newCount, oldCount) == oldCount)
76                     {
77                         if (newCount == 0)
78                             obj.OnFinalRelease(disposed: false);
79                         return;
80                     }
81                 }
82                 else
83                 {
84                     Debug.Assert(oldCount != 0 && oldCount != -1);
85
86                     // We've been disposed.  Increment _count.
87                     int newCount = oldCount + 1;
88                     if (Interlocked.CompareExchange(ref _count, newCount, oldCount) == oldCount)
89                     {
90                         if (newCount == -1)
91                             obj.OnFinalRelease(disposed: true);
92                         return;
93                     }
94                 }
95             }
96         }
97
98         public void Dispose(T obj)
99         {
100             while (true)
101             {
102                 int oldCount = Volatile.Read(ref _count);
103                 if (oldCount < 0)
104                     return; // already disposed
105
106                 int newCount = -1 - oldCount;
107                 if (Interlocked.CompareExchange(ref _count, newCount, oldCount) == oldCount)
108                 {
109                     if (newCount == -1)
110                         obj.OnFinalRelease(disposed: true);
111                     return;
112                 }
113             }
114         }
115     }
116 }