diff --git a/NEWS b/NEWS index 2451186..b431d7e 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,8 @@ PHP NEWS ?? ??? 2014, PHP 5.4.33 - Core: + . Fixed microsecond resolution and accuracy in time functions on Windows + versions before 8/Server 2012. (Matt) . Fixed bug #47358 (glob returns error, should be empty array()). (Pierre) - OpenSSL: diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index 9a9df30..390e931 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -3528,6 +3528,10 @@ PHP_MINIT_FUNCTION(basic) /* {{{ */ #endif #endif +#ifdef PHP_WIN32 + PHP_MINIT(win32_core_globals)(INIT_FUNC_ARGS_PASSTHRU); +#endif + BG(incomplete_class) = incomplete_class_entry = php_create_incomplete_class(TSRMLS_C); REGISTER_LONG_CONSTANT("CONNECTION_ABORTED", PHP_CONNECTION_ABORTED, CONST_CS | CONST_PERSISTENT); diff --git a/ext/standard/uniqid.c b/ext/standard/uniqid.c index a87bdb9..f460d4d 100644 --- a/ext/standard/uniqid.c +++ b/ext/standard/uniqid.c @@ -70,7 +70,7 @@ PHP_FUNCTION(uniqid) #endif gettimeofday((struct timeval *) &tv, (struct timezone *) NULL); sec = (int) tv.tv_sec; - usec = (int) (tv.tv_usec % 0x100000); + usec = (int) tv.tv_usec; /* The max value usec can have is 0xF423F, so we use only five hex * digits for usecs. diff --git a/main/php_variables.c b/main/php_variables.c index be6448e..ec9fff6 100644 --- a/main/php_variables.c +++ b/main/php_variables.c @@ -584,7 +584,7 @@ static inline void php_register_server_variables(TSRMLS_D) Z_DVAL(request_time_float) = sapi_get_request_time(TSRMLS_C); php_register_variable_ex("REQUEST_TIME_FLOAT", &request_time_float, array_ptr TSRMLS_CC); Z_TYPE(request_time_long) = IS_LONG; - Z_LVAL(request_time_long) = zend_dval_to_lval(Z_DVAL(request_time_float)); + Z_LVAL(request_time_long) = (long) Z_DVAL(request_time_float); php_register_variable_ex("REQUEST_TIME", &request_time_long, array_ptr TSRMLS_CC); } diff --git a/win32/globals.c b/win32/globals.c index 0f88e4c..6a204cf 100644 --- a/win32/globals.c +++ b/win32/globals.c @@ -21,6 +21,7 @@ #include "php.h" #include "php_win32_globals.h" #include "syslog.h" +#include "time.h" #ifdef ZTS PHPAPI int php_win32_core_globals_id; @@ -54,18 +55,58 @@ void php_win32_core_globals_dtor(void *vg TSRMLS_DC) } -PHP_RSHUTDOWN_FUNCTION(win32_core_globals) +PHP_MINIT_FUNCTION(win32_core_globals) { - php_win32_core_globals *wg = -#ifdef ZTS - ts_resource(php_win32_core_globals_id) -#else - &the_php_win32_core_globals -#endif - ; + HMODULE kernel32 = GetModuleHandle("kernel32.dll"); + + /* Available since Win 8/Server 2012 */ + getsystemtimeprecise = (getsystemtime_func_t) GetProcAddress(kernel32, "GetSystemTimePreciseAsFileTime"); + + if (!getsystemtimeprecise) { + DWORD TimeAdj; + BOOL TimeAdjDisabled; + LARGE_INTEGER freq; + + /* Frequency never changes + * No need to check return value since XP + */ + QueryPerformanceFrequency(&freq); + + filetime_per_count = 10000000 / (double) freq.QuadPart; + + /* If TimeAdjDisabled is FALSE and TimeAdj != update_rate (constant), + * with clock advancing slower/faster than normal, it complicates + * things! We'll just ignore those cases for Performance Counter + * time estimations, since anything reasonable shouldn't really have + * much effect over short periods. Otherwise we'd need to get TimeAdj + * when estimate is out-of-range, and/or update filetime_per_count... + * + * It's also less "finely" adjustable (less likely?) on Vista/7 than XP + * + * Additionally, and separately, if the system timer resolution is + * non-default (NtQueryTimerResolution), that will affect when the + * clock updates and by how much. For example, consider a 9ms timer + * with a 10ms update_rate. We'll also ignore these cases! :-P + * + * Much more info at http://www.windowstimestamp.com/description + */ + GetSystemTimeAdjustment(&TimeAdj, &time_update_rate, &TimeAdjDisabled); + } + + return SUCCESS; +} + +PHP_RSHUTDOWN_FUNCTION(win32_core_globals) +{ closelog(); + /* It would be fine to keep perf_count_time once set (still accurate next use) + * But we don't want the next REQUEST_TIME[_FLOAT] to be greater, so reset + * and force real clock to be used first time instead of interpolating + */ + PW32G(perf_count_time) = 0; + return SUCCESS; } diff --git a/win32/php_win32_globals.h b/win32/php_win32_globals.h index 6bf9ba5..1facffc 100644 --- a/win32/php_win32_globals.h +++ b/win32/php_win32_globals.h @@ -38,6 +38,8 @@ struct _php_win32_core_globals { char *log_header; HANDLE log_source; + __int64 perf_count_time; + HKEY registry_key; HANDLE registry_event; HashTable *registry_directories; @@ -45,6 +47,7 @@ struct _php_win32_core_globals { void php_win32_core_globals_ctor(void *vg TSRMLS_DC); void php_win32_core_globals_dtor(void *vg TSRMLS_DC); +PHP_MINIT_FUNCTION(win32_core_globals); PHP_RSHUTDOWN_FUNCTION(win32_core_globals); #endif diff --git a/win32/time.c b/win32/time.c index 7553974..04cf4b6 100644 --- a/win32/time.c +++ b/win32/time.c @@ -25,71 +25,80 @@ #include #include "php_win32_globals.h" -typedef VOID (WINAPI *MyGetSystemTimeAsFileTime)(LPFILETIME lpSystemTimeAsFileTime); +getsystemtime_func_t getsystemtimeprecise = NULL; +double filetime_per_count; +DWORD time_update_rate = 0; -static MyGetSystemTimeAsFileTime get_time_func(void) +PHPAPI int gettimeofday(struct timeval *tv, struct timezone *tz) { - MyGetSystemTimeAsFileTime timefunc = NULL; - HMODULE hMod = LoadLibrary("kernel32.dll"); - - if (hMod) { - /* Max possible resolution <1us, win8/server2012 */ - timefunc = (MyGetSystemTimeAsFileTime)GetProcAddress(hMod, "GetSystemTimePreciseAsFileTime"); + FILETIME ft; + ULARGE_INTEGER tmp; + unsigned __int64 ts; - if(!timefunc) { - /* 100ns blocks since 01-Jan-1641 */ - timefunc = (MyGetSystemTimeAsFileTime)GetProcAddress(hMod, "GetSystemTimeAsFileTime"); + /* Get the time, if they want it */ + if (tv) { + if (getsystemtimeprecise) { + getsystemtimeprecise(&ft); + + /* + * Do not cast a pointer to a FILETIME structure to either a + * ULARGE_INTEGER* or __int64* value because it can cause alignment faults on 64-bit Windows. + * via http://technet.microsoft.com/en-us/library/ms724284(v=vs.85).aspx + */ + tmp.HighPart = ft.dwHighDateTime; + tmp.LowPart = ft.dwLowDateTime; + ts = tmp.QuadPart; + } else { + LARGE_INTEGER counter; + unsigned __int64 count_filetime, target; + TSRMLS_FETCH(); + + /* No need to check return value since XP */ + QueryPerformanceCounter(&counter); + + /* double will lose counter precision after a relatively short + * time (e.g. a month) at higher frequencies, but we only "need" + * 10 million/sec for time, so double's 53 bits are enough for + * over 28 years (actually only need usecs, so 10x longer) + */ + count_filetime = (__int64)((double) counter.QuadPart * filetime_per_count); + + ts = PW32G(perf_count_time) + count_filetime; + + /* The returned time will be behind the real time, by some amount, + * immediately after each update. So it's probably better to get the + * time AFTER the counter, in case it updates between the 2 calls, and + * have counter estimates a *hair* ahead of REAL time (not only returned) + * instead of the MAX amount behind if calls were reversed + */ + GetSystemTimeAsFileTime(&ft); + + tmp.HighPart = ft.dwHighDateTime; + tmp.LowPart = ft.dwLowDateTime; + target = tmp.QuadPart; + + if (ts < target - time_update_rate) { + if (PW32G(perf_count_time)) { + ts = target - time_update_rate; + } else { + ts = target; /* First call this request */ + } + + PW32G(perf_count_time) = ts - count_filetime; + } else if (ts > target + time_update_rate) { + ts = target + time_update_rate; + PW32G(perf_count_time) = ts - count_filetime; + } } - } - - return timefunc; -} -int getfilesystemtime(struct timeval *tv) -{ - FILETIME ft; - unsigned __int64 ff = 0; - MyGetSystemTimeAsFileTime timefunc; - ULARGE_INTEGER fft; + ts -= 11644473600Ui64 * 10000000; /* Convert to Unix epoch */ - timefunc = get_time_func(); - if (timefunc) { - timefunc(&ft); - } else { - GetSystemTimeAsFileTime(&ft); + tv->tv_sec = (long)(ts / 10000000); + /* Divide 32-bit % result by 10 again for microseconds, saving a 64-bit div operation + * Division above and modulo with same divisor is optimized to 1 operation */ + tv->tv_usec = (long)(ts % 10000000) / 10; } - /* - * Do not cast a pointer to a FILETIME structure to either a - * ULARGE_INTEGER* or __int64* value because it can cause alignment faults on 64-bit Windows. - * via http://technet.microsoft.com/en-us/library/ms724284(v=vs.85).aspx - */ - fft.HighPart = ft.dwHighDateTime; - fft.LowPart = ft.dwLowDateTime; - ff = fft.QuadPart; - - ff /= 10Ui64; /* convert to microseconds */ - ff -= 11644473600000000Ui64; /* convert to unix epoch */ - - tv->tv_sec = (long)(ff / 1000000Ui64); - tv->tv_usec = (long)(ff % 1000000Ui64); - - return 0; -} - -PHPAPI int gettimeofday(struct timeval *time_Info, struct timezone *timezone_Info) -{ - /* Get the time, if they want it */ - if (time_Info != NULL) { - getfilesystemtime(time_Info); - } - /* Get the timezone, if they want it */ - if (timezone_Info != NULL) { - _tzset(); - timezone_Info->tz_minuteswest = _timezone; - timezone_Info->tz_dsttime = _daylight; - } - /* And return */ return 0; } diff --git a/win32/time.h b/win32/time.h index d5d86eb..63516b8 100644 --- a/win32/time.h +++ b/win32/time.h @@ -42,7 +42,13 @@ struct timespec #define ITIMER_PROF 2 /*generates sigprof */ /* Prototype stuff ********************************************************** */ -PHPAPI extern int gettimeofday(struct timeval *time_Info, struct timezone *timezone_Info); +typedef VOID (WINAPI *getsystemtime_func_t)(LPFILETIME lpSystemTimeAsFileTime); + +extern getsystemtime_func_t getsystemtimeprecise; +extern double filetime_per_count; +extern DWORD time_update_rate; + +PHPAPI extern int gettimeofday(struct timeval *tv, struct timezone *tz); /* setitimer operates at 100 millisecond resolution */ PHPAPI extern int setitimer(int which, const struct itimerval *value,