Commit eb589393 authored by Davi Arnaut's avatar Davi Arnaut

Bug#42054: SELECT CURDATE() is returning bad value

The problem from a user point of view was that on Solaris the
time related functions (e.g. NOW(), SYSDATE(), etc) would always
return a fixed time.

This bug was happening due to a logic in the time retrieving
wrapper function which would only call the time() function every
half second. This interval between calls would be calculated
using the gethrtime() and the logic relied on the fact that time
returned by it is monotonic.

Unfortunately, due to bugs in the gethrtime() implementation,
there are some cases where the time returned by it can drift
(See Solaris bug id 6600939), potentially causing the interval
calculation logic to fail.

Since newer versions of Solaris (10+) have alleviated the
performance degradation associated with time(2), the solution is
to simply directly rely on time() at each invocation.

This simplification has an upside that it allows us to eliminate
a lock which was used to control access to the variables used
to track the half second interval, thus improving the overall
scalability of timekeeping related functions (e.g. NOW()).

Benchmarks runs have shown no significant degradation associated
with this change. With this, there are actually improvements in
performance for cases involving many connections.

In summary, the changes introduced by this patch are:

a) my_time() and my_micro_time_and_time() no longer use gethrtime().
   Instead, time() and gettimeofdate() are used correspondingly.

b) my_micro_time() is changed to not use gethrtime() so as to
   have the same time source as my_micro_time_and_time().
   There shouldn't be any performance impact from this change
   since this function is used only a few times during statement
   execution and, on Solaris, gettimeofday() shows acceptable
   performance.

mysys/my_getsystime.c:
  Use time() even if gethrtime() is available. Remove logic which
  relied on gethrtime() to only call time() every half second.
  Since gethrtime() is not used anymore, also remove it from
  my_micro_time() to keep a common time source.
  
  Also, function comments are cleaned up (fixed typos and wrong
  information) and converted to doxygen.
mysys/my_thr_init.c:
  Remove mutex which is no longer used.
mysys/mysys_priv.h:
  Remove mutex which is no longer used.
