• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bitfaster / BitFaster.Caching / 15663525208

15 Jun 2025 01:07PM UTC coverage: 99.102% (-0.07%) from 99.168%
15663525208

Pull #686

github

web-flow
Merge 41e7272ea into aefdf9075
Pull Request #686: Avoid unobserved task exception in AsyncAtomicFactory

1123 of 1146 branches covered (97.99%)

Branch coverage included in aggregate %.

2 of 2 new or added lines in 2 files covered. (100.0%)

7 existing lines in 3 files now uncovered.

4838 of 4869 relevant lines covered (99.36%)

51749139.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.1
/BitFaster.Caching/Atomic/AsyncAtomicFactory.cs
1
using System;
2
using System.Collections.Generic;
3
using System.Diagnostics;
4
using System.Threading;
5
using System.Threading.Tasks;
6

7
namespace BitFaster.Caching.Atomic
8
{
9
    /// <summary>
10
    /// A class that provides simple, lightweight exactly once initialization for values
11
    /// stored in a cache.
12
    /// </summary>
13
    /// <typeparam name="K">The type of the key.</typeparam>
14
    /// <typeparam name="V">The type of the value.</typeparam>
15
    [DebuggerDisplay("IsValueCreated={IsValueCreated}, Value={ValueIfCreated}")]
16
    public sealed class AsyncAtomicFactory<K, V> : IEquatable<AsyncAtomicFactory<K, V>>
17
        where K : notnull
18
    {
19
        private Initializer? initializer;
20

21
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
22
        private V? value;
23

24
        /// <summary>
25
        /// Initializes a new instance of the <see cref="AsyncAtomicFactory{K, V}"/> class.
26
        /// </summary>
27
        public AsyncAtomicFactory()
91✔
28
        {
91✔
29
            initializer = new Initializer();
91✔
30
        }
91✔
31

32
        /// <summary>
33
        /// Initializes a new instance of the <see cref="AsyncAtomicFactory{K, V}"/> class with the
34
        /// specified value.
35
        /// </summary>
36
        /// <param name="value">The value.</param>
37
        public AsyncAtomicFactory(V value)
135✔
38
        {
135✔
39
            this.value = value;
135✔
40
        }
135✔
41

42
        /// <summary>
43
        /// Gets the value. If <see cref="IsValueCreated"/> is false, calling <see cref="GetValueAsync"/> will force initialization via the <paramref name="valueFactory"/> parameter.
44
        /// </summary>
45
        /// <param name="key">The key associated with the value.</param>
46
        /// <param name="valueFactory">The value factory to use to create the value when it is not initialized.</param>
47
        /// <returns>The value.</returns>
48
        public async ValueTask<V> GetValueAsync(K key, Func<K, Task<V>> valueFactory)
49
        {
72✔
50
            if (initializer == null)
72✔
51
            {
8✔
52
                return value!;
8✔
53
            }
54

55
            return await CreateValueAsync(key, new AsyncValueFactory<K, V>(valueFactory)).ConfigureAwait(false);
64✔
56
        }
44✔
57

58
        /// <summary>
59
        /// Gets the value. If <see cref="IsValueCreated"/> is false, calling <see cref="GetValueAsync{TArg}"/> will force initialization via the <paramref name="valueFactory"/> parameter.
60
        /// </summary>
61
        /// <typeparam name="TArg">The type of the value factory argument.</typeparam>
62
        /// <param name="key">The key associated with the value.</param>
63
        /// <param name="valueFactory">The value factory to use to create the value when it is not initialized.</param>
64
        /// <param name="factoryArgument">The value factory argument.</param>
65
        /// <returns>The value.</returns>
66
        public async ValueTask<V> GetValueAsync<TArg>(K key, Func<K, TArg, Task<V>> valueFactory, TArg factoryArgument)
67
        {
20✔
68
            if (initializer == null)
20✔
69
            {
4✔
70
                return value!;
4✔
71
            }
72

73
            return await CreateValueAsync(key, new AsyncValueFactoryArg<K, TArg, V>(valueFactory, factoryArgument)).ConfigureAwait(false);
16✔
74
        }
20✔
75

76
        /// <summary>
77
        /// Gets a value indicating whether the value has been initialized.
78
        /// </summary>
79
        public bool IsValueCreated => initializer == null;
248✔
80

81
        /// <summary>
82
        /// Gets the value if it has been initialized, else default.
83
        /// </summary>
84
        public V? ValueIfCreated
85
        {
86
            get
87
            {
113✔
88
                if (!IsValueCreated)
113✔
89
                {
4✔
90
                    return default;
4✔
91
                }
92

93
                return value;
109✔
94
            }
113✔
95
        }
96

97
        ///<inheritdoc/>
98
        public override bool Equals(object? obj)
99
        {
4✔
100
            return Equals(obj as AsyncAtomicFactory<K, V>);
4✔
101
        }
4✔
102

103
        ///<inheritdoc/>
104
        public bool Equals(AsyncAtomicFactory<K, V>? other)
105
        {
29✔
106
            if (other is null || !IsValueCreated || !other.IsValueCreated)
29✔
107
            {
15✔
108
                return false;
15✔
109
            }
110

111
            return EqualityComparer<V>.Default.Equals(ValueIfCreated, other.ValueIfCreated);
14✔
112
        }
29✔
113

114
        ///<inheritdoc/>
115
        public override int GetHashCode()
116
        {
8✔
117
            if (!IsValueCreated)
8✔
118
            {
4✔
119
                return 0;
4✔
120
            }
121

122
            return ValueIfCreated!.GetHashCode();
4✔
123
        }
8✔
124

125
        private async ValueTask<V> CreateValueAsync<TFactory>(K key, TFactory valueFactory) where TFactory : struct, IAsyncValueFactory<K, V>
126
        {
80✔
127
            var init = Volatile.Read(ref initializer);
80✔
128

129
            if (init != null)
80✔
130
            {
80✔
131
                value = await init.CreateValueAsync(key, valueFactory).ConfigureAwait(false);
80✔
132
                Volatile.Write(ref initializer, null);
52✔
133
            }
52✔
134

135
            return value!;
52✔
136
        }
52✔
137

138
        private class Initializer
139
        {
140
            private bool isInitialized;
141
            private Task<V>? valueTask;
142

143
            public async ValueTask<V> CreateValueAsync<TFactory>(K key, TFactory valueFactory) where TFactory : struct, IAsyncValueFactory<K, V>
144
            {
80✔
145
                var tcs = new TaskCompletionSource<V>(TaskCreationOptions.RunContinuationsAsynchronously);
80✔
146

147
                var synchronizedTask = DoubleCheck(tcs.Task);
80✔
148

149
                if (ReferenceEquals(synchronizedTask, tcs.Task))
80✔
150
                {
72✔
151
                    try
152
                    {
72✔
153
                        var value = await valueFactory.CreateAsync(key).ConfigureAwait(false);
72✔
154
                        tcs.SetResult(value);
48✔
155

156
                        return value;
48✔
157
                    }
158
                    catch (Exception ex)
24✔
159
                    {
24✔
160
                        Volatile.Write(ref isInitialized, false);
24✔
161
                        tcs.SetException(ex);
24✔
162

163
                        // always await the task to avoid unobserved task exceptions - normal case is that no other thread is waiting.
164
                        // this will re-throw the exception.
165
                        await tcs.Task.ConfigureAwait(false);
24✔
UNCOV
166
                    }
×
UNCOV
167
                }
×
168

169
                return await synchronizedTask.ConfigureAwait(false);
8✔
170
            }
52✔
171

172
#pragma warning disable CA2002 // Do not lock on objects with weak identity
173
            private Task<V> DoubleCheck(Task<V> value)
174
            {
80✔
175
                // Fast path
176
                if (Volatile.Read(ref isInitialized))
80✔
177
                {
8✔
178
                    return valueTask!;
8✔
179
                }
180

181
                lock (this)
72✔
182
                {
72✔
183
                    if (!isInitialized)
72✔
184
                    {
72✔
185
                        valueTask = value;
72✔
186
                        isInitialized = true;
72✔
187
                    }
72✔
188
                }
72✔
189

190
                return valueTask!;
72✔
191
            }
80✔
192
#pragma warning restore CA2002 // Do not lock on objects with weak identity
193
        }
194
    }
195
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc