Skip to content

Commit e6f47a0

Browse files
committed
Bug#20705648 - max_statement_time leaks memory on windows
Bug#20705642 - max_statement_time: assertion failed: pending || thd_timer->thread_id Description: ------------- Both of these issues are related to deletion of timer from the Timer-queue. Issue in bug20705648 is, the timer object created in windows is not deleted after the timer expiration. Hence memory leak is seen on the windows. Issue in bug20705642 is, the timer reset operation sets state of timer as "not expired" when the timer is already expired and the callback function execution of it is completed. This is resulting in the assert condition failure. Analysis: ------------- In the current code, for the set timer operation new Timer-queue timer is created. For timer cancel and destroy operations, timer -queue timer is deleted from the Timer-queue. But when timer is expired, assumption was timers of type "WT_EXECUTEONLYONCE" are deleted. But these timer objects are not delete from Timer-queue on expiration. Hence the memory leak is observed in the current code. The windows API to delete timer-queue timer, 1 Fails when timer is expired or callback function is in execution. 2 Succeeds when timer is *not* expired. 3 Succeeds when timer is expired and callback function execution is completed. In the current code, case 1 and 2 are handled properly but case 3 is not handled. Because of which windows timer reset operation returns timer state as not expired. If timer is not expired then non zero thread_id value is expected (as notify function for timer expiration is not called) in reset timer code. Assert condition to check the same fails in debug build because of not handling condition 3. In non-debug build, timer object is just set for the reuse. Fix: ------ To fix memory leak, if timer is expired then deleting it from the queue on next timer set or timer destroy operations. To fix assert issues, new timer_state member is introduced in the my_timer for windows. timer_state is set to "TIMER_SET" in timer set operation and in timer_callback to "TIMER_EXPIRED". With timer_state, setting timer expired/not expired state in timer reset operation is handled as below, Timer reset operation (my_timer_cancel()): Windows API DeleteTimerQueueTimer is used to delete m the timer queue. This API, * Succeeds when timer is *not* expired. state is set to *not expired* as timer_state is TIMER_SET. * Succeeds when timer is expired and callback function execution is completed. state is set to *expired* as timer_state is TIMER_EXPIRED. * *Fails* when timer is expired or callback function is in execution. state is set to *expired*. timer_state value is *not* checked in this case. So to fix assert condition failure, "timer_state" value is checked to set timer expired or non expired state. Now if timer cancellation is called after timer callback execution then "expired" state is returned. Testing: -------- Without patch, memory leak of around 400kb/sec is observed with mysqlslap.exe -hlocalhost --number-of-queries=10000000000 --query="select max_statement_time=1 sleep(rand()) ;" --iterations=1000000 --create-schema=test With patch no memory leak or crash is observed, \bin>time The current time is: 11:21:03.07 Enter the new time: \bin>tasklist /FI "IMAGENAME eq mysqld.exe" Image Name PID Session Name Session# Mem Usage ========================= ======== ================ =========== ============ mysqld.exe 11304 RDP-Tcp#28 2 119,244 K \bin>time The current time is: 11:28:35.59 Enter the new time: . \bin>tasklist /FI "IMAGENAME eq mysqld.exe" Image Name PID Session Name Session# Mem Usage ========================= ======== ================ =========== ============ mysqld.exe 11304 RDP-Tcp#28 2 119,244 K
1 parent fa96ffe commit e6f47a0

File tree

2 files changed

+90
-22
lines changed

2 files changed

+90
-22
lines changed

include/my_timer.h

+5-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@
2727
# include <sys/types.h> /* uintptr_t */
2828
typedef uintptr_t os_timer_t;
2929
#elif _WIN32
30-
typedef HANDLE os_timer_t;
30+
typedef struct st_os_timer
31+
{
32+
HANDLE timer_handle;
33+
my_bool timer_state;
34+
} os_timer_t;
3135
#endif
3236

3337
typedef struct st_my_timer my_timer_t;

mysys/win_timers.c

+85-21
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
#include "my_timer.h" /* my_timer_t */
2222
#include <windows.h> /* Timer Queue and IO completion port functions */
2323

24-
#define TIMER_EXPIRED 1
24+
25+
enum enum_timer_state { TIMER_SET=FALSE, TIMER_EXPIRED=TRUE };
2526

