summaryrefslogtreecommitdiff
path: root/ports/win32
diff options
context:
space:
mode:
Diffstat (limited to 'ports/win32')
-rw-r--r--ports/win32/vs_2019/CMakeLists.txt1
-rw-r--r--ports/win32/vs_2019/inc/tx_port.h93
-rw-r--r--ports/win32/vs_2019/src/tx_initialize_low_level.c246
-rw-r--r--ports/win32/vs_2019/src/tx_thread_context_restore.c55
-rw-r--r--ports/win32/vs_2019/src/tx_thread_context_save.c19
-rw-r--r--ports/win32/vs_2019/src/tx_thread_interrupt_control.c15
-rw-r--r--ports/win32/vs_2019/src/tx_thread_schedule.c139
-rw-r--r--ports/win32/vs_2019/src/tx_thread_stack_build.c89
-rw-r--r--ports/win32/vs_2019/src/tx_thread_system_return.c27
9 files changed, 545 insertions, 139 deletions
diff --git a/ports/win32/vs_2019/CMakeLists.txt b/ports/win32/vs_2019/CMakeLists.txt
index 5a766f06..d2210a8f 100644
--- a/ports/win32/vs_2019/CMakeLists.txt
+++ b/ports/win32/vs_2019/CMakeLists.txt
@@ -15,6 +15,7 @@ target_sources(${PROJECT_NAME}
)
target_include_directories(${PROJECT_NAME}
+ SYSTEM
PUBLIC
${CMAKE_CURRENT_LIST_DIR}/inc
)
diff --git a/ports/win32/vs_2019/inc/tx_port.h b/ports/win32/vs_2019/inc/tx_port.h
index 5dd65653..f878bd0e 100644
--- a/ports/win32/vs_2019/inc/tx_port.h
+++ b/ports/win32/vs_2019/inc/tx_port.h
@@ -9,6 +9,8 @@
* SPDX-License-Identifier: MIT
**************************************************************************/
+// Portions of this file were generated with AI assistance.
+
/**************************************************************************/
/**************************************************************************/
@@ -63,6 +65,7 @@
/* Define compiler library include files. */
+#include <stdint.h>
#include <stdlib.h>
#include <string.h>
@@ -155,47 +158,12 @@ extern TEST_FLAG test_forced_mutex_timeout;
#endif
-
-/* Define performance metric symbols. */
-
-#ifndef TX_BLOCK_POOL_ENABLE_PERFORMANCE_INFO
-#define TX_BLOCK_POOL_ENABLE_PERFORMANCE_INFO
-#endif
-
-#ifndef TX_BYTE_POOL_ENABLE_PERFORMANCE_INFO
-#define TX_BYTE_POOL_ENABLE_PERFORMANCE_INFO
-#endif
-
-#ifndef TX_EVENT_FLAGS_ENABLE_PERFORMANCE_INFO
-#define TX_EVENT_FLAGS_ENABLE_PERFORMANCE_INFO
-#endif
-
-#ifndef TX_MUTEX_ENABLE_PERFORMANCE_INFO
-#define TX_MUTEX_ENABLE_PERFORMANCE_INFO
-#endif
-
-#ifndef TX_QUEUE_ENABLE_PERFORMANCE_INFO
-#define TX_QUEUE_ENABLE_PERFORMANCE_INFO
-#endif
-
-#ifndef TX_SEMAPHORE_ENABLE_PERFORMANCE_INFO
-#define TX_SEMAPHORE_ENABLE_PERFORMANCE_INFO
-#endif
-
-#ifndef TX_THREAD_ENABLE_PERFORMANCE_INFO
-#define TX_THREAD_ENABLE_PERFORMANCE_INFO
-#endif
-
-#ifndef TX_TIMER_ENABLE_PERFORMANCE_INFO
-#define TX_TIMER_ENABLE_PERFORMANCE_INFO
-#endif
-
-/* Enable trace info. */
-
-#ifndef TX_ENABLE_EVENT_TRACE
-#define TX_ENABLE_EVENT_TRACE
-#endif
-
+/* Performance metrics and event trace are left to the application. Set the
+ TX_*_ENABLE_PERFORMANCE_INFO symbols, or TX_ENABLE_EVENT_TRACE, in
+ tx_user.h or on the command line when they are wanted. They extend the
+ control blocks, so a port that turns them on behind the application's
+ back changes structures the application also sees. This port and the
+ win64 one were the only two that did. */
/* Define ThreadX basic types for this port. */
@@ -338,9 +306,10 @@ void _tx_initialize_start_interrupts(void);
#define TX_THREAD_EXTENSION_0 HANDLE tx_thread_win32_thread_handle; \
DWORD tx_thread_win32_thread_id; \
HANDLE tx_thread_win32_thread_run_semaphore; \
+ HANDLE tx_thread_win32_thread_start_semaphore; \
UINT tx_thread_win32_suspension_type; \
UINT tx_thread_win32_int_disabled_flag;
-#define TX_THREAD_EXTENSION_1
+#define TX_THREAD_EXTENSION_1 VOID *tx_thread_extension_ptr;
#define TX_THREAD_EXTENSION_2
#define TX_THREAD_EXTENSION_3
@@ -396,13 +365,27 @@ void _tx_initialize_start_interrupts(void);
#define TX_TIMER_DELETE_EXTENSION(timer_ptr)
+/* Store the owning object pointer in the internal timer so timeout handlers can
+ recover it via a pointer field rather than the ULONG timeout parameter. This
+ matches the Win64 port and satisfies NetXDuo/USBX default extension macros
+ that reference tx_timer_internal_extension_ptr / tx_thread_extension_ptr. */
+
+#define TX_TIMER_INTERNAL_EXTENSION VOID *tx_timer_internal_extension_ptr;
+
+#define TX_THREAD_CREATE_TIMEOUT_SETUP(t) (t) -> tx_thread_timer.tx_timer_internal_timeout_function = &(_tx_thread_timeout); \
+ (t) -> tx_thread_timer.tx_timer_internal_timeout_param = 0; \
+ (t) -> tx_thread_timer.tx_timer_internal_extension_ptr = (VOID *) (t);
+
+#define TX_THREAD_TIMEOUT_POINTER_SETUP(t) (t) = (TX_THREAD *) _tx_timer_expired_timer_ptr -> tx_timer_internal_extension_ptr;
+
+
struct TX_THREAD_STRUCT;
/* Define the Win32 critical section data structure. */
typedef struct TX_WIN32_CRITICAL_SECTION_STRUCT
{
- HANDLE tx_win32_critical_section_mutex_handle;
+ CRITICAL_SECTION tx_win32_critical_section_lock;
DWORD tx_win32_critical_section_owner;
ULONG tx_win32_critical_section_nested_count;
} TX_WIN32_CRITICAL_SECTION;
@@ -500,7 +483,7 @@ VOID _tx_thread_interrupt_restore(UINT previous_posture);
#ifdef TX_THREAD_INIT
CHAR _tx_version_id[] =
- "(c) 2024 Microsoft Corp. (c) 2026-present Eclipse ThreadX contributors. * ThreadX Win32/Visual Studio Version 6.5.1.202602a *";
+ "(c) 2024 Microsoft Corp. (c) 2026-present Eclipse ThreadX contributors. * ThreadX Win32/Visual Studio Version 6.5.2.202603 *";
#else
extern CHAR _tx_version_id[];
#endif
@@ -510,24 +493,40 @@ extern CHAR _tx_version_id[];
extern TX_WIN32_CRITICAL_SECTION _tx_win32_critical_section;
extern HANDLE _tx_win32_scheduler_semaphore;
+extern HANDLE _tx_win32_scheduler_wake_event;
extern DWORD _tx_win32_scheduler_id;
extern ULONG _tx_win32_global_int_disabled_flag;
extern LARGE_INTEGER _tx_win32_time_stamp;
extern ULONG _tx_win32_system_error;
extern HANDLE _tx_win32_timer_handle;
+extern HANDLE _tx_win32_timer_thread_handle;
+extern HANDLE _tx_win32_isr_semaphore;
extern UINT _tx_win32_timer_id;
-extern LARGE_INTEGER _tx_win32_time_stamp;
+extern UINT _tx_win32_timer_waiting;
+#ifdef TX_WIN32_NO_IDLE_ENABLE
+extern HANDLE _tx_win32_timer_kick_event;
+#endif
+
+VOID _tx_win32_scheduler_wake(VOID);
+#ifndef TX_WIN32_USE_HIGH_RESOLUTION_TIMER
+#define TX_WIN32_USE_HIGH_RESOLUTION_TIMER 1
+#endif
+
+#ifndef TX_WIN32_HANDOFF_SPIN_COUNT
+#define TX_WIN32_HANDOFF_SPIN_COUNT 64
+#endif
+
#ifndef TX_WIN32_MEMORY_SIZE
-#define TX_WIN32_MEMORY_SIZE 64000
+#define TX_WIN32_MEMORY_SIZE 256000
#endif
#ifndef TX_TIMER_PERIODIC
#ifdef TX_WIN32_SLOW_TIMER
#define TX_TIMER_PERIODIC TX_WIN32_SLOW_TIMER
#else
-#define TX_TIMER_PERIODIC 10
+#define TX_TIMER_PERIODIC 1
#endif
#endif
diff --git a/ports/win32/vs_2019/src/tx_initialize_low_level.c b/ports/win32/vs_2019/src/tx_initialize_low_level.c
index c362b3e7..82bee742 100644
--- a/ports/win32/vs_2019/src/tx_initialize_low_level.c
+++ b/ports/win32/vs_2019/src/tx_initialize_low_level.c
@@ -1,6 +1,6 @@
/***************************************************************************
* Copyright (c) 2024 Microsoft Corporation
- * Copyright (c) 2026-present Eclipse ThreadX contributors
+ * Copyright (c) 2026 Eclipse ThreadX contributors
*
* This program and the accompanying materials are made available under the
* terms of the MIT License which is available at
@@ -9,7 +9,7 @@
* SPDX-License-Identifier: MIT
**************************************************************************/
-// Some portions generated by Codex (gpt 5.4).
+// Portions of this file were generated with AI assistance.
/**************************************************************************/
/**************************************************************************/
@@ -37,19 +37,40 @@
TX_WIN32_CRITICAL_SECTION _tx_win32_critical_section;
HANDLE _tx_win32_scheduler_semaphore;
+HANDLE _tx_win32_scheduler_wake_event;
DWORD _tx_win32_scheduler_id;
ULONG _tx_win32_global_int_disabled_flag;
LARGE_INTEGER _tx_win32_time_stamp;
ULONG _tx_win32_system_error;
+HANDLE _tx_win32_timer_handle;
+HANDLE _tx_win32_timer_thread_handle;
+HANDLE _tx_win32_isr_semaphore;
+UINT _tx_win32_timer_waiting;
extern TX_THREAD *_tx_thread_current_ptr;
+#ifdef TX_WIN32_NO_IDLE_ENABLE
+/* Auto-reset event used by the scheduler to kick the timer thread so that the
+ simulated clock advances immediately when no thread is ready to run, instead
+ of waiting for the wall-clock periodic timer. This makes the simulation
+ CPU-bound rather than wall-clock-bound during idle periods, mirroring the
+ Linux port's TX_LINUX_NO_IDLE_ENABLE behavior. */
+HANDLE _tx_win32_timer_kick_event;
+#endif
+
+/* Flag set by the atexit handler to stop the timer thread before CRT cleanup
+ suspends any application threads. Declared volatile so both the main thread
+ (which sets it) and the timer thread (which reads it) see the change. */
+volatile LONG _tx_win32_exiting = 0;
+
/* Define simulated timer interrupt. This is done inside a thread, which is
how other interrupts may be defined as well. See code below for an
example. */
UINT _tx_win32_timer_id;
-VOID CALLBACK _tx_win32_timer_interrupt(UINT wTimerID, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2);
+VOID CALLBACK _tx_win32_timer_interrupt(UINT wTimerID, UINT msg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2);
+static VOID _tx_win32_timer_start(VOID);
+static DWORD WINAPI _tx_win32_timer_thread_entry(LPVOID thread_input);
#ifdef TX_WIN32_DEBUG_ENABLE
@@ -151,6 +172,10 @@ void _tx_timer_interrupt(void);
VOID _tx_initialize_low_level(VOID);
VOID _tx_thread_context_save(VOID);
VOID _tx_thread_context_restore(VOID);
+VOID _tx_win32_scheduler_wake(VOID);
+
+/* Forward declaration of the process-exit cleanup function. */
+static void _tx_win32_exit_cleanup(void);
/* Define other external variable references. */
@@ -186,7 +211,7 @@ extern VOID *_tx_initialize_unused_memory;
/* */
/* CALLS */
/* */
-/* CreateMutex Win32 create mutex */
+/* InitializeCriticalSection Win32 initialize lock */
/* CreateThread Win32 create thread */
/* CreateSemaphore Win32 create semaphore */
/* GetCurrentThreadId Win32 get current thread ID */
@@ -226,22 +251,80 @@ VOID _tx_initialize_low_level(VOID)
/* Pickup the unique Id of the current thread, which will also be the Id of the scheduler. */
_tx_win32_scheduler_id = GetCurrentThreadId();
- /* Create the system critical section mutex. This is used by the system to block all other access,
+ /* Initialize the system critical section. This is used by the system to block all other access,
analogous to an interrupt lockout on an embedded target. */
- _tx_win32_critical_section.tx_win32_critical_section_mutex_handle = CreateMutex(NULL, FALSE, NULL);
+ InitializeCriticalSection(&_tx_win32_critical_section.tx_win32_critical_section_lock);
_tx_win32_critical_section.tx_win32_critical_section_nested_count = 0;
_tx_win32_critical_section.tx_win32_critical_section_owner = 0;
/* Create the semaphore that regulates when the scheduler executes. */
_tx_win32_scheduler_semaphore = CreateSemaphore(NULL, 0, 1, NULL);
+ _tx_win32_isr_semaphore = CreateSemaphore(NULL, 0, 1, NULL);
+
+ /* Create the event that wakes the scheduler whenever the ready state changes. */
+ _tx_win32_scheduler_wake_event = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (_tx_win32_scheduler_wake_event == NULL)
+ {
+ printf("ThreadX Win32 error creating scheduler wake event!\n");
+ while(1)
+ {
+ }
+ }
+
+#ifdef TX_WIN32_NO_IDLE_ENABLE
+
+ /* Create the auto-reset event used to kick the timer thread when the
+ scheduler detects an idle system (see _tx_thread_schedule). */
+ _tx_win32_timer_kick_event = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (_tx_win32_timer_kick_event == NULL)
+ {
+ printf("ThreadX Win32 error creating timer kick event!\n");
+ while(1)
+ {
+ }
+ }
+#endif
/* Initialize the global interrupt disabled flag. */
_tx_win32_global_int_disabled_flag = TX_FALSE;
+ _tx_win32_timer_waiting = TX_FALSE;
/* Done, return to caller. */
}
+/* Called by the C runtime during exit() before any CRT cleanup. Sets the
+ exiting flag so the timer thread and context-save code stop touching
+ application OS threads (which may hold the CRT heap lock), then forcibly
+ terminates the timer thread so it cannot fire again during cleanup. */
+
+static void _tx_win32_exit_cleanup(void)
+{
+
+ /* Signal all timer-path code to stop. */
+ _InterlockedExchange(&_tx_win32_exiting, 1);
+
+ /* Unblock the timer thread if it is waiting on the waitable timer. */
+ if (_tx_win32_timer_handle != NULL)
+ {
+ CancelWaitableTimer(_tx_win32_timer_handle);
+ }
+
+ /* Wait up to 50 ms for the timer thread to exit on its own. */
+ if (_tx_win32_timer_thread_handle != NULL)
+ {
+ if (WaitForSingleObject(_tx_win32_timer_thread_handle, 50) != WAIT_OBJECT_0)
+ {
+
+ /* Force-terminate if it has not stopped in time. */
+ TerminateThread(_tx_win32_timer_thread_handle, 0);
+ }
+ _tx_win32_timer_thread_handle = NULL;
+ }
+}
+
+
+
/* This routine is called after initialization is complete in order to start
all interrupt threads. Interrupt threads in addition to the timer may
be added to this routine as well. */
@@ -263,22 +346,161 @@ void _tx_initialize_start_interrupts(void)
wTimerRes = min(max(tc.wPeriodMin, TX_TIMER_PERIODIC), tc.wPeriodMax);
- /* Start a specified timer event. The timer runs in its own thread.
- It calls the specified callback function when the event is activated. */
- _tx_win32_timer_id = timeSetEvent(TX_TIMER_PERIODIC, wTimerRes, _tx_win32_timer_interrupt, 0, TIME_PERIODIC);
+ /* Request the best available timer resolution for the simulation. */
+ if (timeBeginPeriod(wTimerRes) != TIMERR_NOERROR)
+ {
+ printf("ThreadX Win32 error configuring timer resolution!\n");
+ while (1)
+ {
+ }
+ }
+
+ /* Create the periodic waitable timer used to drive simulated interrupts. */
+#if (TX_WIN32_USE_HIGH_RESOLUTION_TIMER != 0)
+ _tx_win32_timer_handle = CreateWaitableTimerEx(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
+ if (_tx_win32_timer_handle == NULL)
+#endif
+ {
+ _tx_win32_timer_handle = CreateWaitableTimer(NULL, FALSE, NULL);
+ }
+
+ if (_tx_win32_timer_handle == NULL)
+ {
+ printf("ThreadX Win32 error creating timer handle!\n");
+ while (1)
+ {
+ }
+ }
+
+ /* Create the timer thread so interrupts are serialized through one execution context. */
+ _tx_win32_timer_thread_handle = CreateThread(NULL, 0, _tx_win32_timer_thread_entry, NULL, 0, NULL);
+ if (_tx_win32_timer_thread_handle == NULL)
+ {
+ printf("ThreadX Win32 error creating timer thread!\n");
+ while (1)
+ {
+ }
+ }
+
+ SetThreadPriority(_tx_win32_timer_thread_handle, THREAD_PRIORITY_HIGHEST);
+
+ _tx_win32_timer_id = 1;
+
+ /* Register exit cleanup so the timer thread is stopped before CRT cleanup
+ runs. Without this, exit() can deadlock: the CRT holds the heap lock
+ while the timer fires and suspends the exiting thread via SuspendThread,
+ causing any subsequent malloc to block forever. */
+ atexit(_tx_win32_exit_cleanup);
+
+ /* Start the first simulated tick. */
+ _tx_win32_timer_start();
}
/* Define the ThreadX system timer interrupt. Other interrupts may be simulated
in a similar way. */
-VOID CALLBACK _tx_win32_timer_interrupt(UINT wTimerID, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2)
+VOID CALLBACK _tx_win32_timer_interrupt(UINT wTimerID, UINT msg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2)
{
+ TX_PARAMETER_NOT_USED(wTimerID);
+ TX_PARAMETER_NOT_USED(msg);
+ TX_PARAMETER_NOT_USED(dwUser);
+ TX_PARAMETER_NOT_USED(dw1);
+ TX_PARAMETER_NOT_USED(dw2);
+
+ /* Skip the interrupt entirely if exit() has been called. The CRT heap
+ lock may be held by the exiting thread; calling SuspendThread() on it
+ at this point causes a permanent deadlock. */
+ if (_tx_win32_exiting)
+ return;
+
/* Call ThreadX context save for interrupt preparation. */
_tx_thread_context_save();
- /* Call the ThreadX system timer interrupt processing. */
- _tx_timer_interrupt();
+ /* Fire TX_WIN32_TICKS_PER_INTERRUPT ticks inside a single interrupt
+ context. The SuspendThread/ResumeThread overhead is amortized across
+ all N ticks, and all timer-based delays shrink by factor N. The
+ relative ordering of thread wakeups is preserved because each call to
+ _tx_timer_interrupt() advances the tick counter by exactly one step and
+ processes the timers that expire at that step. */
+#ifndef TX_WIN32_TICKS_PER_INTERRUPT
+#define TX_WIN32_TICKS_PER_INTERRUPT 5
+#endif
+ {
+ UINT _tick_i;
+ for (_tick_i = 0; _tick_i < TX_WIN32_TICKS_PER_INTERRUPT; _tick_i++)
+ _tx_timer_interrupt();
+ }
/* Call ThreadX context restore for interrupt completion. */
_tx_thread_context_restore();
+
+ /* Wake the scheduler so it can promptly observe timer-driven work. */
+ _tx_win32_scheduler_wake();
+}
+
+
+static DWORD WINAPI _tx_win32_timer_thread_entry(LPVOID thread_input)
+{
+ TX_PARAMETER_NOT_USED(thread_input);
+
+ /* Drive periodic simulated interrupts from a single thread.
+ Exit the loop when _tx_win32_exiting is set by the atexit handler. */
+ while (!_tx_win32_exiting)
+ {
+#ifdef TX_WIN32_NO_IDLE_ENABLE
+
+ /* Wake either on the periodic wall-clock timer or on a scheduler kick
+ (issued when the system is idle). Firing on the kick advances the
+ simulated clock immediately, without waiting for the wall clock. */
+ HANDLE _wait_handles[2];
+
+ _wait_handles[0] = _tx_win32_timer_handle;
+ _wait_handles[1] = _tx_win32_timer_kick_event;
+ if (WaitForMultipleObjects(2, _wait_handles, FALSE, INFINITE) == WAIT_FAILED)
+ break;
+#else
+ if (WaitForSingleObject(_tx_win32_timer_handle, INFINITE) != WAIT_OBJECT_0)
+ break;
+#endif
+ if (_tx_win32_exiting)
+ break;
+ _tx_win32_timer_interrupt(0, 0, 0, 0, 0);
+ if (_tx_win32_exiting)
+ break;
+ _tx_win32_timer_start();
+ }
+ return 0;
+}
+
+
+VOID _tx_win32_scheduler_wake(VOID)
+{
+
+ /* Wake the scheduler if it is waiting for a state change. */
+ if (_tx_win32_scheduler_wake_event != NULL)
+ {
+ SetEvent(_tx_win32_scheduler_wake_event);
+ }
+}
+
+
+static VOID _tx_win32_timer_start(VOID)
+{
+
+LARGE_INTEGER due_time;
+
+
+ /* Rearm the host timer relative to "now" to avoid burst catch-up ticks. */
+ due_time.QuadPart = -(((LONGLONG) TX_TIMER_PERIODIC) * 10000LL);
+#if (TX_WIN32_USE_HIGH_RESOLUTION_TIMER != 0)
+ if (SetWaitableTimerEx(_tx_win32_timer_handle, &due_time, 0, NULL, NULL, NULL, 0) == 0)
+#else
+ if (SetWaitableTimer(_tx_win32_timer_handle, &due_time, 0, NULL, NULL, FALSE) == 0)
+#endif
+ {
+ printf("ThreadX Win32 error starting timer!\n");
+ while (1)
+ {
+ }
+ }
}
diff --git a/ports/win32/vs_2019/src/tx_thread_context_restore.c b/ports/win32/vs_2019/src/tx_thread_context_restore.c
index 36fa1e4b..215489c9 100644
--- a/ports/win32/vs_2019/src/tx_thread_context_restore.c
+++ b/ports/win32/vs_2019/src/tx_thread_context_restore.c
@@ -9,6 +9,8 @@
* SPDX-License-Identifier: MIT
**************************************************************************/
+// Portions of this file were generated with AI assistance.
+
/**************************************************************************/
/**************************************************************************/
@@ -70,6 +72,7 @@
/**************************************************************************/
VOID _tx_thread_context_restore(VOID)
{
+TX_THREAD *execute_thread;
/* Enter critical section to ensure other threads are not playing with
the core ThreadX data structures. */
@@ -81,6 +84,9 @@ VOID _tx_thread_context_restore(VOID)
/* Decrement the nested interrupt count. */
_tx_thread_system_state--;
+ /* Pickup the execute thread pointer. */
+ execute_thread = _tx_thread_execute_ptr;
+
/* Determine if this is the first nested interrupt and if a ThreadX
application thread was running at the time. */
if ((!_tx_thread_system_state) && (_tx_thread_current_ptr))
@@ -109,8 +115,32 @@ VOID _tx_thread_context_restore(VOID)
/* Clear the current thread pointer. */
_tx_thread_current_ptr = TX_NULL;
+ /* Block the timer ISR until the resumed thread has observed the wakeup. */
+ _tx_win32_timer_waiting = TX_TRUE;
+
/* Wakeup the system thread by setting the system semaphore. */
ReleaseSemaphore(_tx_win32_scheduler_semaphore, 1, NULL);
+ _tx_win32_scheduler_wake();
+
+ /* If the timer made a solicited wakeup ready, let that thread run before
+ the host timer ISR continues. */
+ if ((execute_thread != TX_NULL) &&
+ (execute_thread -> tx_thread_win32_suspension_type == 0))
+ {
+
+ /* Spin-poll for the scheduler to complete the solicited wakeup
+ before the timer ISR proceeds. */
+ _tx_win32_critical_section_release_all(&_tx_win32_critical_section);
+ while (WaitForSingleObject(_tx_win32_isr_semaphore, 0) != WAIT_OBJECT_0)
+ SwitchToThread();
+ _tx_win32_critical_section_obtain(&_tx_win32_critical_section);
+ while (WaitForSingleObject(_tx_win32_isr_semaphore, 0) == WAIT_OBJECT_0)
+ {
+ }
+ }
+
+ /* The timer ISR no longer needs to hold off future ticks. */
+ _tx_win32_timer_waiting = TX_FALSE;
}
else
{
@@ -119,8 +149,31 @@ VOID _tx_thread_context_restore(VOID)
ResumeThread(_tx_thread_current_ptr -> tx_thread_win32_thread_handle);
}
}
+ else if ((!_tx_thread_system_state) && (execute_thread != TX_NULL))
+ {
+
+ /* The timer made a thread ready while the scheduler was idle. Keep the
+ timer ISR blocked until the solicited wakeup has started running. */
+ _tx_win32_timer_waiting = TX_TRUE;
+ _tx_win32_scheduler_wake();
+
+ if (execute_thread -> tx_thread_win32_suspension_type == 0)
+ {
+
+ /* Spin-poll for the scheduler to hand off to the next thread and
+ acknowledge via the ISR semaphore. Keeps timer-path latency low. */
+ _tx_win32_critical_section_release_all(&_tx_win32_critical_section);
+ while (WaitForSingleObject(_tx_win32_isr_semaphore, 0) != WAIT_OBJECT_0)
+ SwitchToThread();
+ _tx_win32_critical_section_obtain(&_tx_win32_critical_section);
+ while (WaitForSingleObject(_tx_win32_isr_semaphore, 0) == WAIT_OBJECT_0)
+ {
+ }
+ }
+
+ _tx_win32_timer_waiting = TX_FALSE;
+ }
/* Leave Win32 critical section. */
_tx_win32_critical_section_release_all(&_tx_win32_critical_section);
}
-
diff --git a/ports/win32/vs_2019/src/tx_thread_context_save.c b/ports/win32/vs_2019/src/tx_thread_context_save.c
index 2f4afb40..769c650b 100644
--- a/ports/win32/vs_2019/src/tx_thread_context_save.c
+++ b/ports/win32/vs_2019/src/tx_thread_context_save.c
@@ -1,6 +1,6 @@
/***************************************************************************
* Copyright (c) 2024 Microsoft Corporation
- * Copyright (c) 2026-present Eclipse ThreadX contributors
+ * Copyright (c) 2026 Eclipse ThreadX contributors
*
* This program and the accompanying materials are made available under the
* terms of the MIT License which is available at
@@ -9,6 +9,8 @@
* SPDX-License-Identifier: MIT
**************************************************************************/
+// Portions of this file were generated with AI assistance.
+
/**************************************************************************/
/**************************************************************************/
@@ -30,6 +32,11 @@
#include "tx_thread.h"
#include "tx_timer.h"
+/* Set to non-zero by the atexit handler in tx_initialize_low_level.c when the
+ process is calling exit(). Prevents SuspendThread() from being called on
+ a thread that may be holding the CRT heap lock. */
+extern volatile LONG _tx_win32_exiting;
+
/**************************************************************************/
/* */
@@ -86,6 +93,15 @@ TX_THREAD *thread_ptr;
if ((thread_ptr) && (_tx_thread_system_state == 0))
{
+ /* Skip if the process is calling exit(). Suspending a thread that
+ holds the CRT heap lock causes a permanent deadlock because any
+ subsequent malloc in another thread will block forever. */
+ if (_tx_win32_exiting)
+ {
+ _tx_win32_critical_section_release(&_tx_win32_critical_section);
+ return;
+ }
+
/* Yes, this is the first interrupt and an application thread is running...
suspend it! */
@@ -104,4 +120,3 @@ TX_THREAD *thread_ptr;
/* Exit Win32 critical section. */
_tx_win32_critical_section_release(&_tx_win32_critical_section);
}
-
diff --git a/ports/win32/vs_2019/src/tx_thread_interrupt_control.c b/ports/win32/vs_2019/src/tx_thread_interrupt_control.c
index 019aabef..03a017fb 100644
--- a/ports/win32/vs_2019/src/tx_thread_interrupt_control.c
+++ b/ports/win32/vs_2019/src/tx_thread_interrupt_control.c
@@ -9,6 +9,8 @@
* SPDX-License-Identifier: MIT
**************************************************************************/
+// Portions of this file were generated with AI assistance.
+
/**************************************************************************/
/**************************************************************************/
@@ -93,8 +95,6 @@ UINT _tx_thread_interrupt_control(UINT new_posture)
{
UINT old_posture;
-HANDLE threadhandle;
-int threadpriority;
DWORD threadid;
TX_THREAD *thread_ptr;
@@ -119,22 +119,16 @@ TX_THREAD *thread_ptr;
/* Determine if the thread was terminated. */
- /* Pickup the handle of the current thread. */
- threadhandle = GetCurrentThread();
-
/* Pickup the current thread pointer. */
thread_ptr = _tx_thread_current_ptr;
- /* Pickup the priority of the current thread. */
- threadpriority = GetThreadPriority(threadhandle);
-
/* Pickup the ID of the current thread. */
threadid = GetCurrentThreadId();
/* Determine if this is a thread (THREAD_PRIORITY_LOWEST) and it does not
match the current thread pointer. */
- if ((threadpriority == THREAD_PRIORITY_LOWEST) &&
- ((!thread_ptr) || (thread_ptr -> tx_thread_win32_thread_id != threadid)))
+ if (((!thread_ptr) || (thread_ptr -> tx_thread_win32_thread_id != threadid)) &&
+ (GetThreadPriority(GetCurrentThread()) == THREAD_PRIORITY_LOWEST))
{
/* This indicates the Win32 thread was actually terminated by ThreadX is only
@@ -204,4 +198,3 @@ TX_THREAD *thread_ptr;
/* Return the previous interrupt disable posture. */
return(old_posture);
}
-
diff --git a/ports/win32/vs_2019/src/tx_thread_schedule.c b/ports/win32/vs_2019/src/tx_thread_schedule.c
index d67efee8..e9dc43d7 100644
--- a/ports/win32/vs_2019/src/tx_thread_schedule.c
+++ b/ports/win32/vs_2019/src/tx_thread_schedule.c
@@ -1,6 +1,6 @@
/***************************************************************************
* Copyright (c) 2024 Microsoft Corporation
- * Copyright (c) 2026-present Eclipse ThreadX contributors
+ * Copyright (c) 2026 Eclipse ThreadX contributors
*
* This program and the accompanying materials are made available under the
* terms of the MIT License which is available at
@@ -9,6 +9,8 @@
* SPDX-License-Identifier: MIT
**************************************************************************/
+// Portions of this file were generated with AI assistance.
+
/**************************************************************************/
/**************************************************************************/
@@ -31,6 +33,9 @@
#include "tx_timer.h"
+static VOID _tx_win32_semaphore_reset(HANDLE semaphore_handle);
+
+
/**************************************************************************/
/* */
/* FUNCTION RELEASE */
@@ -59,7 +64,6 @@
/* */
/* ReleaseSemaphore Win32 release semaphore */
/* ResumeThread Win32 resume thread */
-/* Sleep Win32 thread sleep */
/* WaitForSingleObject Win32 wait on a semaphore */
/* _tx_win32_critical_section_obtain Obtain critical section */
/* _tx_win32_critical_section_release Release critical section */
@@ -71,6 +75,8 @@
/**************************************************************************/
VOID _tx_thread_schedule(VOID)
{
+DWORD wait_status;
+
/* Loop forever. */
@@ -102,8 +108,27 @@ VOID _tx_thread_schedule(VOID)
/* Leave the critical section. */
_tx_win32_critical_section_release(&_tx_win32_critical_section);
- /* Now sleep so we don't block forever. */
- Sleep(2);
+#ifdef TX_WIN32_NO_IDLE_ENABLE
+
+ /* No thread is ready to run: advance the simulated clock now
+ instead of waiting for the wall-clock periodic timer. Kick
+ the timer thread so it fires the next tick(s) immediately,
+ then wait for it to signal progress via the wake event. A
+ short bounded timeout guards against a missed wake. This
+ makes idle periods CPU-bound rather than wall-clock-bound,
+ mirroring the Linux port's TX_LINUX_NO_IDLE_ENABLE path. */
+ SetEvent(_tx_win32_timer_kick_event);
+ WaitForSingleObject(_tx_win32_scheduler_wake_event, 2);
+#else
+
+ /* Yield to other threads (timer, application threads) instead of
+ blocking indefinitely. This spin-poll eliminates the ~0.5 ms
+ kernel-wake latency of WaitForSingleObject(INFINITE) and reduces
+ context-switch overhead by >10x at the cost of higher CPU usage
+ during test runs. Drain any pending wake event to keep it clean. */
+ WaitForSingleObject(_tx_win32_scheduler_wake_event, 0);
+ SwitchToThread();
+#endif
}
}
@@ -136,8 +161,38 @@ VOID _tx_thread_schedule(VOID)
/* Debug entry. */
_tx_win32_debug_entry_insert("SCHEDULE-release_sem", __FILE__, __LINE__);
+ /* Clear any stale wakeup acknowledgements before this solicited resume. */
+ _tx_win32_semaphore_reset(_tx_thread_current_ptr -> tx_thread_win32_thread_start_semaphore);
+ _tx_win32_semaphore_reset(_tx_thread_current_ptr -> tx_thread_win32_thread_run_semaphore);
+
/* Let the thread run again by releasing its run semaphore. */
- ReleaseSemaphore(_tx_thread_current_ptr -> tx_thread_win32_thread_run_semaphore, 1, NULL);
+ if (ReleaseSemaphore(_tx_thread_current_ptr -> tx_thread_win32_thread_run_semaphore, 1, NULL) == 0)
+ {
+
+ /* Increment the system error counter. */
+ _tx_win32_system_error++;
+ }
+
+ /* Let the solicited wakeup reach ThreadX before the timer ISR advances again. */
+ if (_tx_win32_timer_waiting)
+ {
+
+ /* Wait for the thread to acknowledge the wakeup and then release the ISR. */
+ wait_status = WaitForSingleObject(_tx_thread_current_ptr -> tx_thread_win32_thread_start_semaphore, INFINITE);
+ if (ReleaseSemaphore(_tx_win32_isr_semaphore, 1, NULL) == 0)
+ {
+
+ /* Increment the system error counter. */
+ _tx_win32_system_error++;
+ }
+
+ if (wait_status != WAIT_OBJECT_0)
+ {
+
+ /* Increment the system error counter. */
+ _tx_win32_system_error++;
+ }
+ }
}
/* Debug entry. */
@@ -146,20 +201,29 @@ VOID _tx_thread_schedule(VOID)
/* Exit Win32 critical section. */
_tx_win32_critical_section_release(&_tx_win32_critical_section);
- /* Now suspend the main thread so the application thread can run. */
- WaitForSingleObject(_tx_win32_scheduler_semaphore, INFINITE);
+ /* Spin-poll for the application thread to return control. Using
+ SwitchToThread() between polls yields the CPU without blocking,
+ matching the low-latency approach used in the idle loop above. */
+ while (WaitForSingleObject(_tx_win32_scheduler_semaphore, 0) != WAIT_OBJECT_0)
+ SwitchToThread();
}
}
-/* Define the ThreadX Win32 critical section get, release, and release all functions. */
-
-void _tx_win32_critical_section_obtain(TX_WIN32_CRITICAL_SECTION *critical_section)
+static VOID _tx_win32_semaphore_reset(HANDLE semaphore_handle)
{
-TX_THREAD *thread_ptr;
+ /* Drain any stale semaphore count from a previous host-side wakeup. */
+ while (WaitForSingleObject(semaphore_handle, 0) == WAIT_OBJECT_0)
+ {
+ }
+}
+/* Define the ThreadX Win32 critical section get, release, and release all functions. */
+
+void _tx_win32_critical_section_obtain(TX_WIN32_CRITICAL_SECTION *critical_section)
+{
/* Is the protection owned? */
if (critical_section -> tx_win32_critical_section_owner == GetCurrentThreadId())
{
@@ -170,15 +234,10 @@ TX_THREAD *thread_ptr;
else
{
- /* Pickup the current thread pointer. */
- thread_ptr = _tx_thread_current_ptr;
-
/* Get the Win32 critical section. */
- while (WaitForSingleObject(critical_section -> tx_win32_critical_section_mutex_handle, 3) != WAIT_OBJECT_0)
- {
- }
+ EnterCriticalSection(&critical_section -> tx_win32_critical_section_lock);
- /* At this point we have the mutex. */
+ /* At this point we have the critical section. */
/* Increment the nesting counter. */
critical_section -> tx_win32_critical_section_nested_count = 1;
@@ -193,7 +252,7 @@ void _tx_win32_critical_section_release(TX_WIN32_CRITICAL_SECTION *critical_s
{
- /* Ensure the caller is the mutex owner. */
+ /* Ensure the caller is the critical section owner. */
if (critical_section -> tx_win32_critical_section_owner == GetCurrentThreadId())
{
@@ -211,25 +270,9 @@ void _tx_win32_critical_section_release(TX_WIN32_CRITICAL_SECTION *critical_s
/* Yes, it is being released clear the owner. */
critical_section -> tx_win32_critical_section_owner = 0;
- /* Finally, release the mutex. */
- if (ReleaseMutex(critical_section -> tx_win32_critical_section_mutex_handle) != TX_TRUE)
- {
-
- /* Increment the system error counter. */
- _tx_win32_system_error++;
- }
-
- /* Just in case, make sure there the mutex is not owned. */
- while (ReleaseMutex(critical_section -> tx_win32_critical_section_mutex_handle) == TX_TRUE)
- {
-
- /* Increment the system error counter. */
- _tx_win32_system_error++;
- }
-
- /* Sleep for 0, just to relinquish to other ready threads. */
- Sleep(0);
- }
+ /* Finally, release the critical section. */
+ LeaveCriticalSection(&critical_section -> tx_win32_critical_section_lock);
+ }
}
}
else
@@ -244,7 +287,7 @@ void _tx_win32_critical_section_release(TX_WIN32_CRITICAL_SECTION *critical_s
void _tx_win32_critical_section_release_all(TX_WIN32_CRITICAL_SECTION *critical_section)
{
- /* Ensure the caller is the mutex owner. */
+ /* Ensure the caller is the critical section owner. */
if (critical_section -> tx_win32_critical_section_owner == GetCurrentThreadId())
{
@@ -258,21 +301,8 @@ void _tx_win32_critical_section_release_all(TX_WIN32_CRITICAL_SECTION *cri
/* Yes, it is being release clear the owner. */
critical_section -> tx_win32_critical_section_owner = 0;
- /* Finally, release the mutex. */
- if (ReleaseMutex(critical_section -> tx_win32_critical_section_mutex_handle) != TX_TRUE)
- {
-
- /* Increment the system error counter. */
- _tx_win32_system_error++;
- }
-
- /* Just in case, make sure there the mutex is not owned. */
- while (ReleaseMutex(critical_section -> tx_win32_critical_section_mutex_handle) == TX_TRUE)
- {
-
- /* Increment the system error counter. */
- _tx_win32_system_error++;
- }
+ /* Finally, release the critical section. */
+ LeaveCriticalSection(&critical_section -> tx_win32_critical_section_lock);
}
}
else
@@ -282,4 +312,3 @@ void _tx_win32_critical_section_release_all(TX_WIN32_CRITICAL_SECTION *cri
_tx_win32_system_error++;
}
}
-
diff --git a/ports/win32/vs_2019/src/tx_thread_stack_build.c b/ports/win32/vs_2019/src/tx_thread_stack_build.c
index da51e7c4..7b6e139b 100644
--- a/ports/win32/vs_2019/src/tx_thread_stack_build.c
+++ b/ports/win32/vs_2019/src/tx_thread_stack_build.c
@@ -9,6 +9,8 @@
* SPDX-License-Identifier: MIT
**************************************************************************/
+// Portions of this file were generated with AI assistance.
+
/**************************************************************************/
/**************************************************************************/
@@ -75,6 +77,7 @@ DWORD WINAPI _tx_win32_thread_entry(LPVOID p);
/**************************************************************************/
VOID _tx_thread_stack_build(TX_THREAD *thread_ptr, VOID (*function_ptr)(VOID))
{
+ALIGN_TYPE fake_stack_ptr;
/* Create a Win32 thread for the application thread. */
thread_ptr -> tx_thread_win32_thread_handle =
@@ -111,6 +114,20 @@ VOID _tx_thread_stack_build(TX_THREAD *thread_ptr, VOID (*function_ptr)(VOID))
}
}
+ /* Create the scheduler acknowledgement semaphore for this thread. */
+ thread_ptr -> tx_thread_win32_thread_start_semaphore = CreateSemaphore(NULL, 0, 1, NULL);
+
+ /* Determine if the start semaphore was created successfully. */
+ if (!thread_ptr -> tx_thread_win32_thread_start_semaphore)
+ {
+
+ /* Display an error message. */
+ printf("ThreadX Win32 error creating thread start semaphore!\n");
+ while(1)
+ {
+ }
+ }
+
/* Setup the thread suspension type to solicited thread suspension.
Pseudo interrupt handlers will suspend with this field set to 1. */
thread_ptr -> tx_thread_win32_suspension_type = 0;
@@ -119,8 +136,13 @@ VOID _tx_thread_stack_build(TX_THREAD *thread_ptr, VOID (*function_ptr)(VOID))
tx_interrupt_control nesting. */
thread_ptr -> tx_thread_win32_int_disabled_flag = 0;
- /* Setup a fake thread stack pointer. */
- thread_ptr -> tx_thread_stack_ptr = (VOID *) (((CHAR *) thread_ptr -> tx_thread_stack_end) - 8);
+ /* Setup a fake thread stack pointer. The stack end points at the last byte of the
+ thread's stack area and is therefore not necessarily aligned, so round the result
+ down to a ULONG boundary. This pointer is dereferenced as a ULONG below and is
+ also the starting value of the stack checking logic's highest used pointer. */
+ fake_stack_ptr = (ALIGN_TYPE) ((VOID *) (((CHAR *) thread_ptr -> tx_thread_stack_end) - 8));
+ fake_stack_ptr = fake_stack_ptr & (~((ALIGN_TYPE) (sizeof(ULONG) - 1)));
+ thread_ptr -> tx_thread_stack_ptr = (VOID *) ((ALIGN_TYPE) fake_stack_ptr);
/* Clear the first word of the stack. */
*(((ULONG *) thread_ptr -> tx_thread_stack_ptr) - 1) = 0;
@@ -128,24 +150,77 @@ VOID _tx_thread_stack_build(TX_THREAD *thread_ptr, VOID (*function_ptr)(VOID))
/* Make the thread initially ready so it will run to the initial wait on
its run semaphore. */
ResumeThread(thread_ptr -> tx_thread_win32_thread_handle);
+
+ /* Wait until the host thread is parked at the controlled handoff point
+ before ThreadX can schedule it. */
+ if (WaitForSingleObject(thread_ptr -> tx_thread_win32_thread_start_semaphore, INFINITE) != WAIT_OBJECT_0)
+ {
+
+ /* Display an error message. */
+ printf("ThreadX Win32 error synchronizing thread startup!\n");
+ while(1)
+ {
+ }
+ }
}
DWORD WINAPI _tx_win32_thread_entry(LPVOID ptr)
{
-TX_THREAD *thread_ptr;
+TX_THREAD *thread_ptr;
+TX_THREAD *current_thread_ptr;
+HANDLE threadhandle;
+int threadpriority;
+DWORD threadid;
+ULONG handoff_spin_count;
/* Pickup the current thread pointer. */
thread_ptr = (TX_THREAD *) ptr;
- /* Now suspend the thread initially. If the thread has already
- been scheduled, this will return immediately. */
- WaitForSingleObject(thread_ptr -> tx_thread_win32_thread_run_semaphore, INFINITE);
+ /* Tell the creator that this host thread has reached the controlled
+ handoff point and is ready to be scheduled. */
+ ReleaseSemaphore(thread_ptr -> tx_thread_win32_thread_start_semaphore, 1, NULL);
+
+ /* Spin briefly for the scheduler to release this thread to run, then
+ block so dormant threads do not consume host CPU indefinitely. */
+ handoff_spin_count = TX_WIN32_HANDOFF_SPIN_COUNT;
+ while (WaitForSingleObject(thread_ptr -> tx_thread_win32_thread_run_semaphore, 0) != WAIT_OBJECT_0)
+ {
+ if (handoff_spin_count != 0)
+ {
+ handoff_spin_count--;
+ SwitchToThread();
+ }
+ else
+ {
+ WaitForSingleObject(thread_ptr -> tx_thread_win32_thread_run_semaphore, INFINITE);
+ break;
+ }
+ }
+
+ /* Acknowledge that the host thread is now able to execute ThreadX code. */
+ ReleaseSemaphore(thread_ptr -> tx_thread_win32_thread_start_semaphore, 1, NULL);
+
+ /* A deleted host thread can be released only to let it exit. In notify-enabled
+ builds, the first TX_DISABLE in _tx_thread_shell_entry catches this path.
+ When callbacks are disabled, perform the same check before the shell calls
+ the stale ThreadX entry function. */
+ _tx_win32_critical_section_obtain(&_tx_win32_critical_section);
+ threadhandle = GetCurrentThread();
+ threadpriority = GetThreadPriority(threadhandle);
+ threadid = GetCurrentThreadId();
+ current_thread_ptr = _tx_thread_current_ptr;
+ if ((threadpriority == THREAD_PRIORITY_LOWEST) &&
+ ((current_thread_ptr == TX_NULL) || (current_thread_ptr -> tx_thread_win32_thread_id != threadid)))
+ {
+ _tx_win32_critical_section_release_all(&_tx_win32_critical_section);
+ ExitThread(0);
+ }
+ _tx_win32_critical_section_release(&_tx_win32_critical_section);
/* Call ThreadX thread entry point. */
_tx_thread_shell_entry();
return EXIT_SUCCESS;
}
-
diff --git a/ports/win32/vs_2019/src/tx_thread_system_return.c b/ports/win32/vs_2019/src/tx_thread_system_return.c
index 60176135..a14c326e 100644
--- a/ports/win32/vs_2019/src/tx_thread_system_return.c
+++ b/ports/win32/vs_2019/src/tx_thread_system_return.c
@@ -9,6 +9,8 @@
* SPDX-License-Identifier: MIT
**************************************************************************/
+// Portions of this file were generated with AI assistance.
+
/**************************************************************************/
/**************************************************************************/
@@ -83,6 +85,7 @@ UINT temp_thread_state;
HANDLE threadhandle;
int threadpriority;
DWORD threadid;
+ULONG handoff_spin_count;
/* Enter Win32 critical section. */
@@ -149,6 +152,7 @@ DWORD threadid;
on. Note that the main scheduling algorithm will take care of
setting the current thread pointer to NULL. */
ReleaseSemaphore(_tx_win32_scheduler_semaphore, 1, NULL);
+ _tx_win32_scheduler_wake();
/* Leave Win32 critical section. */
_tx_win32_critical_section_release_all(&_tx_win32_critical_section);
@@ -161,9 +165,25 @@ DWORD threadid;
ExitThread(0);
}
- /* Wait on the run semaphore for this thread. This won't get set again
- until the thread is scheduled. */
- WaitForSingleObject(temp_run_semaphore, INFINITE);
+ /* Spin briefly for the scheduler to grant this thread a new time-slice,
+ then block so suspended threads do not consume host CPU indefinitely. */
+ handoff_spin_count = TX_WIN32_HANDOFF_SPIN_COUNT;
+ while (WaitForSingleObject(temp_run_semaphore, 0) != WAIT_OBJECT_0)
+ {
+ if (handoff_spin_count != 0)
+ {
+ handoff_spin_count--;
+ SwitchToThread();
+ }
+ else
+ {
+ WaitForSingleObject(temp_run_semaphore, INFINITE);
+ break;
+ }
+ }
+
+ /* Acknowledge that the thread is once again executing ThreadX code. */
+ ReleaseSemaphore(temp_thread_ptr -> tx_thread_win32_thread_start_semaphore, 1, NULL);
/* Enter Win32 critical section. */
_tx_win32_critical_section_obtain(&_tx_win32_critical_section);
@@ -203,4 +223,3 @@ DWORD threadid;
_tx_win32_critical_section_release(&_tx_win32_critical_section);
}
}
-