1 /* Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License, version 2.0,
5 as published by the Free Software Foundation.
6
7 This program is also distributed with certain software (including
8 but not limited to OpenSSL) that is licensed under separate terms,
9 as designated in a particular file or component or in included license
10 documentation. The authors of MySQL hereby grant you an additional
11 permission to link the program and your derivative works with the
12 separately licensed software that they have included with MySQL.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License, version 2.0, for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
22
23 /**
24 @file mysys/win_timers.cc
25 */
26
27 #if defined(_WIN32)
28 #include <errno.h>
29 #include <windows.h> /* Timer Queue and IO completion port functions */
30
31 #include "my_dbug.h"
32 #include "my_sys.h" /* my_message_local */
33 #include "my_thread.h" /* my_thread_init, my_thread_end */
34 #include "my_timer.h" /* my_timer_t */
35 #include "mysql/psi/mysql_thread.h"
36 #include "mysys_err.h"
37 #include "mysys_priv.h" /* key_thread_timer_notifier */
38
39 enum enum_timer_state { TIMER_SET = false, TIMER_EXPIRED = true };
40
41 // Timer notifier thread id.
42 static my_thread_handle timer_notify_thread;
43
44 // IO completion port handle
45 HANDLE io_compl_port = 0;
46
47 // Timer queue handle
48 HANDLE timer_queue = 0;
49
50 /**
51 Callback function registered to execute on timer expiration.
52
53 @param timer_data timer data passed to function.
54 @param timer_or_wait_fired flag to represent timer fired or signalled.
55
56 @remark this function is executed in timer owner thread when timer
57 expires.
58 */
timer_callback_function(PVOID timer_data,BOOLEAN timer_or_wait_fired MY_ATTRIBUTE ((unused)))59 static void CALLBACK timer_callback_function(
60 PVOID timer_data, BOOLEAN timer_or_wait_fired MY_ATTRIBUTE((unused))) {
61 my_timer_t *timer = (my_timer_t *)timer_data;
62 DBUG_ASSERT(timer != NULL);
63 timer->id.timer_state = TIMER_EXPIRED;
64 PostQueuedCompletionStatus(io_compl_port, 0, (ULONG_PTR)timer, 0);
65 }
66
67 /**
68 Timer expiration notification thread.
69
70 @param arg Unused.
71 */
timer_notify_thread_func(void * arg MY_ATTRIBUTE ((unused)))72 static void *timer_notify_thread_func(void *arg MY_ATTRIBUTE((unused))) {
73 DWORD bytes_transferred;
74 ULONG_PTR compl_key;
75 LPOVERLAPPED overlapped;
76 my_timer_t *timer;
77
78 my_thread_init();
79
80 while (1) {
81 // Get IO Completion status.
82 if (GetQueuedCompletionStatus(io_compl_port, &bytes_transferred, &compl_key,
83 &overlapped, INFINITE) == 0)
84 break;
85
86 timer = (my_timer_t *)compl_key;
87 timer->notify_function(timer);
88 }
89
90 my_thread_end();
91
92 return NULL;
93 }
94
95 /**
96 Delete a timer.
97
98 @param timer Timer Object.
99 @param state The state of the timer at the time of deletion, either
100 signaled (0) or nonsignaled (1).
101
102 @return 0 On Success
103 -1 On error.
104 */
delete_timer(my_timer_t * timer,int * state)105 static int delete_timer(my_timer_t *timer, int *state) {
106 int ret_val;
107 int retry_count = 3;
108
109 DBUG_ASSERT(timer != 0);
110 DBUG_ASSERT(timer_queue != 0);
111
112 if (state != NULL) *state = 0;
113
114 if (timer->id.timer_handle) {
115 do {
116 ret_val =
117 DeleteTimerQueueTimer(timer_queue, timer->id.timer_handle, NULL);
118
119 if (ret_val != 0) {
120 /**
121 From MSDN documentation of DeleteTimerQueueTimer:
122
123 ------------------------------------------------------------------
124
125 BOOL WINAPI DeleteTimerQueueTimer(
126 _In_opt_ HANDLE TimerQueue,
127 _In_ HANDLE Timer,
128 _In_opt_ HANDLE CompletionEvent
129 );
130
131 ...
132 If there are outstanding callback functions and CompletionEvent is
133 NULL, the function will fail and set the error code to
134 ERROR_IO_PENDING. This indicates that there are outstanding callback
135 functions. Those callbacks either will execute or are in the middle
136 of executing.
137 ...
138
139 ------------------------------------------------------------------
140
141 So we are here only in 2 cases,
142 1 When timer is *not* expired yet.
143 2 When timer is expired and callback function execution is
144 completed.
145
146 So here in case 1 timer.id->timer_state is TIMER_SET and
147 in case 2 timer.id->timer_state is TIMER_EXPIRED
148 (From MSDN documentation(pasted above), if timer callback function is
149 not yet executed or it is in the middle of execution then
150 DeleteTimerQueueTimer() fails. Hence when we are here, we are sure
151 that state is either TIMER_SET(case 1) or TIMER_EXPIRED(case 2))
152
153 Note:
154 timer.id->timer_state is set to TIMER_EXPIRED in
155 timer_callback_function(). This function is executed by the OS
156 thread on timer expiration.
157
158 On timer expiration, when callback function is in the middle of
159 execution or it is yet to be executed by OS thread the call to
160 DeleteTimerQueueTimer() fails with an error "ERROR_IO_PENDING".
161 In this case, timer.id->timer_state is not accessed in the current
162 code (please check else if block below).
163
164 Since timer.id->timer_state is not accessed in the current code
165 while it is getting modified in timer_callback_function,
166 no synchronization mechanism used.
167
168 Setting state to 1(non-signaled) if timer_state is not set to
169 "TIMER_EXPIRED"
170 */
171 if (timer->id.timer_state != TIMER_EXPIRED && state != NULL) *state = 1;
172
173 timer->id.timer_handle = 0;
174 } else if (GetLastError() == ERROR_IO_PENDING) {
175 /**
176 Timer is expired and timer callback function execution is not
177 yet completed.
178
179 Note: timer->id.timer_state is modified in callback function.
180 Accessing timer->id.timer_state here might result in
181 race conditions.
182 Currently we are not accessing timer->id.timer_state
183 here so not using any synchronization mechanism.
184 */
185 timer->id.timer_handle = 0;
186 ret_val = 1;
187 } else {
188 /**
189 Timer deletion from queue failed and there are no outstanding
190 callback functions for this timer.
191 */
192 if (--retry_count == 0) {
193 my_message_local(ERROR_LEVEL, EE_FAILED_TO_DELETE_TIMER, errno);
194 return -1;
195 }
196 }
197 } while (ret_val == 0);
198 }
199
200 return 0;
201 }
202
203 /**
204 Initialize internal components.
205
206 @return 0 On success
207 -1 On error.
208 */
my_timer_initialize(void)209 int my_timer_initialize(void) {
210 // Create timer queue.
211 timer_queue = CreateTimerQueue();
212 if (!timer_queue) {
213 my_message_local(ERROR_LEVEL, EE_FAILED_TO_CREATE_TIMER_QUEUE, errno);
214 goto err;
215 }
216
217 // Create IO completion port.
218 io_compl_port = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);
219 if (!io_compl_port) {
220 my_message_local(ERROR_LEVEL, EE_FAILED_TO_CREATE_IO_COMPLETION_PORT,
221 errno);
222 goto err;
223 }
224
225 if (mysql_thread_create(key_thread_timer_notifier, &timer_notify_thread, 0,
226 timer_notify_thread_func, 0)) {
227 my_message_local(ERROR_LEVEL, EE_FAILED_TO_START_TIMER_NOTIFY_THREAD,
228 errno);
229 goto err;
230 }
231
232 return 0;
233
234 err:
235 if (timer_queue) {
236 DeleteTimerQueueEx(timer_queue, NULL);
237 timer_queue = 0;
238 }
239
240 if (io_compl_port) {
241 CloseHandle(io_compl_port);
242 io_compl_port = 0;
243 }
244
245 return -1;
246 }
247
248 /**
249 Release any resources that were allocated as part of initialization.
250 */
my_timer_deinitialize()251 void my_timer_deinitialize() {
252 if (timer_queue) {
253 DeleteTimerQueueEx(timer_queue, NULL);
254 timer_queue = 0;
255 }
256
257 if (io_compl_port) {
258 CloseHandle(io_compl_port);
259 io_compl_port = 0;
260 }
261
262 my_thread_join(&timer_notify_thread, NULL);
263 }
264
265 /**
266 Create a timer object.
267
268 @param timer Timer object.
269
270 @return On success, 0.
271 */
my_timer_create(my_timer_t * timer)272 int my_timer_create(my_timer_t *timer) {
273 DBUG_ASSERT(timer_queue != 0);
274 timer->id.timer_handle = 0;
275 return 0;
276 }
277
278 /**
279 Set the time until the next expiration of the timer.
280
281 @param timer Timer object.
282 @param time Amount of time (in milliseconds) before the timer expires.
283
284 @return On success, 0.
285 On error, -1.
286 */
my_timer_set(my_timer_t * timer,unsigned long time)287 int my_timer_set(my_timer_t *timer, unsigned long time) {
288 DBUG_ASSERT(timer != NULL);
289 DBUG_ASSERT(timer_queue != 0);
290
291 /**
292 If timer set previously is expired then it will not be
293 removed from the timer queue. Removing it before creating
294 a new timer queue timer.
295 */
296 if (timer->id.timer_handle != 0) my_timer_delete(timer);
297
298 timer->id.timer_state = TIMER_SET;
299
300 if (CreateTimerQueueTimer(&timer->id.timer_handle, timer_queue,
301 timer_callback_function, timer, time, 0,
302 WT_EXECUTEONLYONCE) == 0)
303 return -1;
304
305 return 0;
306 }
307
308 /**
309 Cancel the timer.
310
311 @param timer Timer object.
312 @param state The state of the timer at the time of cancellation, either
313 signaled (0) or nonsignaled (1).
314
315 @return On success, 0.
316 On error, -1.
317 */
my_timer_cancel(my_timer_t * timer,int * state)318 int my_timer_cancel(my_timer_t *timer, int *state) {
319 DBUG_ASSERT(state != NULL);
320
321 return delete_timer(timer, state);
322 }
323
324 /**
325 Delete a timer object.
326
327 @param timer Timer Object.
328 */
my_timer_delete(my_timer_t * timer)329 void my_timer_delete(my_timer_t *timer) { delete_timer(timer, NULL); }
330 #endif
331