-
Notifications
You must be signed in to change notification settings - Fork 523
[DESIGN] Damianedwards/date header perf #167
Conversation
Hi @DamianEdwards, I'm your friendly neighborhood .NET Foundation Pull Request Bot (You can call me DNFBOT). Thanks for your contribution! The agreement was validated by .NET Foundation and real humans are currently evaluating your PR. TTYL, DNFBOT; |
{ | ||
// Immediately assign the date value and start the timer again | ||
_dateValue = DateTime.UtcNow.ToString("r"); | ||
_dateValueTimer = new Timer(UpdateDateValue, state: null, dueTime: _timerInterval, period: _timerInterval); |
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.
Why not create the timer in the constructor and just do Change()
here? Then, _dateValueTimer != null
is an invariant and wouldn't need to be checked all the time.
Also: Why not use dueTime: TimeSpan.Zero
to run the handler immediately, which sets the _dateValue
immediately.
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.
Why not create the timer in the constructor and just do Change() here? Then, _dateValueTimer != null is an invariant and wouldn't need to be checked all the time.
_dateValueTimer == null
is the thing determining whether it needs to be switched on again. Could use boolean with _dateValueTimer.Change()
but that may make the logic more complicated?
Why not use dueTime: TimeSpan.Zero to run the handler immediately
That would queue the callback to run immediately; but doesn't call it as part of the Timer
constructor so the initialisation of the value would be in a race with the function return; which it would probably always loose. Though that would be fixed by the null coalescing in GetDateHeaderValue
as it would create its own value.
However, not sure switching it off during low request periods vs creating a string every second with a single timer has much benefit as the timer isn't necessarily a real "individual" timer? http://referencesource.microsoft.com/#mscorlib/system/threading/timer.cs,29
We use a single native timer, supplied by the VM, to schedule all managed timers in the AppDomain.
So in the spirit of
Why not create the timer in the constructor
Just switch the timer on in constructor and leave it ticking away until shut down? The side effect would be the GC has to clean up a string every second when the server isn't doing anything; but its happy doing that when the server is fully loaded; and the logic becomes much simpler in UpdateDateValue
and you can drop PumpTimer
and the locks altogether?
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.
Here is a different thought for the entire implementation that completely removes any need for synchronization:
http://docs.libuv.org/en/latest/timer.html
There would be one timer per Kestrel thread, but on the other hand the DateTime string wouldn't be shared between processors and every update is already in the "correct" processor's cache.
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 libuv timers are implemented in more or less the same way on linux and win as the .net timer; but you'd pay the added overhead of interop'ing the callback and the code for it would be less clear.
As Kestrel uses multiple (maybe?) libuv threads for socket IO then as you say, this would update in different cpu caches. However, as its a write once per second, read many times the processor cache would only have to refresh once per second (or once every 100k? executions) so it wouldn't be that significant; and it would put extra book keeping on the socket IO threads.
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.
There was concern over having any code running when the process is idle as it may interfere with mechanisms that perform operations on or in relation to idle processes. This design allows us to run the timer only when there are requests.
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.
Process suspension type things? If is a requirement LGTM
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.
Yep, that was one of the scenarios.
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.
That requirement makes this solution rather elegant 👍
Added extra bit to: #169 |
I like the design 👍 |
@DamianEdwards Merge or close? |
{ | ||
_Server = new[] { "Kestrel" }; | ||
_Date = new[] { DateTime.UtcNow.ToString("r") }; | ||
_Date = new[] { dateHeaderValue }; |
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 needs to be set at the end of the response rather than the beginning. Things like the error handling middleware will clear the response headers.
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.
Yeah, we'll do that as a separate change.
@muratg I'll close it and resubmit after rebasing and adding tests |
👍 |
757d158
to
098e04a
Compare
⌚ |
#207 will conflict with this. |
If they're set at the end the app can't see or change them... Should we change the middleware to preserve the server and re-apply the current time? |
Date is required and you can't trust misc middleware to handle it correctly. You can honor their value if they set it, but the server needs to set it at the end if it's missing. Server is optional, we don't really care if it gets cleared. |
- Doing it on each request is expensive - The Timer is started when the first request comes in and fires every second - Every request flips a bool so the Timer knows requests are coming in - The Timer stops itself after a period of no requests coming in (10 ticks) - #163
098e04a
to
4da048e
Compare
/// <summary> | ||
/// Abstracts the system clock to facilitate testing. | ||
/// </summary> | ||
public interface ISystemClock |
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.
internal
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.
Closed this one, look at #220 instead
Design review please.
@halter73 @lodejard @davidfowl @Tratcher