aboutsummaryrefslogtreecommitdiff
path: root/Utils/src/Async/AsyncExclusiveResource.cs
blob: 18e2a42791f3a09d525d1bc6395bc8d3217c9e58 (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
160
161
162
163
164
165
166
167
168
169
/*
* Copyright (c) 2022 Vaughn Nugent
* 
* Library: VNLib
* Package: VNLib.Utils
* File: AsyncExclusiveResource.cs 
*
* AsyncExclusiveResource.cs is part of VNLib.Utils which is part of the larger 
* VNLib collection of libraries and utilities.
*
* VNLib.Utils is free software: you can redistribute it and/or modify 
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 2 of the License,
* or (at your option) any later version.
*
* VNLib.Utils is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License 
* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
*/

using System;
using System.Threading;
using System.Threading.Tasks;

namespace VNLib.Utils.Async
{
    /// <summary>
    /// Provides a base class for resources that must be obtained exclusivly in a multi-threaded environment
    /// but allow state update operations (and their exceptions) to be deferred to the next accessor.
    /// </summary>
    /// <typeparam name="TState">The state parameter type passed during updates</typeparam>
    public abstract class AsyncExclusiveResource<TState> : VnDisposeable, IWaitHandle, IAsyncWaitHandle
    {
        /// <summary>
        /// Main mutli-threading lock used for primary access synchronization
        /// </summary>
        protected SemaphoreSlim MainLock { get; } = new (1, 1);       

        private Task? LastUpdate;

        /// <summary>
        /// <inheritdoc/>
        /// <br></br>
        /// <br></br>
        /// If the previous call to <see cref="UpdateAndRelease"/> resulted in an asynchronous update, and exceptions occured, an <see cref="AsyncUpdateException"/>
        /// will be thrown enclosing the exception
        /// </summary>
        /// <param name="millisecondsTimeout">Time in milliseconds to wait for exclusive access to the resource</param>
        /// <exception cref="AsyncUpdateException"></exception>
        /// <inheritdoc/>
        public virtual bool WaitOne(int millisecondsTimeout)
        {
            //First wait for main lock
            if (MainLock.Wait(millisecondsTimeout))
            {
                //Main lock has been taken
                try
                {
                    //Wait for async update if there is one pending(will throw exceptions if any occurred)
                    LastUpdate?.Wait();
                    return true;
                }
                catch (AggregateException ae) when (ae.InnerException != null)
                {
                    //Release the main lock and re-throw the inner exception
                    _ = MainLock.Release();
                    //Throw a new async update exception
                    throw new AsyncUpdateException(ae.InnerException);
                }
                catch
                {
                    //Release the main lock and re-throw the exception
                    _ = MainLock.Release();
                    throw;
                }
            }
            return false;
        }

        ///<inheritdoc/>
        ///<exception cref="ObjectDisposedException"></exception>
        public virtual async Task WaitOneAsync(CancellationToken token = default)
        {
            //Wait for main lock
            await MainLock.WaitAsync(token).ConfigureAwait(true);
            //if the last update completed synchronously, return true
            if (LastUpdate == null)
            {
                return;
            }
            try
            {
                //Await the last update task and catch its exceptions
                await LastUpdate.ConfigureAwait(false);
            }
            catch
            {
                //Release the main lock and re-throw the exception
                _ = MainLock.Release();
                throw;
            }
        }

        /// <summary>
        /// Requests a resource update and releases the exclusive lock on this resource. If a deferred update operation has any 
        /// exceptions during its last operation, they will be thrown here.  
        /// </summary>
        /// <param name="defer">Specifies weather the update should be deferred or awaited on the current call</param>
        /// <param name="state">A state parameter to be pased to the update function</param>
        /// <exception cref="ObjectDisposedException"></exception>
        public async ValueTask UpdateAndRelease(bool defer, TState state)
        {
            //Otherwise wait and update on the current thread
            try
            {
                //Dispose the update task
                LastUpdate?.Dispose();
                //Remove the referrence
                LastUpdate = null;
                //Run update on the current thread
                LastUpdate = await UpdateResource(defer, state).ConfigureAwait(true);
                //If the update is not deferred, await the results 
                if (!defer && LastUpdate != null)
                {
                    await LastUpdate.ConfigureAwait(true);
                }
            }
            finally
            {
                //Release the main lock
                _ = MainLock.Release();
            }
        }

        /// <summary>
        /// <para>
        /// When overrriden in a derived class, is responsible for updating the state of the instance if necessary.
        /// </para>
        /// <para>
        /// If the result of the update retruns a <see cref="Task"/> that represents the deferred update, the next call to <see cref="WaitOne"/> will 
        /// block until the operation completes and will throw any exceptions that occured
        /// </para>
        /// </summary>
        /// <param name="defer">true if the caller expects a resource update to be deferred, false if the caller expects the result of the update to be awaited</param>
        /// <param name="state">State parameter passed when releasing</param>
        /// <returns>A <see cref="Task"/> representing the async state update operation, or null if no async state update operation need's to be monitored</returns>
        protected abstract ValueTask<Task?> UpdateResource(bool defer, TState state);

        ///<inheritdoc/>
        protected override void Free()
        {
            //Dispose lock
            MainLock.Dispose();

            //Try to cleanup the last update
            if (LastUpdate != null && LastUpdate.IsCompletedSuccessfully)
            {
                LastUpdate.Dispose();
            }

            LastUpdate = null;
        }

    }
}