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

CalionVarduk / LfrlAnvil / 9243936461

26 May 2024 02:14PM UTC coverage: 96.92% (-0.002%) from 96.922%
9243936461

push

github

CalionVarduk
Reactive.Chrono:
- Populate readme.md;

14635 of 15454 branches covered (94.7%)

Branch coverage included in aggregate %.

41635 of 42604 relevant lines covered (97.73%)

34398.33 hits per line

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

97.99
/src/LfrlAnvil.Identifiers/IdentifierGenerator.cs
1
using System;
2
using System.Diagnostics.Contracts;
3
using System.Runtime.CompilerServices;
4
using System.Threading;
5
using LfrlAnvil.Chrono;
6
using LfrlAnvil.Exceptions;
7
using LfrlAnvil.Extensions;
8
using LfrlAnvil.Generators;
9
using LfrlAnvil.Identifiers.Exceptions;
10

11
namespace LfrlAnvil.Identifiers;
12

13
/// <inheritdoc />
14
/// <remarks>
15
/// Generators use the <see cref="Identifier.High"/> value as a representation of the time slice during which an <see cref="Identifier"/>
16
/// has been created and the <see cref="Identifier.Low"/> value as a sequential number unique within a single <see cref="Identifier.High"/>
17
/// value (or a time slice).
18
/// </remarks>
19
public sealed class IdentifierGenerator : IIdentifierGenerator
20
{
21
    private readonly ulong _maxHighValue;
22

23
    /// <summary>
24
    /// Creates a new <see cref="IdentifierGenerator"/> instance.
25
    /// </summary>
26
    /// <param name="timestamps"><see cref="ITimestampProvider"/> to use in this generator.</param>
27
    /// <param name="params">Optional parameters. See <see cref="IdentifierGeneratorParams"/> for more information.</param>
28
    /// <exception cref="ArgumentOutOfRangeException">
29
    /// When <see cref="IdentifierGeneratorParams.TimeEpsilon"/> is less than or equal to <see cref="Duration.Zero"/>
30
    /// or the current <see cref="Timestamp"/> returned by the <see cref="ITimestampProvider"/> instance
31
    /// is less than <see cref="Timestamp.Zero"/> or <see cref="IdentifierGeneratorParams.BaseTimestamp"/> is not between
32
    /// <see cref="Timestamp.Zero"/> and the current <see cref="Timestamp"/>.
33
    /// </exception>
34
    public IdentifierGenerator(ITimestampProvider timestamps, IdentifierGeneratorParams @params = default)
337✔
35
    {
36
        var timeEpsilon = @params.TimeEpsilon;
337✔
37
        Ensure.IsGreaterThan( timeEpsilon, Duration.Zero );
337✔
38

39
        var startTimestamp = timestamps.GetNow();
335✔
40
        Ensure.IsGreaterThanOrEqualTo( startTimestamp, Timestamp.Zero );
335✔
41

42
        var baseTimestamp = @params.BaseTimestamp;
333✔
43
        Ensure.IsInRange( baseTimestamp, Timestamp.Zero, startTimestamp );
333✔
44

45
        Timestamps = timestamps;
331✔
46
        BaseTimestamp = ConvertTimestamp( baseTimestamp, timeEpsilon );
331✔
47
        StartTimestamp = ConvertTimestamp( startTimestamp, timeEpsilon );
331✔
48
        TimeEpsilon = timeEpsilon;
331✔
49
        LowValueBounds = @params.LowValueBounds;
331✔
50
        LowValueOverflowStrategy = @params.LowValueOverflowStrategy;
331✔
51

52
        LastLowValue = LowValueBounds.Min - 1;
331✔
53
        LastHighValue = ConvertToHighValue( StartTimestamp.Subtract( BaseTimestamp ), timeEpsilon );
331✔
54

55
        var maxPossibleHighValueOffset = ConvertTimestamp( new Timestamp( DateTime.MaxValue ), timeEpsilon ).Subtract( BaseTimestamp );
331✔
56
        var maxExpectedHighValueOffset = ConvertToDuration( Identifier.MaxHighValue - 1, timeEpsilon );
331✔
57
        _maxHighValue = ConvertToHighValue( maxExpectedHighValueOffset.Min( maxPossibleHighValueOffset ), timeEpsilon );
331✔
58
    }
331✔
59

60
    /// <summary>
61
    /// <see cref="ITimestampProvider"/> instance used by this generator.
62
    /// </summary>
63
    public ITimestampProvider Timestamps { get; }
1,355✔
64

65
    /// <summary>
66
    /// <see cref="Timestamp"/> of the creation of this generator instance.
67
    /// </summary>
68
    public Timestamp StartTimestamp { get; }
348✔
69

70
    /// <inheritdoc />
71
    public Timestamp BaseTimestamp { get; }
2,093✔
72

73
    /// <summary>
74
    /// Specifies the range of available <see cref="Identifier.Low"/> values for identifiers created by this generator.
75
    /// </summary>
76
    public Bounds<ushort> LowValueBounds { get; }
1,808✔
77

78
    /// <summary>
79
    /// Specifies the time resolution of this generator.
80
    /// </summary>
81
    public Duration TimeEpsilon { get; }
1,534✔
82

83
    /// <summary>
84
    /// Specifies <see cref="LowValueOverflowStrategy"/> used by this generator.
85
    /// </summary>
86
    public LowValueOverflowStrategy LowValueOverflowStrategy { get; }
97✔
87

88
    /// <summary>
89
    /// Specifies the last <see cref="Identifier.High"/> value of an <see cref="Identifier"/> created by this generator.
90
    /// </summary>
91
    public ulong LastHighValue { get; private set; }
3,006✔
92

93
    /// <summary>
94
    /// Specifies the last <see cref="Identifier.Low"/> value of an <see cref="Identifier"/> created by this generator.
95
    /// </summary>
96
    public int LastLowValue { get; private set; }
3,933✔
97

98
    /// <summary>
99
    /// <see cref="Timestamp"/> of the last <see cref="Identifier"/> created by this generator.
100
    /// </summary>
101
    public Timestamp LastTimestamp => BaseTimestamp.Add( ConvertToDuration( LastHighValue, TimeEpsilon ) );
24✔
102

103
    /// <summary>
104
    /// Maximum possible <see cref="Timestamp"/> of an <see cref="Identifier"/> created by this generator.
105
    /// </summary>
106
    public Timestamp MaxTimestamp => BaseTimestamp.Add( ConvertToDuration( _maxHighValue, TimeEpsilon ) );
17✔
107

108
    /// <summary>
109
    /// Specifies the maximum number of high values that this generator can use to create new identifiers.
110
    /// </summary>
111
    /// <remarks>See <see cref="Identifier.High"/> for more information.</remarks>
112
    public ulong HighValuesLeft
113
    {
114
        get
115
        {
116
            var highValue = GetCurrentHighValue();
22✔
117
            if ( highValue > _maxHighValue )
22✔
118
                return 0;
1✔
119

120
            if ( highValue > LastHighValue )
21✔
121
                return _maxHighValue - highValue + 1;
6✔
122

123
            var lowValuesLeft = LowValueBounds.Max - LastLowValue;
15✔
124
            if ( lowValuesLeft > 0 )
15✔
125
                return _maxHighValue - LastHighValue + 1;
12✔
126

127
            return _maxHighValue - LastHighValue;
3✔
128
        }
129
    }
130

131
    /// <summary>
132
    /// Specifies the maximum number of identifiers this generator can still create for the current time slice without having
133
    /// to resolve low value overflow.
134
    /// </summary>
135
    /// <remarks>See <see cref="Identifier.Low"/> for more information.</remarks>
136
    public int LowValuesLeft
137
    {
138
        get
139
        {
140
            var highValue = GetCurrentHighValue();
14✔
141
            if ( highValue > _maxHighValue )
14✔
142
                return 0;
1✔
143

144
            if ( highValue > LastHighValue )
13✔
145
                return LowValueBounds.Max - LowValueBounds.Min + 1;
3✔
146

147
            return LowValueBounds.Max - LastLowValue;
10✔
148
        }
149
    }
150

151
    /// <summary>
152
    /// Specifies the maximum number of identifiers that this generate can still create.
153
    /// </summary>
154
    public ulong ValuesLeft
155
    {
156
        get
157
        {
158
            var highValue = GetCurrentHighValue();
45✔
159
            if ( highValue > _maxHighValue )
45✔
160
                return 0;
2✔
161

162
            var lowValuesPerHighValue = ( ulong )(LowValueBounds.Max - LowValueBounds.Min + 1);
43✔
163

164
            if ( highValue > LastHighValue )
43✔
165
            {
166
                var highValuesLeft = _maxHighValue - highValue + 1;
13✔
167
                return highValuesLeft * lowValuesPerHighValue;
13✔
168
            }
169

170
            var futureHighValuesLeft = _maxHighValue - LastHighValue;
30✔
171
            var lowValuesLeft = ( ulong )(LowValueBounds.Max - LastLowValue);
30✔
172
            return futureHighValuesLeft * lowValuesPerHighValue + lowValuesLeft;
30✔
173
        }
174
    }
175

176
    /// <summary>
177
    /// Specifies whether or not this generator can still create identifiers.
178
    /// </summary>
179
    public bool IsOutOfValues => ValuesLeft <= 0;
2✔
180

181
    /// <summary>
182
    /// Generates a new <see cref="Identifier"/>.
183
    /// </summary>
184
    /// <returns>New <see cref="Identifier"/> instance.</returns>
185
    /// <exception cref="IdentifierGenerationException">When generator has failed to create a new <see cref="Identifier"/>.</exception>
186
    /// <remarks>
187
    /// Generators will fail to create an <see cref="Identifier"/> when they are completely out of values
188
    /// or when low value overflow has occurred and the current <see cref="LowValueOverflowStrategy"/>
189
    /// is equal to <see cref="LowValueOverflowStrategy.Forbidden"/>.
190
    /// </remarks>
191
    [MethodImpl( MethodImplOptions.AggressiveInlining )]
192
    public Identifier Generate()
193
    {
194
        if ( ! TryGenerate( out var id ) )
1,132✔
195
            ExceptionThrower.Throw( new IdentifierGenerationException() );
21✔
196

197
        return id;
1,111✔
198
    }
199

200
    /// <summary>
201
    /// Attempts to generate a new <see cref="Identifier"/>.
202
    /// </summary>
203
    /// <param name="result"><b>out</b> parameter that returns generated <see cref="Identifier"/> if successful.</param>
204
    /// <returns><b>true</b> if an <see cref="Identifier"/> has been generated successfully, otherwise <b>false</b>.</returns>
205
    /// <remarks>
206
    /// Generators will fail to create an <see cref="Identifier"/> when they are completely out of values
207
    /// or when low value overflow has occurred and the current <see cref="LowValueOverflowStrategy"/>
208
    /// is equal to <see cref="LowValueOverflowStrategy.Forbidden"/>.
209
    /// </remarks>
210
    public bool TryGenerate(out Identifier result)
211
    {
212
        var highValue = GetCurrentHighValue();
1,224✔
213

214
        if ( highValue > LastHighValue )
1,224✔
215
        {
216
            if ( highValue > _maxHighValue )
63✔
217
            {
218
                result = default;
2✔
219
                return false;
2✔
220
            }
221

222
            result = CreateNextId( highValue );
61✔
223
            return true;
61✔
224
        }
225

226
        if ( LastLowValue < LowValueBounds.Max )
1,161✔
227
        {
228
            result = CreateNextId();
1,081✔
229
            return true;
1,081✔
230
        }
231

232
        highValue = LowValueOverflowStrategy switch
80✔
233
        {
80✔
234
            LowValueOverflowStrategy.AddHighValue => LastHighValue + 1,
25✔
235
            LowValueOverflowStrategy.SpinWait => GetHighValueBySpinWait(),
25✔
236
            LowValueOverflowStrategy.Sleep => GetHighValueBySleep( highValue ),
25✔
237
            _ => _maxHighValue + 1
5✔
238
        };
80✔
239

240
        if ( highValue > _maxHighValue )
80✔
241
        {
242
            result = default;
41✔
243
            return false;
41✔
244
        }
245

246
        result = CreateNextId( highValue );
39✔
247
        return true;
39✔
248
    }
249

250
    /// <inheritdoc />
251
    [Pure]
252
    [MethodImpl( MethodImplOptions.AggressiveInlining )]
253
    public Timestamp GetTimestamp(Identifier id)
254
    {
255
        var offset = ConvertToDuration( id.High, TimeEpsilon );
18✔
256
        return BaseTimestamp.Add( offset );
18✔
257
    }
258

259
    /// <summary>
260
    /// Calculates maximum possible number of identifiers that this generator can produce in the given time,
261
    /// without having to resort to <see cref="LowValueOverflowStrategy"/> resolution.
262
    /// </summary>
263
    /// <param name="duration">Time to calculate this generator's throughput for.</param>
264
    /// <returns>Maximum possible number of identifiers that this generator can produce in the given time.</returns>
265
    [Pure]
266
    public ulong CalculateThroughput(Duration duration)
267
    {
268
        duration = duration.Max( Duration.Zero );
26✔
269
        var fullHighValueCount = ConvertToHighValue( duration, TimeEpsilon );
26✔
270
        var fullLowValueCount = ( ulong )(LowValueBounds.Max - LowValueBounds.Min + 1);
26✔
271
        var lowValueRemainderRatio = ( double )(duration.Ticks % TimeEpsilon.Ticks) / TimeEpsilon.Ticks;
26✔
272
        var remainingLowValueCount = ( ulong )Math.Truncate( lowValueRemainderRatio * fullLowValueCount );
26✔
273
        return fullHighValueCount * fullLowValueCount + remainingLowValueCount;
26✔
274
    }
275

276
    [MethodImpl( MethodImplOptions.AggressiveInlining )]
277
    private ulong GetCurrentHighValue()
278
    {
279
        var elapsedTime = Timestamps.GetNow() - BaseTimestamp;
1,355✔
280
        return ConvertToHighValue( elapsedTime, TimeEpsilon );
1,355✔
281
    }
282

283
    [MethodImpl( MethodImplOptions.AggressiveInlining )]
284
    private ulong GetHighValueBySpinWait()
285
    {
286
        var spinWait = new SpinWait();
25✔
287
        var highValue = GetCurrentHighValue();
25✔
288
        while ( highValue <= LastHighValue )
25!
289
        {
290
            spinWait.SpinOnce();
×
291
            highValue = GetCurrentHighValue();
×
292
        }
293

294
        return highValue;
25✔
295
    }
296

297
    [MethodImpl( MethodImplOptions.AggressiveInlining )]
298
    private ulong GetHighValueBySleep(ulong highValue)
299
    {
300
        do
301
        {
302
            var timeout = ConvertToDuration( LastHighValue - highValue, TimeEpsilon ).Max( Duration.FromMilliseconds( 1 ) );
25✔
303
            Thread.Sleep( ( int )Math.Ceiling( timeout.TotalMilliseconds ) );
25✔
304
            highValue = GetCurrentHighValue();
25✔
305
        }
306
        while ( highValue <= LastHighValue );
25✔
307

308
        return highValue;
25✔
309
    }
310

311
    [MethodImpl( MethodImplOptions.AggressiveInlining )]
312
    private Identifier CreateNextId(ulong highValue)
313
    {
314
        LastHighValue = highValue;
100✔
315
        LastLowValue = LowValueBounds.Min;
100✔
316
        return new Identifier( highValue, unchecked( ( ushort )LastLowValue ) );
100✔
317
    }
318

319
    [MethodImpl( MethodImplOptions.AggressiveInlining )]
320
    private Identifier CreateNextId()
321
    {
322
        return new Identifier( LastHighValue, unchecked( ( ushort )++LastLowValue ) );
1,081✔
323
    }
324

325
    [Pure]
326
    [MethodImpl( MethodImplOptions.AggressiveInlining )]
327
    private static Timestamp ConvertTimestamp(Timestamp timestamp, Duration epsilon)
328
    {
329
        var ticksToSubtract = timestamp.UnixEpochTicks % epsilon.Ticks;
993✔
330
        return timestamp.Subtract( Duration.FromTicks( ticksToSubtract ) );
993✔
331
    }
332

333
    [Pure]
334
    [MethodImpl( MethodImplOptions.AggressiveInlining )]
335
    private static ulong ConvertToHighValue(Duration elapsedTime, Duration epsilon)
336
    {
337
        return unchecked( ( ulong )(elapsedTime.Ticks / epsilon.Ticks) );
2,043✔
338
    }
339

340
    [Pure]
341
    [MethodImpl( MethodImplOptions.AggressiveInlining )]
342
    private static Duration ConvertToDuration(ulong highValue, Duration epsilon)
343
    {
344
        return Duration.FromTicks( ( long )highValue * epsilon.Ticks );
415✔
345
    }
346

347
    object IGenerator.Generate()
348
    {
349
        return Generate();
4✔
350
    }
351

352
    bool IGenerator.TryGenerate(out object? result)
353
    {
354
        if ( TryGenerate( out var internalResult ) )
2✔
355
        {
356
            result = internalResult;
1✔
357
            return true;
1✔
358
        }
359

360
        result = null;
1✔
361
        return false;
1✔
362
    }
363
}
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