parent f8697642
......@@ -31,10 +31,6 @@ select count(name) from mutex_instances
where name like "wait/synch/mutex/mysys/THR_LOCK_charset";
count(name)
1
select count(name) from mutex_instances
where name like "wait/synch/mutex/mysys/THR_LOCK_time";
count(name)
1
select count(name) from cond_instances
where name like "wait/synch/cond/mysys/THR_COND_threads";
count(name)
......
......@@ -52,9 +52,6 @@ select count(name) from mutex_instances
select count(name) from mutex_instances
where name like "wait/synch/mutex/mysys/THR_LOCK_charset";
select count(name) from mutex_instances
where name like "wait/synch/mutex/mysys/THR_LOCK_time";
# There are no global rwlock in mysys
# Verify that these global conditions have been properly initilized in mysys
......
......@@ -25,13 +25,25 @@
#include "mysys_priv.h"
#include "my_static.h"
/**
Get high-resolution time.
@remark For windows platforms we need the frequency value of
the CPU. This is initialized in my_init.c through
QueryPerformanceFrequency(). If the Windows platform
doesn't support QueryPerformanceFrequency(), zero is
returned.
@retval current high-resolution time.
*/
ulonglong my_getsystime()
{
#ifdef HAVE_CLOCK_GETTIME
struct timespec tp;
clock_gettime(CLOCK_REALTIME, &tp);
return (ulonglong)tp.tv_sec*10000000+(ulonglong)tp.tv_nsec/100;
#elif defined(__WIN__)
#elif defined(_WIN32)
LARGE_INTEGER t_cnt;
if (query_performance_frequency)
{
......@@ -50,22 +62,17 @@ ulonglong my_getsystime()
}
/*
Return current time
/**
Return current time.
SYNOPSIS
my_time()
flags If MY_WME is set, write error if time call fails
@param flags If MY_WME is set, write error if time call fails.
@retval current time.
*/
time_t my_time(myf flags __attribute__((unused)))
time_t my_time(myf flags)
{
time_t t;
#ifdef HAVE_GETHRTIME
(void) my_micro_time_and_time(&t);
return t;
#else
/* The following loop is here beacuse time() may fail on some systems */
while ((t= time(0)) == (time_t) -1)
{
......@@ -73,39 +80,26 @@ time_t my_time(myf flags __attribute__((unused)))
fprintf(stderr, "%s: Warning: time() call failed\n", my_progname);
}
return t;
#endif
}
/*
Return time in micro seconds
SYNOPSIS
my_micro_time()
NOTES
This function is to be used to measure performance in micro seconds.
As it's not defined whats the start time for the clock, this function
us only useful to measure time between two moments.
/**
Return time in microseconds.
For windows platforms we need the frequency value of the CUP. This is
initalized in my_init.c through QueryPerformanceFrequency().
@remark This function is to be used to measure performance in
micro seconds. As it's not defined whats the start time
for the clock, this function us only useful to measure
time between two moments.
If Windows platform doesn't support QueryPerformanceFrequency() we will
obtain the time via GetClockCount, which only supports milliseconds.
RETURN
Value in microseconds from some undefined point in time
@retval Value in microseconds from some undefined point in time.
*/
ulonglong my_micro_time()
{
#if defined(__WIN__)
#ifdef _WIN32
ulonglong newtime;
GetSystemTimeAsFileTime((FILETIME*)&newtime);
return (newtime/10);
#elif defined(HAVE_GETHRTIME)
return gethrtime()/1000;
#else
ulonglong newtime;
struct timeval t;
......@@ -116,69 +110,37 @@ ulonglong my_micro_time()
{}
newtime= (ulonglong)t.tv_sec * 1000000 + t.tv_usec;
return newtime;
#endif /* defined(__WIN__) */
#endif
}
/*
/**
Return time in seconds and timer in microseconds (not different start!)
SYNOPSIS
my_micro_time_and_time()
time_arg Will be set to seconds since epoch (00:00:00 UTC,
January 1, 1970)
@param time_arg Will be set to seconds since epoch.
NOTES
This function is to be useful when we need both the time and microtime.
For example in MySQL this is used to get the query time start of a query
and to measure the time of a query (for the slow query log)
@remark This function is to be useful when we need both the time and
microtime. For example in MySQL this is used to get the query
time start of a query and to measure the time of a query (for
the slow query log)
IMPLEMENTATION
Value of time is as in time() call.
Value of microtime is same as my_micro_time(), which may be totally
unrealated to time()
@remark The time source is the same as for my_micro_time(), meaning
that time values returned by both functions can be intermixed
in meaningful ways (i.e. for comparison purposes).
RETURN
Value in microseconds from some undefined point in time
@retval Value in microseconds from some undefined point in time.
*/
#define DELTA_FOR_SECONDS 500000000LL /* Half a second */
/* Difference between GetSystemTimeAsFileTime() and now() */
#define OFFSET_TO_EPOCH 116444736000000000ULL
ulonglong my_micro_time_and_time(time_t *time_arg)
{
#if defined(__WIN__)
#ifdef _WIN32
ulonglong newtime;
GetSystemTimeAsFileTime((FILETIME*)&newtime);
*time_arg= (time_t) ((newtime - OFFSET_TO_EPOCH) / 10000000);
return (newtime/10);
#elif defined(HAVE_GETHRTIME)
/*
Solaris has a very slow time() call. We optimize this by using the very
fast gethrtime() call and only calling time() every 1/2 second
*/
static hrtime_t prev_gethrtime= 0;
static time_t cur_time= 0;
hrtime_t cur_gethrtime;
mysql_mutex_lock(&THR_LOCK_time);
cur_gethrtime= gethrtime();
/*
Due to bugs in the Solaris (x86) implementation of gethrtime(),
the time returned by it might not be monotonic. Don't use the
cached time(2) value if this is a case.
*/
if ((prev_gethrtime > cur_gethrtime) ||
((cur_gethrtime - prev_gethrtime) > DELTA_FOR_SECONDS))
{
cur_time= time(0);
prev_gethrtime= cur_gethrtime;
}
*time_arg= cur_time;
mysql_mutex_unlock(&THR_LOCK_time);
return cur_gethrtime/1000;
#else
ulonglong newtime;
struct timeval t;
......@@ -190,37 +152,31 @@ ulonglong my_micro_time_and_time(time_t *time_arg)
*time_arg= t.tv_sec;
newtime= (ulonglong)t.tv_sec * 1000000 + t.tv_usec;
return newtime;
#endif /* defined(__WIN__) */
#endif
}
/*
Returns current time
/**
Returns current time.
SYNOPSIS
my_time_possible_from_micro()
microtime Value from very recent my_micro_time()
@param microtime Value from very recent my_micro_time().
NOTES
This function returns the current time. The microtime argument is only used
if my_micro_time() uses a function that can safely be converted to the
current time.
@remark This function returns the current time. The microtime argument
is only used if my_micro_time() uses a function that can safely
be converted to the current time.
RETURN
current time
@retval current time.
*/
time_t my_time_possible_from_micro(ulonglong microtime __attribute__((unused)))
{
#if defined(__WIN__)
#ifdef _WIN32
time_t t;
while ((t= time(0)) == (time_t) -1)
{}
return t;
#elif defined(HAVE_GETHRTIME)
return my_time(0); /* Cached time */
#else
return (time_t) (microtime / 1000000);
#endif /* defined(__WIN__) */
#endif
}
......@@ -510,7 +510,7 @@ PSI_mutex_key key_BITMAP_mutex, key_IO_CACHE_append_buffer_lock,
key_my_thread_var_mutex, key_THR_LOCK_charset, key_THR_LOCK_heap,
key_THR_LOCK_isam, key_THR_LOCK_lock, key_THR_LOCK_malloc,
key_THR_LOCK_mutex, key_THR_LOCK_myisam, key_THR_LOCK_net,
key_THR_LOCK_open, key_THR_LOCK_threads, key_THR_LOCK_time,
key_THR_LOCK_open, key_THR_LOCK_threads,
key_TMPDIR_mutex, key_THR_LOCK_myisam_mmap;
static PSI_mutex_info all_mysys_mutexes[]=
......@@ -540,7 +540,6 @@ static PSI_mutex_info all_mysys_mutexes[]=
{ &key_THR_LOCK_net, "THR_LOCK_net", PSI_FLAG_GLOBAL},
{ &key_THR_LOCK_open, "THR_LOCK_open", PSI_FLAG_GLOBAL},
{ &key_THR_LOCK_threads, "THR_LOCK_threads", PSI_FLAG_GLOBAL},
{ &key_THR_LOCK_time, "THR_LOCK_time", PSI_FLAG_GLOBAL},
{ &key_TMPDIR_mutex, "TMPDIR_mutex", PSI_FLAG_GLOBAL},
{ &key_THR_LOCK_myisam_mmap, "THR_LOCK_myisam_mmap", PSI_FLAG_GLOBAL}
};
......
......@@ -25,7 +25,7 @@
pthread_key(struct st_my_thread_var*, THR_KEY_mysys);
mysql_mutex_t THR_LOCK_malloc, THR_LOCK_open,
THR_LOCK_lock, THR_LOCK_isam, THR_LOCK_myisam, THR_LOCK_heap,
THR_LOCK_net, THR_LOCK_charset, THR_LOCK_threads, THR_LOCK_time,
THR_LOCK_net, THR_LOCK_charset, THR_LOCK_threads,
THR_LOCK_myisam_mmap;
mysql_cond_t THR_COND_threads;
......@@ -219,7 +219,6 @@ my_bool my_thread_global_init(void)
mysql_mutex_init(key_THR_LOCK_myisam_mmap, &THR_LOCK_myisam_mmap, MY_MUTEX_INIT_FAST);
mysql_mutex_init(key_THR_LOCK_heap, &THR_LOCK_heap, MY_MUTEX_INIT_FAST);
mysql_mutex_init(key_THR_LOCK_net, &THR_LOCK_net, MY_MUTEX_INIT_FAST);
mysql_mutex_init(key_THR_LOCK_time, &THR_LOCK_time, MY_MUTEX_INIT_FAST);
mysql_cond_init(key_THR_COND_threads, &THR_COND_threads, NULL);
#if !defined(HAVE_LOCALTIME_R) || !defined(HAVE_GMTIME_R)
......@@ -288,7 +287,6 @@ void my_thread_global_end(void)
mysql_mutex_destroy(&THR_LOCK_myisam_mmap);
mysql_mutex_destroy(&THR_LOCK_heap);
mysql_mutex_destroy(&THR_LOCK_net);
mysql_mutex_destroy(&THR_LOCK_time);
mysql_mutex_destroy(&THR_LOCK_charset);
if (all_threads_killed)
{
......
......@@ -45,7 +45,7 @@ extern PSI_mutex_key key_BITMAP_mutex, key_IO_CACHE_append_buffer_lock,
key_my_thread_var_mutex, key_THR_LOCK_charset, key_THR_LOCK_heap,
key_THR_LOCK_isam, key_THR_LOCK_lock, key_THR_LOCK_malloc,
key_THR_LOCK_mutex, key_THR_LOCK_myisam, key_THR_LOCK_net,
key_THR_LOCK_open, key_THR_LOCK_threads, key_THR_LOCK_time,
key_THR_LOCK_open, key_THR_LOCK_threads,
key_TMPDIR_mutex, key_THR_LOCK_myisam_mmap;
extern PSI_cond_key key_COND_alarm, key_IO_CACHE_SHARE_cond,
......@@ -60,7 +60,7 @@ extern PSI_thread_key key_thread_alarm;
extern mysql_mutex_t THR_LOCK_malloc, THR_LOCK_open, THR_LOCK_keycache;
extern mysql_mutex_t THR_LOCK_lock, THR_LOCK_isam, THR_LOCK_net;
extern mysql_mutex_t THR_LOCK_charset, THR_LOCK_time;
extern mysql_mutex_t THR_LOCK_charset;
#include <mysql/psi/mysql_file.h>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment