1 /* Copyright (c) 2014, 2021, Oracle and/or its affiliates.
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 #if defined (_WIN32)
25 #include "my_global.h"
26 #include "my_thread.h"      /* my_thread_init, my_thread_end */
27 #include "my_sys.h"         /* my_message_local */
28 #include "my_timer.h"       /* my_timer_t */
29 #include <windows.h>        /* Timer Queue and IO completion port functions */
30 
31 
32 enum enum_timer_state { TIMER_SET=FALSE, TIMER_EXPIRED=TRUE };
33 
34 // Timer notifier thread id.
35 static my_thread_handle timer_notify_thread;
36 
37 // IO completion port handle
38 HANDLE io_compl_port= 0;
39 
40 // Timer queue handle
41 HANDLE timer_queue= 0;
42 
43 /**
44   Callback function registered to execute on timer expiration.
45 
46   @param  timer_data             timer data passed to function.
47   @param  timer_or_wait_fired    flag to represent timer fired or signalled.
48 
49   @remark this function is executed in timer owner thread when timer
50           expires.
51 */
52 static void CALLBACK
timer_callback_function(PVOID timer_data,BOOLEAN timer_or_wait_fired MY_ATTRIBUTE ((unused)))53 timer_callback_function(PVOID timer_data, BOOLEAN timer_or_wait_fired MY_ATTRIBUTE((unused)))
54 {
55   my_timer_t *timer= (my_timer_t *)timer_data;
56   assert(timer != NULL);
57   timer->id.timer_state= TIMER_EXPIRED;
58   PostQueuedCompletionStatus(io_compl_port, 0, (ULONG_PTR)timer, 0);
59 }
60 
61 
62 /**
63   Timer expiration notification thread.
64 
65   @param arg  Unused.
66 */
67 static void*
timer_notify_thread_func(void * arg MY_ATTRIBUTE ((unused)))68 timer_notify_thread_func(void *arg MY_ATTRIBUTE((unused)))
69 {
70   DWORD bytes_transferred;
71   ULONG_PTR compl_key;
72   LPOVERLAPPED overlapped;
73   my_timer_t *timer;
74 
75   my_thread_init();
76 
77   while(1)
78   {
79     // Get IO Completion status.
80     if (GetQueuedCompletionStatus(io_compl_port, &bytes_transferred, &compl_key,
81                                   &overlapped, INFINITE) == 0)
82       break;
83 
84     timer= (my_timer_t*)compl_key;
85     timer->notify_function(timer);
86   }
87 
88   my_thread_end();
89 
90   return NULL;
91 }
92 
93 
94 /**
95   Delete a timer.
96 
97   @param timer    Timer Object.
98   @param state    The state of the timer at the time of deletion, either
99                   signaled (0) or nonsignaled (1).
100 
101   @return  0 On Success
102           -1 On error.
103 */
104 static int
delete_timer(my_timer_t * timer,int * state)105 delete_timer(my_timer_t *timer, int *state)
106 {
107   int ret_val;
108   int retry_count= 3;
109 
110   assert(timer != 0);
111   assert(timer_queue != 0);
112 
113   if (state != NULL)
114     *state= 0;
115 
116   if (timer->id.timer_handle)
117   {
118     do {
119       ret_val= DeleteTimerQueueTimer(timer_queue, timer->id.timer_handle, NULL);
120 
121       if (ret_val != 0)
122       {
123         /**
124           From MSDN documentation of DeleteTimerQueueTimer:
125 
126             ------------------------------------------------------------------
127 
128             BOOL WINAPI DeleteTimerQueueTimer(
129               _In_opt_ HANDLE TimerQueue,
130               _In_     HANDLE Timer,
131               _In_opt_ HANDLE CompletionEvent
132             );
133 
134             ...
135             If there are outstanding callback functions and CompletionEvent is
136             NULL, the function will fail and set the error code to
137             ERROR_IO_PENDING. This indicates that there are outstanding callback
138             functions. Those callbacks either will execute or are in the middle
139             of executing.
140             ...
141 
142             ------------------------------------------------------------------
143 
144           So we are here only in 2 cases,
145              1 When timer is *not* expired yet.
146              2 When timer is expired and callback function execution is
147                completed.
148 
149           So here in case 1 timer.id->timer_state is TIMER_SET and
150                   in case 2 timer.id->timer_state is TIMER_EXPIRED
151           (From MSDN documentation(pasted above), if timer callback function is
152            not yet executed or it is in the middle of execution then
153            DeleteTimerQueueTimer() fails. Hence when we are here, we are sure
154            that state is either TIMER_SET(case 1) or TIMER_EXPIRED(case 2))
155 
156           Note:
157             timer.id->timer_state is set to TIMER_EXPIRED in
158             timer_callback_function(). This function is executed by the OS
159             thread on timer expiration.
160 
161             On timer expiration, when callback function is in the middle of
162             execution or it is yet to be executed by OS thread the call to
163             DeleteTimerQueueTimer() fails with an error "ERROR_IO_PENDING".
164             In this case,  timer.id->timer_state is not accessed in the current
165             code (please check else if block below).
166 
167             Since timer.id->timer_state is not accessed in the current code
168             while it is getting modified in timer_callback_function,
169             no synchronization mechanism used.
170 
171           Setting state to 1(non-signaled) if timer_state is not set to
172           "TIMER_EXPIRED"
173         */
174         if (timer->id.timer_state != TIMER_EXPIRED && state != NULL)
175           *state= 1;
176 
177         timer->id.timer_handle= 0;
178       }
179       else if (GetLastError() == ERROR_IO_PENDING)
180       {
181         /**
182           Timer is expired and timer callback function execution is not
183           yet completed.
184 
185           Note: timer->id.timer_state is modified in callback function.
186                 Accessing timer->id.timer_state here might result in
187                 race conditions.
188                 Currently we are not accessing timer->id.timer_state
189                 here so not using any synchronization mechanism.
190         */
191         timer->id.timer_handle= 0;
192         ret_val= 1;
193       }
194       else
195       {
196         /**
197           Timer deletion from queue failed and there are no outstanding
198           callback functions for this timer.
199         */
200         if (--retry_count == 0)
201         {
202           my_message_local(ERROR_LEVEL, "Failed to delete timer (errno= %d).",
203                            errno);
204           return -1;
205         }
206       }
207     } while (ret_val == 0);
208   }
209 
210   return 0;
211 }
212 
213 
214 /**
215   Initialize internal components.
216 
217   @return 0 On success
218           -1 On error.
219 */
220 int
my_timer_initialize(void)221 my_timer_initialize(void)
222 {
223   // Create timer queue.
224   timer_queue= CreateTimerQueue();
225   if (!timer_queue)
226   {
227     my_message_local(ERROR_LEVEL, "Failed to create Timer Queue (errno= %d)",
228                      errno);
229     goto err;
230   }
231 
232   // Create IO completion port.
233   io_compl_port= CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);
234   if (!io_compl_port)
235   {
236     my_message_local(ERROR_LEVEL,
237                      "Failed to create IO completion port (errno= %d)",
238                      errno);
239     goto err;
240   }
241 
242   if (mysql_thread_create(key_thread_timer_notifier, &timer_notify_thread, 0,
243                           timer_notify_thread_func, 0))
244   {
245     my_message_local(ERROR_LEVEL,
246                      "Failed to create timer notify thread (errno= %d).",
247                      errno);
248     goto err;
249   }
250 
251   return 0;
252 
253 err:
254   if (timer_queue)
255   {
256     DeleteTimerQueueEx(timer_queue, NULL);
257     timer_queue= 0;
258   }
259 
260   if (io_compl_port)
261   {
262     CloseHandle(io_compl_port);
263     io_compl_port= 0;
264   }
265 
266   return -1;
267 }
268 
269 
270 /**
271   Release any resources that were allocated as part of initialization.
272 */
273 void
my_timer_deinitialize()274 my_timer_deinitialize()
275 {
276   if (timer_queue)
277   {
278     DeleteTimerQueueEx(timer_queue, NULL);
279     timer_queue= 0;
280   }
281 
282   if (io_compl_port)
283   {
284     CloseHandle(io_compl_port);
285     io_compl_port= 0;
286   }
287 
288   my_thread_join(&timer_notify_thread, NULL);
289 }
290 
291 
292 /**
293   Create a timer object.
294 
295   @param  timer   Timer object.
296 
297   @return On success, 0.
298 */
299 int
my_timer_create(my_timer_t * timer)300 my_timer_create(my_timer_t *timer)
301 {
302   assert(timer_queue != 0);
303   timer->id.timer_handle= 0;
304   return 0;
305 }
306 
307 
308 /**
309   Set the time until the next expiration of the timer.
310 
311   @param  timer   Timer object.
312   @param  time    Amount of time (in milliseconds) before the timer expires.
313 
314   @return On success, 0.
315           On error, -1.
316 */
317 int
my_timer_set(my_timer_t * timer,unsigned long time)318 my_timer_set(my_timer_t *timer, unsigned long time)
319 {
320   assert(timer != NULL);
321   assert(timer_queue != 0);
322 
323   /**
324     If timer set previously is expired then it will not be
325     removed from the timer queue. Removing it before creating
326     a new timer queue timer.
327   */
328   if (timer->id.timer_handle != 0)
329     my_timer_delete(timer);
330 
331   timer->id.timer_state= TIMER_SET;
332 
333   if (CreateTimerQueueTimer(&timer->id.timer_handle, timer_queue,
334                             timer_callback_function, timer, time, 0,
335                             WT_EXECUTEONLYONCE) == 0)
336     return -1;
337 
338   return 0;
339 }
340 
341 
342 /**
343   Cancel the timer.
344 
345   @param  timer   Timer object.
346   @param  state   The state of the timer at the time of cancellation, either
347                   signaled (0) or nonsignaled (1).
348 
349   @return On success, 0.
350           On error,  -1.
351 */
352 int
my_timer_cancel(my_timer_t * timer,int * state)353 my_timer_cancel(my_timer_t *timer, int *state)
354 {
355   assert(state != NULL);
356 
357   return delete_timer(timer, state);
358 }
359 
360 
361 /**
362   Delete a timer object.
363 
364   @param  timer   Timer Object.
365 */
366 void
my_timer_delete(my_timer_t * timer)367 my_timer_delete(my_timer_t *timer)
368 {
369   delete_timer(timer, NULL);
370 }
371 #endif
372