-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Implement Crystal::System::Time for win32 #5422
Conversation
@@ -0,0 +1,33 @@ | |||
lib LibC |
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.
Seems that all these definitions are in winbase.h
, so perhaps they should be in winbase.cr
?
alias Word = UInt16 | ||
alias WChar = UInt16 | ||
alias DWord = UInt32 | ||
alias BOOL = Int32 |
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.
These are all in winapi.cr
(which I actually think should be removed, would love to head the core team's opinion on this)
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 depends on where they're expected to be defined in windows includes. AFAIK there is no winapi.h
.
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.
DWORD
is declared in IntSafe.h
, WCHAR
in WinNT.h
, WORD
and BOOL
in WinDef.h
https://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx
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.
Well I don't think they should be in sys/types
(that's a header from the c99 standard), and if you are going to define these types in the PR, they should at least be removed from winapi.cr
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.
I'll put them in their respective locations.
daylight_bias : Long | ||
end | ||
|
||
fun get_time_zone_information = GetTimeZoneInformation(tz_info : TimeZoneInformation*) : DWord |
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 standard for functions (which I argued against) was to prefix them with an _
so that their case could be kept the same. See winapi.cr
.
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.
Okay, I didn't know that. I'll change it, allthough it seems more convenient to "crystalize" internal names. But there are pros and cons for both styles.
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.
You don't need the _
anymore, just use GetTimeZoneInformation
.
Opinion is to match syscalls 1:1. We don't have to go through crystal-like namings: they're not crystal methods, but Windows functions. Windows uses GetTimeZoneInformation
not get_time_zone_information
, let's respect that. It also makes automatically generating bindings (precomputed or on-the-fly) easier, and cleans up bindings by avoiding useless aliases.
src/crystal/system/win32/time.cr
Outdated
{(since_epoch / 10_000_000).to_i64 + WINDOWS_EPOCH_IN_SECONDS, since_epoch.remainder(10_000_000).to_i32 * 100} | ||
end | ||
|
||
@@performance_frequency = uninitialized Int64 |
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.
@@performance_frequency = begin
ret = LibC._QueryPerformanceFrequency(out frequency)
raise Errno.new("QueryPerformanceFrequency") if ret == 0
frequency
end
should work.
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.
Or just a plain:
ret = LibC.QueryPerformanceFrequency(out frequency)
raise Errno.new("QueryPerformanceFrequency") if ret == 0
@@performance_frequency = frequency
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.
Or should it be lazy evaluated like mach_timebase_info
for unix/macos?
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.
I would prefer @RX14's version so you dont have a ret
variable floating around
src/crystal/system/win32/time.cr
Outdated
LibC.get_system_time_as_file_time(out filetime) | ||
since_epoch = (filetime.high_date_time.to_u64 << 32) | filetime.low_date_time.to_u64 | ||
|
||
{(since_epoch / 10_000_000).to_i64 + WINDOWS_EPOCH_IN_SECONDS, since_epoch.remainder(10_000_000).to_i32 * 100} |
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.
would be nice to name these constants: FILETIME_TICKS_PER_SECOND
and NANOSECONDS_PER_FILETIME_TICK
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.
FILETIME
does not really represent abstract "ticks" but 100 nanosecond intervals. But I guess it's fine as a name and I don't have any better idea.
src/crystal/system/win32/time.cr
Outdated
raise Errno.new("query_performance_counter") | ||
end | ||
|
||
{ticks / @@performance_frequency, (ticks.remainder(1_000_000_000) * 1_000_000_000 / @@performance_frequency).to_i32} |
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.
ditto on the naming the constants.
You should be able to at least account for daylight time using |
end | ||
|
||
fun get_time_zone_information = GetTimeZoneInformation(tz_info : TimeZoneInformation*) : DWord | ||
fun get_system_time_as_file_time = GetSystemTimeAsFileTime(time : Filetime*) |
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.
Looks like you want GetSystemTimePreciseAsFileTime actually. It's fine that the precision is 100ns, it's fairly impossible to synchronise the system clock to be more accurate than that anyway.
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.
Hm, I don't know if it makes any difference. Both return a FILETIME
structure with 100 ns resolution and the values returned by both functions seem to be equally precise (at least on my system).
GetSystemTimePreciseAsFileTime
is only available on Windows 8+. I don't know which windows versions Crystal should support but it should easily be older ones.
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.
I've taken a look at Go's implementation and they use GetSystemTimePreciseAsFileTime
as well. Might need some more investigation about the actual difference.
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.
We should use the more precise one if it's there. The old one is remarkably inaccurate: see https://github.com/dotnet/coreclr/issues/5061.
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.
Should we have a fallback for API prior to Windows 8?
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.
Yes we should do the same library open and function pointer trick that the coreclr does in the pr linked from that issue.
Thanks so much for this PR! It's largely a great PR, although it seems that we need to document (and agree that we want) some more conventions related to windows naming. |
Yeah, I was just trying to fit everything as I was going along. It seems, a few conventions exists that I wasn't aware of. Some guidance would be really helpfull for ongoing efforts to implement windows bindings. |
About copmputing the UTC offset: It could be more sophisticated. We could even use |
Just match namings 1:1 for types, functions and struct fields as much as possible. If Windows uses UPPERCASE, then use UPPERCASE, if it uses CamelCase then use CamelCase, and so on. The only exception is lowercased type names, which must be transformed. Habit in bindings is to CamelCase them (e.g. Last but not least: binding functions can start with an uppercase letter since some versions back, so |
@ysbaddaden Thanks, that's the kind of guidance I was looking for 👍 |
fun _GetTimeZoneInformation = GetTimeZoneInformation(tz_info : TIME_ZONE_INFORMATION*) : DWord | ||
fun _GetSystemTimeAsFileTime = GetSystemTimeAsFileTime(time : FILETIME*) | ||
fun get_time_zone_information = GetTimeZoneInformation(tz_info : TIME_ZONE_INFORMATION*) : DWord | ||
fun get_system_time_as_file_time = GetSystemTimeAsFileTime(time : FILETIME*) |
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.
What's the difference with winbase.cr did I miss something ? Imo these modifications in this file are wrong
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.
Indeed, that file was commited on error.
8210796
to
3e250d0
Compare
3e250d0
to
5d41ce9
Compare
src/crystal/system/win32/time.cr
Outdated
def self.compute_utc_offset(seconds : Int64) : Int32 | ||
raise NotImplementedError.new("Crystal::System::Time.compute_utc_offset") | ||
ret = LibC.GetTimeZoneInformation(out zone_information) |
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.
Let's at least have a TODO
on ignoring seconds
.
5d41ce9
to
b2e3a00
Compare
I've moved the type declarations to files corresponding to the header names and added some comments. Specs are failing due to formatter error #5432 |
src/crystal/system/win32/time.cr
Outdated
@@ -10,6 +10,8 @@ module Crystal::System::Time | |||
NANOSECONDS_PER_SECOND = 1_000_000_000 | |||
FILETIME_TICKS_PER_SECOND = NANOSECONDS_PER_SECOND / NANOSECONDS_PER_FILETIME_TICK | |||
|
|||
# TODO: For now, this method returns the UTC offset currently in place. | |||
# A proper implementation is required once Crystal's Time Zone API has been revised (see issue #5324) |
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.
I'd prefer not to mention unmerged PRs as if it's a certainty they will be merged. Just say that this is not a complete implementation.
2f8d810
to
d18942a
Compare
src/crystal/system/win32/time.cr
Outdated
end | ||
|
||
def self.compute_utc_seconds_and_nanoseconds : {Int64, Int32} | ||
raise NotImplementedError.new("Crystal::System::Time.compute_utc_seconds_and_nanoseconds") | ||
LibC.GetSystemTimePreciseAsFileTime(out filetime) |
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.
In the future we should use windows's dlopen
equivalent to fallback to GetSystemTimeAsFileTime
. Can we either do that in this PR (see this) or leave a TODO?
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.
@RX14 that approach will be required if we want to support anything older than Windows 8 (function was introduced then).
Considering a few other places might require such approach, perhaps adding a TODO will be recommended.
Cheers.
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.
Should this check be at the callsite or in the bindings definition?
But I think I'd rather leave the implementation out of this PR.
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 dlopen and method exists check would want to be cached, yes. Leaving it for later should be fine.
We might want to create a helper API/macro for this eventually, not sure.
src/crystal/system/win32/time.cr
Outdated
def self.compute_utc_offset(seconds : Int64) : Int32 | ||
raise NotImplementedError.new("Crystal::System::Time.compute_utc_offset") | ||
ret = LibC.GetTimeZoneInformation(out zone_information) | ||
raise Errno.new("GetTimeZoneInformation") if ret == -1 |
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.
Looking at it again, shouldn't this be WinError
?
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.
Oh, I didn't know this class even existed... it's not used anywhere until now.
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.
Yes because windows has both errno
and GetLastError
for the C99 and win32 APIs respectively.
We shouldn't keep WinError
or expose these platform-specific APIs at 1.0 either.
@@ -0,0 +1,3 @@ | |||
lib LibC | |||
alias WChar = UInt16 |
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.
Isn't this WCHAR
?
@@ -0,0 +1,4 @@ | |||
lib LibC | |||
alias Word = UInt16 |
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.
Isn't this WORD
?
@@ -0,0 +1,3 @@ | |||
lib LibC | |||
alias DWord = UInt32 |
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.
Isn't this DWORD
?
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.
True. I was just copying that from winapi.cr
. Changing that will require to change it in that file as well. Not sure if that should be in this PR... WDYT?
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.
Ugh, leave it as-is for now and i'll send a PR to rip out winapi.cr
and redo that to the "new" specs.
This should probably wait for #5448. I'll rebase once it gets merged. |
We can merge in either order. I'd love a second review, or a lease to merge PRs which only touch code for windows with only one review. |
* `compute_utc_offset`: Always returns the **current** UTC offset for now. There seems to be no easy method to access UTC offset at an arbitrary point in time. * `compute_utc_seconds_and_nanoseconds`: Precision is currently only 100 nanoseconds because `FILE_TIME` has no higher resolution. * `monotonic`
17317af
to
2896cd5
Compare
Rebased on master including #5448 and squashed. |
* `compute_utc_offset`: Always returns the **current** UTC offset for now. There seems to be no easy method to access UTC offset at an arbitrary point in time. * `compute_utc_seconds_and_nanoseconds`: Precision is currently only 100 nanoseconds because `FILE_TIME` has no higher resolution. * `monotonic`
compute_utc_offset
: Always returns the current UTC offset for now.There seems to be no easy method to access UTC offset at an arbitrary
point in time.
The offset calculation will change anyway with Add time zones support #5324
compute_utc_seconds_and_nanoseconds
: Precision is currently only100 nanoseconds because
FILE_TIME
has no higher resolution.monotonic
: Seems to work as expected. Precision can very depending on performance frequency.