|   | php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login | 
| 
  [2019-02-07 14:21 UTC] oliver dot pfister at contaware dot com
 Description: ------------ DeleteTimerQueueTimer(HANDLE TimerQueue, HANDLE Timer, HANDLE CompletionEvent) return code problem in file zend_execute_API.c Because of vague Microsoft API documentation (see point 3. below) there is an important implementation difference between Windows and Wine/ReactOS in case of CompletionEvent set to NULL (PHP uses DeleteTimerQueueTimer with CompletionEvent set to NULL): Windows (tested on all versions of Windows from XP to Win10): calling DeleteTimerQueueTimer returns TRUE if the callback code is not yet executing, then when the thread (of the pool) enters the callback code, calling DeleteTimerQueueTimer returns FALSE with ERROR_IO_PENDING. When the thread (of the pool) has terminated execution of the callback code, calling DeleteTimerQueueTimer returns again TRUE. ReactOS/Wine (tested on latest versions of Wine and ReactOS): calling DeleteTimerQueueTimer always returns FALSE with ERROR_IO_PENDING. Important points of the DeleteTimerQueueTimer Microsoft Windows documentation (https://msdn.microsoft.com/en-us/library/windows/desktop/ms682569(v=vs.85).aspx): 1. If CompletionEvent is NULL, the function marks the timer for deletion and returns immediately. If the timer has already expired, the timer callback function will run to completion. 2. If the error code is ERROR_IO_PENDING, it is not necessary to call this function again. For any other error, you should retry the call. 3. If there are outstanding callback functions and CompletionEvent is NULL, the function will fail and set the error code to ERROR_IO_PENDING. This indicates that there are outstanding callback functions. Those callbacks either will execute or are in the middle of executing. The timer is cleaned up when the callback function is finished executing. As a result in Wine/ReactOS it's not possible to use any PHP, it fails with a fatal error. In Windows there is the possibility that the timer is deleted exactly when a thread (of the pool) is executing the callback, in that case a PHP fatal error would be risen for no good reason. So I propose to correct the PHP code and always check GetLastError() making sure it is not returning ERROR_IO_PENDING before failing. Test script: --------------- In zend_set_timeout_ex() and zend_unset_timeout() please correct the return code check from: if (!DeleteTimerQueueTimer(NULL, tq_timer, NULL)) { to: if (!DeleteTimerQueueTimer(NULL, tq_timer, NULL) && GetLastError() != ERROR_IO_PENDING) { PatchesPull RequestsHistoryAllCommentsChangesGit/SVN commits             | |||||||||||||||||||||||||||
|  Copyright © 2001-2025 The PHP Group All rights reserved. | Last updated: Fri Oct 31 06:00:01 2025 UTC | 
Using DeleteTimerQueueTimer in locking mode (with the INVALID_HANDLE_VALUE parameter) guarantees that an old callback which could set timed_out after we reset timed_out in zend_set_timeout() or zend_unset_timeout() cannot exist. For this reason in zend_set_timeout_ex() and zend_unset_timeout() I suggest changing: if (!DeleteTimerQueueTimer(NULL, tq_timer, NULL)) { to: if (!DeleteTimerQueueTimer(NULL, tq_timer, INVALID_HANDLE_VALUE)) { For types which are not bigger than the size of a native integer, if the memory is properly aligned (as it is by default), reads and writes with literals (like setting or resetting a flag) are always atomic. timed_out is of type zend_bool which is an unsigned char, read and writes on that 8-bit variable is always atomic (no alignment restrictions for a single byte). So it's not necessary to use InterlockedExchange.