summaryrefslogtreecommitdiff
path: root/src/mscorlib/src/System/Threading/CancellationTokenRegistration.cs
blob: be760fab3f4f17cec1f47085b8a0ec0feb9ae63f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

//
////////////////////////////////////////////////////////////////////////////////

using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;

namespace System.Threading
{
    /// <summary>
    /// Represents a callback delegate that has been registered with a <see cref="T:System.Threading.CancellationToken">CancellationToken</see>.
    /// </summary>
    /// <remarks>
    /// To unregister a callback, dispose the corresponding Registration instance.
    /// </remarks>
    public struct CancellationTokenRegistration : IEquatable<CancellationTokenRegistration>, IDisposable
    {
        private readonly CancellationCallbackInfo m_callbackInfo;
        private readonly SparselyPopulatedArrayAddInfo<CancellationCallbackInfo> m_registrationInfo;

        internal CancellationTokenRegistration(
            CancellationCallbackInfo callbackInfo,
            SparselyPopulatedArrayAddInfo<CancellationCallbackInfo> registrationInfo)
        {
            m_callbackInfo = callbackInfo;
            m_registrationInfo = registrationInfo;
        }

        /// <summary>
        /// Attempts to deregister the item. If it's already being run, this may fail.
        /// Entails a full memory fence.
        /// </summary>
        /// <returns>True if the callback was found and deregistered, false otherwise.</returns>
        [FriendAccessAllowed]
        internal bool TryDeregister()
        {
            if (m_registrationInfo.Source == null)  //can be null for dummy registrations.
                return false;

            // Try to remove the callback info from the array.
            // It is possible the callback info is missing (removed for run, or removed by someone else)
            // It is also possible there is info in the array but it doesn't match our current registration's callback info.  
            CancellationCallbackInfo prevailingCallbackInfoInSlot = m_registrationInfo.Source.SafeAtomicRemove(m_registrationInfo.Index, m_callbackInfo);

            if (prevailingCallbackInfoInSlot != m_callbackInfo)
                return false;  //the callback in the slot wasn't us.

            return true;
        }

        /// <summary>
        /// Disposes of the registration and unregisters the target callback from the associated 
        /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see>.
        /// If the target callback is currently executing this method will wait until it completes, except
        /// in the degenerate cases where a callback method deregisters itself.
        /// </summary>
        public void Dispose()
        {
            // Remove the entry from the array.
            // This call includes a full memory fence which prevents potential reorderings of the reads below
            bool deregisterOccurred = TryDeregister();

            // We guarantee that we will not return if the callback is being executed (assuming we are not currently called by the callback itself)
            // We achieve this by the following rules:
            //    1. if we are called in the context of an executing callback, no need to wait (determined by tracking callback-executor threadID)
            //       - if the currently executing callback is this CTR, then waiting would deadlock. (We choose to return rather than deadlock)
            //       - if not, then this CTR cannot be the one executing, hence no need to wait
            //
            //    2. if deregistration failed, and we are on a different thread, then the callback may be running under control of cts.Cancel()
            //       => poll until cts.ExecutingCallback is not the one we are trying to deregister.

            var callbackInfo = m_callbackInfo;
            if (callbackInfo != null)
            {
                var tokenSource = callbackInfo.CancellationTokenSource;
                if (tokenSource.IsCancellationRequested && //running callbacks has commenced.
                    !tokenSource.IsCancellationCompleted && //running callbacks hasn't finished
                    !deregisterOccurred && //deregistration failed (ie the callback is missing from the list)
                    tokenSource.ThreadIDExecutingCallbacks != Thread.CurrentThread.ManagedThreadId) //the executingThreadID is not this threadID.
                {
                    // Callback execution is in progress, the executing thread is different to us and has taken the callback for execution
                    // so observe and wait until this target callback is no longer the executing callback.
                    tokenSource.WaitForCallbackToComplete(m_callbackInfo);
                }
            }
        }

        /// <summary>
        /// Determines whether two <see
        /// cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see>
        /// instances are equal.
        /// </summary>
        /// <param name="left">The first instance.</param>
        /// <param name="right">The second instance.</param>
        /// <returns>True if the instances are equal; otherwise, false.</returns>
        public static bool operator ==(CancellationTokenRegistration left, CancellationTokenRegistration right)
        {
            return left.Equals(right);
        }

        /// <summary>
        /// Determines whether two <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see> instances are not equal.
        /// </summary>
        /// <param name="left">The first instance.</param>
        /// <param name="right">The second instance.</param>
        /// <returns>True if the instances are not equal; otherwise, false.</returns>
        public static bool operator !=(CancellationTokenRegistration left, CancellationTokenRegistration right)
        {
            return !left.Equals(right);
        }

        /// <summary>
        /// Determines whether the current <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see> instance is equal to the 
        /// specified <see cref="T:System.Object"/>.
        /// </summary> 
        /// <param name="obj">The other object to which to compare this instance.</param>
        /// <returns>True, if both this and <paramref name="obj"/> are equal. False, otherwise.
        /// Two <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see> instances are equal if
        /// they both refer to the output of a single call to the same Register method of a 
        /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see>. 
        /// </returns>
        public override bool Equals(object obj)
        {
            return ((obj is CancellationTokenRegistration) && Equals((CancellationTokenRegistration)obj));
        }

        /// <summary>
        /// Determines whether the current <see cref="T:System.Threading.CancellationToken">CancellationToken</see> instance is equal to the 
        /// specified <see cref="T:System.Object"/>.
        /// </summary> 
        /// <param name="other">The other <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see> to which to compare this instance.</param>
        /// <returns>True, if both this and <paramref name="other"/> are equal. False, otherwise.
        /// Two <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see> instances are equal if
        /// they both refer to the output of a single call to the same Register method of a 
        /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see>. 
        /// </returns>
        public bool Equals(CancellationTokenRegistration other)
        {
            return m_callbackInfo == other.m_callbackInfo &&
                   m_registrationInfo.Source == other.m_registrationInfo.Source &&
                   m_registrationInfo.Index == other.m_registrationInfo.Index;
        }

        /// <summary>
        /// Serves as a hash function for a <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration.</see>.
        /// </summary>
        /// <returns>A hash code for the current <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see> instance.</returns>
        public override int GetHashCode()
        {
            if (m_registrationInfo.Source != null)
                return m_registrationInfo.Source.GetHashCode() ^ m_registrationInfo.Index.GetHashCode();

            return m_registrationInfo.Index.GetHashCode();
        }
    }
}