Skip to content

Commit 84065a8

Browse files
authored
Introducing Time abstraction Part2 (down-level support) (#84235)
1 parent de5a04b commit 84065a8

File tree

14 files changed

+679
-32
lines changed

14 files changed

+679
-32
lines changed

src/libraries/Common/src/System/TimeProvider.cs

Lines changed: 85 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,15 @@ public abstract class TimeProvider
2929
/// <param name="timestampFrequency">Frequency of the values returned from <see cref="GetTimestamp"/> method.</param>
3030
protected TimeProvider(long timestampFrequency)
3131
{
32+
#if SYSTEM_PRIVATE_CORELIB
3233
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(timestampFrequency);
34+
#else
35+
if (timestampFrequency <= 0)
36+
{
37+
throw new ArgumentOutOfRangeException(nameof(timestampFrequency), timestampFrequency, SR.Format(SR.ArgumentOutOfRange_Generic_MustBeNonNegativeNonZero, nameof(timestampFrequency)));
38+
}
39+
#endif // SYSTEM_PRIVATE_CORELIB
40+
3341
TimestampFrequency = timestampFrequency;
3442
_timeToTicksRatio = (double)TimeSpan.TicksPerSecond / TimestampFrequency;
3543
}
@@ -41,6 +49,9 @@ protected TimeProvider(long timestampFrequency)
4149
/// </summary>
4250
public abstract DateTimeOffset UtcNow { get; }
4351

52+
private static readonly long s_minDateTicks = DateTime.MinValue.Ticks;
53+
private static readonly long s_maxDateTicks = DateTime.MaxValue.Ticks;
54+
4455
/// <summary>
4556
/// Gets a <see cref="DateTimeOffset"/> value that is set to the current date and time according to this <see cref="TimeProvider"/>'s
4657
/// notion of time based on <see cref="UtcNow"/>, with the offset set to the <see cref="LocalTimeZone"/>'s offset from Coordinated Universal Time (UTC).
@@ -53,9 +64,9 @@ public DateTimeOffset LocalNow
5364
TimeSpan offset = LocalTimeZone.GetUtcOffset(utcDateTime);
5465

5566
long localTicks = utcDateTime.Ticks + offset.Ticks;
56-
if ((ulong)localTicks > DateTime.MaxTicks)
67+
if ((ulong)localTicks > (ulong)s_maxDateTicks)
5768
{
58-
localTicks = localTicks < DateTime.MinTicks ? DateTime.MinTicks : DateTime.MaxTicks;
69+
localTicks = localTicks < s_minDateTicks ? s_minDateTicks : s_maxDateTicks;
5970
}
6071

6172
return new DateTimeOffset(localTicks, offset);
@@ -82,7 +93,15 @@ public DateTimeOffset LocalNow
8293
/// <exception cref="ArgumentNullException"><paramref name="timeZone"/> is null.</exception>
8394
public static TimeProvider FromLocalTimeZone(TimeZoneInfo timeZone)
8495
{
96+
#if SYSTEM_PRIVATE_CORELIB
8597
ArgumentNullException.ThrowIfNull(timeZone);
98+
#else
99+
if (timeZone is null)
100+
{
101+
throw new ArgumentNullException(nameof(timeZone));
102+
}
103+
#endif // SYSTEM_PRIVATE_CORELIB
104+
86105
return new SystemTimeProvider(timeZone);
87106
}
88107

@@ -155,7 +174,15 @@ private sealed class SystemTimeProvider : TimeProvider
155174
/// <inheritdoc/>
156175
public override ITimer CreateTimer(TimerCallback callback, object? state, TimeSpan dueTime, TimeSpan period)
157176
{
177+
#if SYSTEM_PRIVATE_CORELIB
158178
ArgumentNullException.ThrowIfNull(callback);
179+
#else
180+
if (callback is null)
181+
{
182+
throw new ArgumentNullException(nameof(callback));
183+
}
184+
#endif // SYSTEM_PRIVATE_CORELIB
185+
159186
return new SystemTimeProviderTimer(dueTime, period, callback, state);
160187
}
161188

@@ -165,7 +192,7 @@ public override ITimer CreateTimer(TimerCallback callback, object? state, TimeSp
165192
/// <inheritdoc/>
166193
public override DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
167194

168-
/// <summary>Thin wrapper for a <see cref="TimerQueueTimer"/>.</summary>
195+
/// <summary>Thin wrapper for a <see cref="Timer"/>.</summary>
169196
/// <remarks>
170197
/// We don't return a TimerQueueTimer directly as it implements IThreadPoolWorkItem and we don't
171198
/// want it exposed in a way that user code could directly queue the timer to the thread pool.
@@ -174,33 +201,85 @@ public override ITimer CreateTimer(TimerCallback callback, object? state, TimeSp
174201
/// </remarks>
175202
private sealed class SystemTimeProviderTimer : ITimer
176203
{
204+
#if SYSTEM_PRIVATE_CORELIB
177205
private readonly TimerQueueTimer _timer;
178-
206+
#else
207+
private readonly Timer _timer;
208+
#endif // SYSTEM_PRIVATE_CORELIB
179209
public SystemTimeProviderTimer(TimeSpan dueTime, TimeSpan period, TimerCallback callback, object? state)
180210
{
181211
(uint duration, uint periodTime) = CheckAndGetValues(dueTime, period);
212+
#if SYSTEM_PRIVATE_CORELIB
182213
_timer = new TimerQueueTimer(callback, state, duration, periodTime, flowExecutionContext: true);
214+
#else
215+
// We want to ensure the timer we create will be tracked as long as it is scheduled.
216+
// To do that, we call the constructor which track only the callback which will make the time to be tracked by the scheduler
217+
// then we call Change on the timer to set the desired duration and period.
218+
_timer = new Timer(_ => callback(state));
219+
_timer.Change(duration, periodTime);
220+
#endif // SYSTEM_PRIVATE_CORELIB
183221
}
184222

185223
public bool Change(TimeSpan dueTime, TimeSpan period)
186224
{
187225
(uint duration, uint periodTime) = CheckAndGetValues(dueTime, period);
188-
return _timer.Change(duration, periodTime);
226+
try
227+
{
228+
return _timer.Change(duration, periodTime);
229+
}
230+
catch (ObjectDisposedException)
231+
{
232+
return false;
233+
}
189234
}
190235

191236
public void Dispose() => _timer.Dispose();
192237

238+
239+
#if SYSTEM_PRIVATE_CORELIB
193240
public ValueTask DisposeAsync() => _timer.DisposeAsync();
241+
#else
242+
public ValueTask DisposeAsync()
243+
{
244+
_timer.Dispose();
245+
return default;
246+
}
247+
#endif // SYSTEM_PRIVATE_CORELIB
194248

195249
private static (uint duration, uint periodTime) CheckAndGetValues(TimeSpan dueTime, TimeSpan periodTime)
196250
{
197251
long dueTm = (long)dueTime.TotalMilliseconds;
252+
long periodTm = (long)periodTime.TotalMilliseconds;
253+
254+
#if SYSTEM_PRIVATE_CORELIB
198255
ArgumentOutOfRangeException.ThrowIfLessThan(dueTm, -1, nameof(dueTime));
199256
ArgumentOutOfRangeException.ThrowIfGreaterThan(dueTm, Timer.MaxSupportedTimeout, nameof(dueTime));
200257

201-
long periodTm = (long)periodTime.TotalMilliseconds;
202258
ArgumentOutOfRangeException.ThrowIfLessThan(periodTm, -1, nameof(periodTime));
203259
ArgumentOutOfRangeException.ThrowIfGreaterThan(periodTm, Timer.MaxSupportedTimeout, nameof(periodTime));
260+
#else
261+
const uint MaxSupportedTimeout = 0xfffffffe;
262+
263+
if (dueTm < -1)
264+
{
265+
throw new ArgumentOutOfRangeException(nameof(dueTime), dueTm, SR.Format(SR.ArgumentOutOfRange_Generic_MustBeGreaterOrEqual, nameof(dueTime), -1));
266+
}
267+
268+
if (dueTm > MaxSupportedTimeout)
269+
{
270+
throw new ArgumentOutOfRangeException(nameof(dueTime), dueTm, SR.Format(SR.ArgumentOutOfRange_Generic_MustBeLessOrEqual, nameof(dueTime), MaxSupportedTimeout));
271+
}
272+
273+
if (periodTm < -1)
274+
{
275+
throw new ArgumentOutOfRangeException(nameof(periodTm), periodTm, SR.Format(SR.ArgumentOutOfRange_Generic_MustBeGreaterOrEqual, nameof(periodTm), -1));
276+
}
277+
278+
if (periodTm > MaxSupportedTimeout)
279+
{
280+
throw new ArgumentOutOfRangeException(nameof(periodTm), periodTm, SR.Format(SR.ArgumentOutOfRange_Generic_MustBeLessOrEqual, nameof(periodTm), MaxSupportedTimeout));
281+
}
282+
#endif // SYSTEM_PRIVATE_CORELIB
204283

205284
return ((uint)dueTm, (uint)periodTm);
206285
}

0 commit comments

Comments
 (0)