2627
// Timer notifier thread id.
2728
static my_thread_handle timer_notify_thread;
@@ -46,7 +47,8 @@ timer_callback_function(PVOID timer_data, BOOLEAN timer_or_wait_fired __attribut
4647
{
4748
my_timer_t *timer= (my_timer_t *)timer_data;
4849
DBUG_ASSERT(timer != NULL);
49-
PostQueuedCompletionStatus(io_compl_port, TIMER_EXPIRED, (ULONG_PTR)timer, 0);
50+
timer->id.timer_state= TIMER_EXPIRED;
51+
PostQueuedCompletionStatus(io_compl_port, 0, (ULONG_PTR)timer, 0);
5052
}
5153

5254

@@ -58,7 +60,7 @@ timer_callback_function(PVOID timer_data, BOOLEAN timer_or_wait_fired __attribut
5860
static void*
5961
timer_notify_thread_func(void *arg __attribute__((unused)))
6062
{
61-
DWORD timer_state;
63+
DWORD bytes_transferred;
6264
ULONG_PTR compl_key;
6365
LPOVERLAPPED overlapped;
6466
my_timer_t *timer;
@@ -68,17 +70,11 @@ timer_notify_thread_func(void *arg __attribute__((unused)))
6870
while(1)
6971
{
7072
// Get IO Completion status.
71-
if (GetQueuedCompletionStatus(io_compl_port, &timer_state, &compl_key,
73+
if (GetQueuedCompletionStatus(io_compl_port, &bytes_transferred, &compl_key,
7274
&overlapped, INFINITE) == 0)
7375
break;
7476

7577
timer= (my_timer_t*)compl_key;
76-
77-
// Timer is cancelled.
78-
if (timer->id == 0)
79-
continue;
80-
81-
timer->id= 0;
8278
timer->notify_function(timer);
8379
}
8480

@@ -102,7 +98,6 @@ static int
10298
delete_timer(my_timer_t *timer, int *state)
10399
{
104100
int ret_val;
105-
HANDLE id= timer->id;
106101
int retry_count= 3;
107102

108103
DBUG_ASSERT(timer != 0);
@@ -111,27 +106,87 @@ delete_timer(my_timer_t *timer, int *state)
111106
if (state != NULL)
112107
*state= 0;
113108

114-
if (id)
109+
if (timer->id.timer_handle)
115110
{
116111
do {
117-
ret_val= DeleteTimerQueueTimer(timer_queue, id, NULL);
112+
ret_val= DeleteTimerQueueTimer(timer_queue, timer->id.timer_handle, NULL);
118113

119114
if (ret_val != 0)
120115
{
121-
// Timer is not signalled yet.
122-
if (state != NULL)
116+
/**
117+
From MSDN documentation of DeleteTimerQueueTimer:
118+
119+
------------------------------------------------------------------
120+
121+
BOOL WINAPI DeleteTimerQueueTimer(
122+
_In_opt_ HANDLE TimerQueue,
123+
_In_ HANDLE Timer,
124+
_In_opt_ HANDLE CompletionEvent
125+
);
126+
127+
...
128+
If there are outstanding callback functions and CompletionEvent is
129+
NULL, the function will fail and set the error code to
130+
ERROR_IO_PENDING. This indicates that there are outstanding callback
131+
functions. Those callbacks either will execute or are in the middle
132+
of executing.
133+
...
134+
135+
------------------------------------------------------------------
136+
137+
So we are here only in 2 cases,
138+
1 When timer is *not* expired yet.
139+
2 When timer is expired and callback function execution is
140+
completed.
141+
142+
So here in case 1 timer.id->timer_state is TIMER_SET and
143+
in case 2 timer.id->timer_state is TIMER_EXPIRED
144+
(From MSDN documentation(pasted above), if timer callback function is
145+
not yet executed or it is in the middle of execution then
146+
DeleteTimerQueueTimer() fails. Hence when we are here, we are sure
147+
that state is either TIMER_SET(case 1) or TIMER_EXPIRED(case 2))
148+
149+
Note:
150+
timer.id->timer_state is set to TIMER_EXPIRED in
151+
timer_callback_function(). This function is executed by the OS
152+
thread on timer expiration.
153+
154+
On timer expiration, when callback function is in the middle of
155+
execution or it is yet to be executed by OS thread the call to
156+
DeleteTimerQueueTimer() fails with an error "ERROR_IO_PENDING".
157+
In this case, timer.id->timer_state is not accessed in the current
158+
code (please check else if block below).
159+
160+
Since timer.id->timer_state is not accessed in the current code
161+
while it is getting modified in timer_callback_function,
162+
no synchronization mechanism used.
163+
164+
Setting state to 1(non-signaled) if timer_state is not set to
165+
"TIMER_EXPIRED"
166+
*/
167+
if (timer->id.timer_state != TIMER_EXPIRED && state != NULL)
123168
*state= 1;
124169

125-
timer->id= 0;
170+
timer->id.timer_handle= 0;
126171
}
127172
else if (GetLastError() == ERROR_IO_PENDING)
128173
{
129-
// Timer is signalled but callback function is not called yet.
174+
/**
175+
Timer is expired and timer callback function execution is not
176+
yet completed.
177+
178+
Note: timer->id.timer_state is modified in callback function.
179+
Accessing timer->id.timer_state here might result in
180+
race conditions.
181+
Currently we are not accessing timer->id.timer_state
182+
here so not using any synchronization mechanism.
183+
*/
184+
timer->id.timer_handle= 0;
130185
ret_val= 1;
131186
}
132187
else
133188
{
134-
/*
189+
/**
135190
Timer deletion from queue failed and there are no outstanding
136191
callback functions for this timer.
137192
*/
@@ -238,7 +293,7 @@ int
238293
my_timer_create(my_timer_t *timer)
239294
{
240295
DBUG_ASSERT(timer_queue != 0);
241-
timer->id= 0;
296+
timer->id.timer_handle= 0;
242297
return 0;
243298
}
244299

@@ -256,10 +311,19 @@ int
256311
my_timer_set(my_timer_t *timer, unsigned long time)
257312
{
258313
DBUG_ASSERT(timer != NULL);
259-
DBUG_ASSERT(timer->id == 0);
260314
DBUG_ASSERT(timer_queue != 0);
261315

262-
if (CreateTimerQueueTimer(&timer->id, timer_queue,
316+
/**
317+
If timer set previously is expired then it will not be
318+
removed from the timer queue. Removing it before creating
319+
a new timer queue timer.
320+
*/
321+
if (timer->id.timer_handle != 0)
322+
my_timer_delete(timer);
323+
324+
timer->id.timer_state= TIMER_SET;
325+
326+
if (CreateTimerQueueTimer(&timer->id.timer_handle, timer_queue,
263327
timer_callback_function, timer, time, 0,
264328
WT_EXECUTEONLYONCE) == 0)
265329
return -1;

0 commit comments

Comments
 (0)