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