New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Client] Time calculations using DateTime are susceptible to produce erroneous values #2546
base: master
Are you sure you want to change the base?
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #2546 +/- ##
==========================================
+ Coverage 54.62% 54.65% +0.02%
==========================================
Files 342 342
Lines 65082 65081 -1
Branches 13350 13350
==========================================
+ Hits 35553 35571 +18
+ Misses 25661 25646 -15
+ Partials 3868 3864 -4 ☔ View full report in Codecov by Sentry. |
…dard into time-calculation-error-on-system-time-change
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is DateTime.UtcNow robust to System clock changes and to VM Time Drift (were VM clock drifts from correct time due to ex: CPU oversubscriptions) ?
Shouldn't the following places also use the monoton Environment.TickCount ?
delay calculation:
TimeSpan delay = endTime - DateTime.UtcNow; |
state.Timestamp:
state.Timestamp = DateTime.UtcNow; |
Timestamp member:
public DateTime Timestamp; |
elapsed:
int elapsed = (int)DateTime.UtcNow.Subtract(reconnectStart).TotalMilliseconds; |
Timeout in DoReconectAsync:
TimeSpan timeout = m_session.LastKeepAliveTime.AddMilliseconds(m_session.SessionTimeout) - DateTime.UtcNow; |
@@ -748,7 +748,7 @@ public bool KeepAliveStopped | |||
{ | |||
get | |||
{ | |||
TimeSpan delta = TimeSpan.FromTicks(DateTime.UtcNow.Ticks - Interlocked.Read(ref m_lastKeepAliveTime)); | |||
TimeSpan delta = TimeSpan.FromTicks(HiResClock.TickCount - Interlocked.Read(ref m_lastKeepAliveTime)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
with the move to TickCount which is an int, the m_lastKeepAliveTime can also be an int and the interlocked is not necessary anymore. .NET guarantees atomic operation for the datatype.
@@ -4002,7 +4002,7 @@ protected virtual void OnKeepAlive(ServerState currentState, DateTime currentTim | |||
/// </summary> | |||
protected virtual bool OnKeepAliveError(ServiceResult result) | |||
{ | |||
long delta = DateTime.UtcNow.Ticks - Interlocked.Read(ref m_lastKeepAliveTime); | |||
long delta = HiResClock.TickCount - Interlocked.Read(ref m_lastKeepAliveTime); | |||
|
|||
Utils.LogInfo( | |||
"KEEP ALIVE LATE: {0}s, EndpointUrl={1}, RequestCount={2}/{3}", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this calculation is now based on millisecond counter, not ticks, and needs to be fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the calculation and datytype change is not quite right.
I am concerned about the use of HiResClock.TickCount. It will wrap over in less than 25 days after the computer is started and cause problems, and there is no code that checks for it. Perhaps HiResClock.TickCount64 was intended. I also suggest to mark HiResClock.TickCount with [Obsolete] with appropriate message, which would force the developers to reconsider every place where it is used. |
I understand the concern, but are time intervals involved in the calculations greater than 24.9 days empirically possible? Do you see any such situations? |
@mrsuciu, what you wrote about "automatic" correct handling of wrap-around would be true if you stayed within the same integer type (Int32, here). The typical operation is subtracting some old time from the new time, and comparing that against some timeout value. With Environment.TickCount, which returns Int32, this operation will work well (even with wrap-around), if it is made as Int32 subtraction. But, this is not the case in the code I am concerned about. Here, m_lastKeepAliveTime is beig subtracted effectively from what is Environment.TickCount, and the m_lastKeepAliveTime is declared as long (Int64), therefore the subtraction will be performed as Int64 subtraction, and it won't give the expected result with wrap-around, I think. |
I see what you mean now, but that problem was signaled previously also by @mregen here: #2546 (comment) so I thought you were generally concerned about the use of HiResClock.TickCount as opposed to HiResClock.TickCount64 having also the implication of not checking the "wrap-around". |
not quite ready |
Proposed changes
Time calculations using DateTime are susceptible to produce erroneous values when System Time is changed during the calculation process. Computing time should be done with a monotonic clock such as existing HiResClock.
Related Issues
Types of changes
What types of changes does your code introduce?
Put an
x
in the boxes that apply. You can also fill these out after creating the PR.Checklist
Put an
x
in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code.Further comments
If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did and what alternatives you considered, etc...