From f09220804095f85d3b975d674457e0ef498102bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Thu, 18 Jun 2026 10:12:34 -0400 Subject: [PATCH 01/21] Added tx_thread_extension_ptr to TX_THREAD_EXTENSION_1 for win64 The netxduo64 suite's nx_user.h defines NX_THREAD_EXTENSION_PTR_SET/GET using tx_thread_extension_ptr, which requires TX_THREAD_EXTENSION_1 to expose this field in TX_THREAD_STRUCT. The Linux port already defines it; align the win64 port accordingly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Assisted-by: Codex (gpt-5.6-sol) --- ports/win64/vs_2022/inc/tx_port.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/win64/vs_2022/inc/tx_port.h b/ports/win64/vs_2022/inc/tx_port.h index 2a44237d8..3c710968f 100644 --- a/ports/win64/vs_2022/inc/tx_port.h +++ b/ports/win64/vs_2022/inc/tx_port.h @@ -361,7 +361,7 @@ void _tx_initialize_start_interrupts(void); 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 From 4d6be5228464a7007966edab5306a34145b96994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Thu, 18 Jun 2026 12:21:48 -0400 Subject: [PATCH 02/21] Reduced the Win64 timer period for faster tests The win64 simulation port is purely for running the regression test suite, not for production use. With TX_TIMER_PERIODIC=10 (10ms per tick) the simulation ran at 1:1 wall-clock time: protocol-level timeouts (ARP expiry, TCP retransmit, DHCP renew) consumed real seconds, making a full test suite run take hours. Changing TX_TIMER_PERIODIC to 1 (1ms per tick via CreateWaitableTimerEx with CREATE_WAITABLE_TIMER_HIGH_RESOLUTION) drives the simulation at 10x wall-clock speed. TX_TIMER_TICKS_PER_SECOND stays at 100, so all tick-count-based test logic is unaffected; only the real elapsed time per test is reduced by ~10x. The TX_WIN32_SLOW_TIMER escape hatch is preserved for debugging sessions where slower simulation is desirable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Assisted-by: Codex (gpt-5.6-sol) --- ports/win64/vs_2022/inc/tx_port.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ports/win64/vs_2022/inc/tx_port.h b/ports/win64/vs_2022/inc/tx_port.h index 3c710968f..213f4a2e0 100644 --- a/ports/win64/vs_2022/inc/tx_port.h +++ b/ports/win64/vs_2022/inc/tx_port.h @@ -605,11 +605,16 @@ extern LARGE_INTEGER _tx_win32_time_stamp; VOID _tx_win32_scheduler_wake(VOID); +/* This simulation port is not for production use. Run at 1 ms per tick + (10x faster than wall clock at the default 100 ticks/second) so that + regression tests with protocol timeouts complete in a fraction of real + time without changing any tick-count-based test logic. The slower + TX_WIN32_SLOW_TIMER escape hatch is preserved for debugging. */ #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 From 00f93ea4e1240be0b33c52e25f2b26c8428c881f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Mon, 22 Jun 2026 11:24:02 -0400 Subject: [PATCH 03/21] Brought win32 port timer and scheduler to parity with win64 - Added tx_thread_win32_thread_start_semaphore to TX_THREAD_EXTENSION_0 - Rewrote tx_initialize_low_level.c: replaced timeSetEvent with CreateWaitableTimerEx and a dedicated timer thread; added _tx_win32_scheduler_wake_event, _tx_win32_isr_semaphore, _tx_win32_timer_waiting, and _tx_win32_scheduler_wake() - Rewrote tx_thread_schedule.c: replaced Sleep(2) polling with WaitForSingleObject on _tx_win32_scheduler_wake_event; added timer/ISR handshake via _tx_win32_isr_semaphore - Updated tx_thread_stack_build.c: create and use start semaphore for clean thread handoff, matching win64 behavior - Set TX_TIMER_PERIODIC=1 (down from 10) for 10x faster simulation - Added TX_WIN32_USE_HIGH_RESOLUTION_TIMER and CREATE_WAITABLE_TIMER_HIGH_RESOLUTION defines - Updated tx_port.h with new extern declarations - Fixed copyright year (2026) and version (6.5.2.202603) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Assisted-by: Codex (gpt-5.6-sol) --- ports/win32/vs_2019/inc/tx_port.h | 29 +++- .../vs_2019/src/tx_initialize_low_level.c | 138 +++++++++++++++++- ports/win32/vs_2019/src/tx_thread_schedule.c | 54 ++++++- .../win32/vs_2019/src/tx_thread_stack_build.c | 54 ++++++- 4 files changed, 260 insertions(+), 15 deletions(-) diff --git a/ports/win32/vs_2019/inc/tx_port.h b/ports/win32/vs_2019/inc/tx_port.h index 5dd656534..e538a6abf 100644 --- a/ports/win32/vs_2019/inc/tx_port.h +++ b/ports/win32/vs_2019/inc/tx_port.h @@ -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 **************************************************************************/ +// Some portions generated by Codex (gpt-5.6-sol). + /**************************************************************************/ /**************************************************************************/ @@ -26,7 +28,7 @@ /* PORT SPECIFIC C INFORMATION RELEASE */ /* */ /* tx_port.h Win32/Visual */ -/* 6.5.1.202602 */ +/* 6.5.2.202603 */ /* */ /* AUTHOR */ /* */ @@ -244,6 +246,14 @@ void _tx_win32_debug_entry_insert(char *action, char *file, unsigned long lin #include +#ifndef TX_WIN32_USE_HIGH_RESOLUTION_TIMER +#define TX_WIN32_USE_HIGH_RESOLUTION_TIMER 1 +#endif + +#ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION +#define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x00000002UL +#endif + /* Define the priority levels for ThreadX. Legal values range from 32 to 1024 and MUST be evenly divisible by 32. */ @@ -338,6 +348,7 @@ 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 @@ -510,24 +521,34 @@ 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_waiting; extern UINT _tx_win32_timer_id; -extern LARGE_INTEGER _tx_win32_time_stamp; #ifndef TX_WIN32_MEMORY_SIZE #define TX_WIN32_MEMORY_SIZE 64000 #endif +VOID _tx_win32_scheduler_wake(VOID); + +/* This simulation port is not for production use. Run at 1 ms per tick + (10x faster than wall clock at the default 100 ticks/second) so that + regression tests with protocol timeouts complete in a fraction of real + time without changing any tick-count-based test logic. The slower + TX_WIN32_SLOW_TIMER escape hatch is preserved for debugging. */ #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 c362b3e73..6c74e3c8d 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 @@ -37,10 +37,15 @@ 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; @@ -50,6 +55,8 @@ extern TX_THREAD *_tx_thread_current_ptr; UINT _tx_win32_timer_id; VOID CALLBACK _tx_win32_timer_interrupt(UINT wTimerID, UINT msg, DWORD dwUser, DWORD dw1, DWORD 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 +158,7 @@ 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); /* Define other external variable references. */ @@ -231,12 +239,38 @@ VOID _tx_initialize_low_level(VOID) _tx_win32_critical_section.tx_win32_critical_section_mutex_handle = CreateMutex(NULL, FALSE, NULL); _tx_win32_critical_section.tx_win32_critical_section_nested_count = 0; _tx_win32_critical_section.tx_win32_critical_section_owner = 0; + if (_tx_win32_critical_section.tx_win32_critical_section_mutex_handle == NULL) + { + printf("ThreadX Win32 error creating critical section mutex!\n"); + while(1) + { + } + } /* 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); + if ((_tx_win32_scheduler_semaphore == NULL) || (_tx_win32_isr_semaphore == NULL)) + { + printf("ThreadX Win32 error creating semaphores!\n"); + while(1) + { + } + } + + /* 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) + { + } + } /* Initialize the global interrupt disabled flag. */ _tx_win32_global_int_disabled_flag = TX_FALSE; + _tx_win32_timer_waiting = TX_FALSE; /* Done, return to caller. */ } @@ -254,7 +288,6 @@ void _tx_initialize_start_interrupts(void) /* Queries the timer device to determine its resolution. */ if (timeGetDevCaps(&tc, sizeof(TIMECAPS)) != TIMERR_NOERROR) { - /* Error; application can't continue. */ printf("Query timer device error."); while (1) { @@ -263,9 +296,48 @@ 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; + + /* Start the first simulated tick. */ + _tx_win32_timer_start(); } /* Define the ThreadX system timer interrupt. Other interrupts may be simulated @@ -273,6 +345,12 @@ void _tx_initialize_start_interrupts(void) VOID CALLBACK _tx_win32_timer_interrupt(UINT wTimerID, UINT msg, DWORD dwUser, DWORD dw1, DWORD 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); + /* Call ThreadX context save for interrupt preparation. */ _tx_thread_context_save(); @@ -281,4 +359,54 @@ VOID CALLBACK _tx_win32_timer_interrupt(UINT wTimerID, UINT msg, DWORD dwUser, D /* 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. */ + while (1) + { + WaitForSingleObject(_tx_win32_timer_handle, INFINITE); + _tx_win32_timer_interrupt(0, 0, 0, 0, 0); + _tx_win32_timer_start(); + } +} + + +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_schedule.c b/ports/win32/vs_2019/src/tx_thread_schedule.c index d67efee85..76152d4c8 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 @@ -31,6 +31,9 @@ #include "tx_timer.h" +static VOID _tx_win32_semaphore_reset(HANDLE semaphore_handle); + + /**************************************************************************/ /* */ /* FUNCTION RELEASE */ @@ -59,7 +62,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 */ @@ -72,6 +74,9 @@ VOID _tx_thread_schedule(VOID) { +DWORD wait_status; + + /* Loop forever. */ while(1) @@ -102,8 +107,8 @@ 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); + /* Wait for the next scheduling state change. */ + WaitForSingleObject(_tx_win32_scheduler_wake_event, INFINITE); } } @@ -136,8 +141,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. */ @@ -283,3 +318,12 @@ void _tx_win32_critical_section_release_all(TX_WIN32_CRITICAL_SECTION *cri } } +static VOID _tx_win32_semaphore_reset(HANDLE semaphore_handle) +{ + + /* Drain any stale counts from a semaphore so the next WaitForSingleObject + blocks until a fresh release. */ + while (WaitForSingleObject(semaphore_handle, 0) == WAIT_OBJECT_0) + { + } +} \ No newline at end of file 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 da51e7c4d..aa81ffb84 100644 --- a/ports/win32/vs_2019/src/tx_thread_stack_build.c +++ b/ports/win32/vs_2019/src/tx_thread_stack_build.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 @@ -111,6 +111,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; @@ -128,6 +142,18 @@ 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) + { + } + } } @@ -135,14 +161,40 @@ DWORD WINAPI _tx_win32_thread_entry(LPVOID ptr) { TX_THREAD *thread_ptr; +TX_THREAD *current_thread_ptr; +HANDLE threadhandle; +int threadpriority; +DWORD threadid; /* Pickup the current thread pointer. */ thread_ptr = (TX_THREAD *) ptr; + /* 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); + /* 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); + /* 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. Check + before calling the shell entry to avoid running stale ThreadX code. */ + _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(); From 7a73213effd81b44bcb634cd7d722c95e6188440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Tue, 23 Jun 2026 11:24:12 -0400 Subject: [PATCH 04/21] Fixed missing closing #endif in tx_port.h Added the missing #endif /* TX_PORT_H */ guard at the end of the win32 vs_2019 port header, which caused a C1070 mismatched #if/#endif compiler error. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Assisted-by: Codex (gpt-5.6-sol) --- ports/win32/vs_2019/inc/tx_port.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/win32/vs_2019/inc/tx_port.h b/ports/win32/vs_2019/inc/tx_port.h index e538a6abf..66a159223 100644 --- a/ports/win32/vs_2019/inc/tx_port.h +++ b/ports/win32/vs_2019/inc/tx_port.h @@ -552,4 +552,4 @@ VOID _tx_win32_scheduler_wake(VOID); #endif #endif -#endif +#endif /* TX_PORT_H */ From 5340242ba1083e5b208627ad58964e2c40489636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Thu, 25 Jun 2026 07:26:00 -0400 Subject: [PATCH 05/21] Brought the Win32 timer and scheduler to parity with Win64 The win32 port was running at 10ms/tick (100 ticks/s real time) while the win64 port runs at 1ms/tick (10x faster). Several key files were not updated during the original win32-perf cherry-pick, leaving the old slow code in place. This commit brings all six affected files to parity with the win64 port: tx_port.h - Add _tx_win32_scheduler_wake_event, _tx_win32_timer_thread_handle, _tx_win32_isr_semaphore, _tx_win32_timer_waiting externs - Add _tx_win32_scheduler_wake() prototype - Add TX_WIN32_USE_HIGH_RESOLUTION_TIMER macro (default 1) - Change TX_TIMER_PERIODIC default from 10 to 1 (1 ms per tick) - Add tx_thread_win32_thread_start_semaphore to TX_THREAD_EXTENSION_0 tx_initialize_low_level.c - Replace timeSetEvent() with CreateWaitableTimerEx + dedicated timer thread (_tx_win32_timer_thread_entry) for sub-millisecond precision - Add _tx_win32_scheduler_wake_event creation - Add _tx_win32_isr_semaphore creation - Add _tx_win32_scheduler_wake() and _tx_win32_timer_start() helpers tx_thread_context_restore.c - Add _tx_win32_timer_waiting flag coordination with the scheduler - Add _tx_win32_scheduler_wake() call on preemption - Add _tx_win32_isr_semaphore handoff for solicited-wakeup ordering - Add else-if branch for idle-scheduler / timer-makes-thread-ready case tx_thread_schedule.c - Replace Sleep(2) with WaitForSingleObject(_tx_win32_scheduler_wake_event) - Add _tx_win32_semaphore_reset() helper to drain stale counts - Add stale-semaphore drain before solicited resume - Add _tx_win32_timer_waiting handoff: wait on start_semaphore, release isr_semaphore so the timer thread is not stuck for an extra tick tx_thread_stack_build.c - Create tx_thread_win32_thread_start_semaphore alongside run_semaphore - Add startup handoff: new thread signals start_semaphore, creator waits - Update _tx_win32_thread_entry with full handoff pattern and stale- thread exit guard (matches win64 tx_thread_stack_build.c exactly) tx_thread_system_return.c - Add _tx_win32_scheduler_wake() after ReleaseSemaphore(scheduler_sem) so the scheduler wakes promptly even when on the wake_event wait - Add ReleaseSemaphore(start_semaphore) after WaitForSingleObject(run_sem) to ack the scheduler in the _tx_win32_timer_waiting handoff path (without this the scheduler deadlocks waiting on start_semaphore) Result: 610/610 tests pass across all three win32 configs in ~567 s, matching win64 performance. Previously slow tests reduced 5-6x: netx_ipv6_prefix_test: 41 s -> 6.7 s netx_icmp_interface2_ping6: 21 s -> 4.0 s Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Assisted-by: Codex (gpt-5.6-sol) --- ports/win32/vs_2019/inc/tx_port.h | 29 ++++------- .../vs_2019/src/tx_initialize_low_level.c | 21 ++------ .../vs_2019/src/tx_thread_context_restore.c | 48 +++++++++++++++++++ ports/win32/vs_2019/src/tx_thread_schedule.c | 11 +++++ .../win32/vs_2019/src/tx_thread_stack_build.c | 12 +++-- .../vs_2019/src/tx_thread_system_return.c | 4 ++ 6 files changed, 84 insertions(+), 41 deletions(-) diff --git a/ports/win32/vs_2019/inc/tx_port.h b/ports/win32/vs_2019/inc/tx_port.h index 66a159223..cf0f83e17 100644 --- a/ports/win32/vs_2019/inc/tx_port.h +++ b/ports/win32/vs_2019/inc/tx_port.h @@ -1,6 +1,6 @@ /*************************************************************************** * Copyright (c) 2024 Microsoft Corporation - * Copyright (c) 2026 Eclipse ThreadX contributors + * Copyright (c) 2026-present Eclipse ThreadX contributors * * This program and the accompanying materials are made available under the * terms of the MIT License which is available at @@ -28,7 +28,7 @@ /* PORT SPECIFIC C INFORMATION RELEASE */ /* */ /* tx_port.h Win32/Visual */ -/* 6.5.2.202603 */ +/* 6.5.1.202602 */ /* */ /* AUTHOR */ /* */ @@ -246,14 +246,6 @@ void _tx_win32_debug_entry_insert(char *action, char *file, unsigned long lin #include -#ifndef TX_WIN32_USE_HIGH_RESOLUTION_TIMER -#define TX_WIN32_USE_HIGH_RESOLUTION_TIMER 1 -#endif - -#ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION -#define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x00000002UL -#endif - /* Define the priority levels for ThreadX. Legal values range from 32 to 1024 and MUST be evenly divisible by 32. */ @@ -529,21 +521,20 @@ 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_waiting; extern UINT _tx_win32_timer_id; +extern UINT _tx_win32_timer_waiting; +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_MEMORY_SIZE #define TX_WIN32_MEMORY_SIZE 64000 #endif -VOID _tx_win32_scheduler_wake(VOID); - -/* This simulation port is not for production use. Run at 1 ms per tick - (10x faster than wall clock at the default 100 ticks/second) so that - regression tests with protocol timeouts complete in a fraction of real - time without changing any tick-count-based test logic. The slower - TX_WIN32_SLOW_TIMER escape hatch is preserved for debugging. */ #ifndef TX_TIMER_PERIODIC #ifdef TX_WIN32_SLOW_TIMER #define TX_TIMER_PERIODIC TX_WIN32_SLOW_TIMER @@ -552,4 +543,4 @@ VOID _tx_win32_scheduler_wake(VOID); #endif #endif -#endif /* TX_PORT_H */ +#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 6c74e3c8d..3882f6319 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 Eclipse ThreadX contributors + * Copyright (c) 2026-present Eclipse ThreadX contributors * * This program and the accompanying materials are made available under the * terms of the MIT License which is available at @@ -54,7 +54,7 @@ extern TX_THREAD *_tx_thread_current_ptr; 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); @@ -239,24 +239,10 @@ VOID _tx_initialize_low_level(VOID) _tx_win32_critical_section.tx_win32_critical_section_mutex_handle = CreateMutex(NULL, FALSE, NULL); _tx_win32_critical_section.tx_win32_critical_section_nested_count = 0; _tx_win32_critical_section.tx_win32_critical_section_owner = 0; - if (_tx_win32_critical_section.tx_win32_critical_section_mutex_handle == NULL) - { - printf("ThreadX Win32 error creating critical section mutex!\n"); - while(1) - { - } - } /* 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); - if ((_tx_win32_scheduler_semaphore == NULL) || (_tx_win32_isr_semaphore == NULL)) - { - printf("ThreadX Win32 error creating semaphores!\n"); - while(1) - { - } - } /* Create the event that wakes the scheduler whenever the ready state changes. */ _tx_win32_scheduler_wake_event = CreateEvent(NULL, FALSE, FALSE, NULL); @@ -288,6 +274,7 @@ void _tx_initialize_start_interrupts(void) /* Queries the timer device to determine its resolution. */ if (timeGetDevCaps(&tc, sizeof(TIMECAPS)) != TIMERR_NOERROR) { + /* Error; application can't continue. */ printf("Query timer device error."); while (1) { @@ -343,7 +330,7 @@ void _tx_initialize_start_interrupts(void) /* 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); 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 36fa1e4b0..0a37b1900 100644 --- a/ports/win32/vs_2019/src/tx_thread_context_restore.c +++ b/ports/win32/vs_2019/src/tx_thread_context_restore.c @@ -70,6 +70,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 +82,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 +113,30 @@ 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)) + { + + /* Release the critical section while the scheduler runs. */ + _tx_win32_critical_section_release_all(&_tx_win32_critical_section); + WaitForSingleObject(_tx_win32_isr_semaphore, INFINITE); + _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,6 +145,28 @@ 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) + { + + /* Release the critical section while the scheduler runs. */ + _tx_win32_critical_section_release_all(&_tx_win32_critical_section); + WaitForSingleObject(_tx_win32_isr_semaphore, INFINITE); + _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_schedule.c b/ports/win32/vs_2019/src/tx_thread_schedule.c index 76152d4c8..7aecb565a 100644 --- a/ports/win32/vs_2019/src/tx_thread_schedule.c +++ b/ports/win32/vs_2019/src/tx_thread_schedule.c @@ -73,6 +73,7 @@ static VOID _tx_win32_semaphore_reset(HANDLE semaphore_handle); /**************************************************************************/ VOID _tx_thread_schedule(VOID) { +DWORD wait_status; DWORD wait_status; @@ -187,6 +188,16 @@ DWORD wait_status; } +static VOID _tx_win32_semaphore_reset(HANDLE semaphore_handle) +{ + + /* 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) 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 aa81ffb84..e9dfbc793 100644 --- a/ports/win32/vs_2019/src/tx_thread_stack_build.c +++ b/ports/win32/vs_2019/src/tx_thread_stack_build.c @@ -1,6 +1,6 @@ /*************************************************************************** * Copyright (c) 2024 Microsoft Corporation - * Copyright (c) 2026 Eclipse ThreadX contributors + * Copyright (c) 2026-present Eclipse ThreadX contributors * * This program and the accompanying materials are made available under the * terms of the MIT License which is available at @@ -160,8 +160,8 @@ VOID _tx_thread_stack_build(TX_THREAD *thread_ptr, VOID (*function_ptr)(VOID)) DWORD WINAPI _tx_win32_thread_entry(LPVOID ptr) { -TX_THREAD *thread_ptr; -TX_THREAD *current_thread_ptr; +TX_THREAD *thread_ptr; +TX_THREAD *current_thread_ptr; HANDLE threadhandle; int threadpriority; DWORD threadid; @@ -180,8 +180,10 @@ DWORD threadid; /* 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. Check - before calling the shell entry to avoid running stale ThreadX code. */ + /* 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); 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 601761357..fcc011baf 100644 --- a/ports/win32/vs_2019/src/tx_thread_system_return.c +++ b/ports/win32/vs_2019/src/tx_thread_system_return.c @@ -149,6 +149,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); @@ -165,6 +166,9 @@ DWORD threadid; until the thread is scheduled. */ WaitForSingleObject(temp_run_semaphore, INFINITE); + /* 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); From a8e65baba490d346cb2e05dc1e98a77dd2bbae08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Thu, 25 Jun 2026 08:37:46 -0400 Subject: [PATCH 06/21] Removed duplicate Win32 scheduler declarations The stash-pop merge left two artefacts in tx_thread_schedule.c: - DWORD wait_status declared twice at the top of _tx_thread_schedule() - _tx_win32_semaphore_reset() defined twice (lines 191 and 332) The second definition also incorrectly replaced the critical-section function bodies (_tx_win32_critical_section_obtain/release/release_all), which caused LNK2001 unresolved-external errors at link time. Fix: remove the duplicate variable declaration, the duplicate semaphore_reset definition, and restore the missing critical-section function bodies. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Assisted-by: Codex (gpt-5.6-sol) --- ports/win32/vs_2019/src/tx_thread_schedule.c | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/ports/win32/vs_2019/src/tx_thread_schedule.c b/ports/win32/vs_2019/src/tx_thread_schedule.c index 7aecb565a..666ff37c6 100644 --- a/ports/win32/vs_2019/src/tx_thread_schedule.c +++ b/ports/win32/vs_2019/src/tx_thread_schedule.c @@ -75,8 +75,6 @@ VOID _tx_thread_schedule(VOID) { DWORD wait_status; -DWORD wait_status; - /* Loop forever. */ @@ -275,7 +273,7 @@ void _tx_win32_critical_section_release(TX_WIN32_CRITICAL_SECTION *critical_s /* Sleep for 0, just to relinquish to other ready threads. */ Sleep(0); - } + } } } else @@ -327,14 +325,4 @@ void _tx_win32_critical_section_release_all(TX_WIN32_CRITICAL_SECTION *cri /* Increment the system error counter. */ _tx_win32_system_error++; } -} - -static VOID _tx_win32_semaphore_reset(HANDLE semaphore_handle) -{ - - /* Drain any stale counts from a semaphore so the next WaitForSingleObject - blocks until a fresh release. */ - while (WaitForSingleObject(semaphore_handle, 0) == WAIT_OBJECT_0) - { - } } \ No newline at end of file From 675427425779beaa7be05a6381fb69a8311028f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Mon, 29 Jun 2026 09:56:49 -0400 Subject: [PATCH 07/21] Fixed exit deadlock in win32 port timer infrastructure On win32 under WOW64, exit() has higher overhead than on native win64. The 1ms waitable timer fires during CRT cleanup, causing _tx_thread_context_save() to call SuspendThread() on the thread currently holding the CRT heap lock. Any subsequent malloc() in another thread deadlocks permanently. The symptom is a test that prints SUCCESS but never exits, causing CTest to kill it at the 120s timeout. Fix: - Added volatile LONG _tx_win32_exiting flag in tx_initialize_low_level.c - Registered _tx_win32_exit_cleanup() atexit handler that sets the flag, cancels the waitable timer, and terminates the timer thread if needed - Added early-exit guard at top of _tx_win32_timer_interrupt() to skip interrupt processing once the flag is set - Modified timer thread loop to check the flag and exit cleanly - Added extern declaration and SuspendThread() guard in tx_thread_context_save.c: if _tx_win32_exiting is set when context save is triggered, release the critical section and return without suspending - Used direct volatile LONG read (not _InterlockedAdd which is x64-only) to ensure the guard compiles correctly on x86 Tested: netx_15_24_test now passes in ~1 s instead of timing out at 120 s. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Assisted-by: Codex (gpt-5.6-sol) --- .../vs_2019/src/tx_initialize_low_level.c | 66 +++++++++++++++++-- .../vs_2019/src/tx_thread_context_save.c | 16 ++++- 2 files changed, 77 insertions(+), 5 deletions(-) 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 3882f6319..6fdcd6dd0 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 @@ -48,6 +48,11 @@ HANDLE _tx_win32_isr_semaphore; UINT _tx_win32_timer_waiting; extern TX_THREAD *_tx_thread_current_ptr; +/* 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 @@ -160,6 +165,9 @@ 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. */ @@ -262,6 +270,37 @@ VOID _tx_initialize_low_level(VOID) } +/* 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. */ @@ -323,6 +362,12 @@ void _tx_initialize_start_interrupts(void) _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(); } @@ -338,6 +383,12 @@ VOID CALLBACK _tx_win32_timer_interrupt(UINT wTimerID, UINT msg, DWORD_PTR dwUse 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(); @@ -356,13 +407,20 @@ 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. */ - while (1) + /* 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) { - WaitForSingleObject(_tx_win32_timer_handle, INFINITE); + if (WaitForSingleObject(_tx_win32_timer_handle, INFINITE) != WAIT_OBJECT_0) + break; + 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; } 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 2f4afb403..12810c358 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 @@ -30,6 +30,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 +91,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! */ From 9f187d780f03ae027fdac724136e9055e87ca902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Fri, 24 Jul 2026 09:36:16 -0400 Subject: [PATCH 08/21] Improved Win64 scheduler performance with spin-poll and tick batching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added spin-poll idle waits using SwitchToThread() loops in place of blocking WaitForSingleObject() calls in tx_thread_schedule.c, tx_thread_system_return.c, tx_thread_stack_build.c, and tx_thread_context_restore.c to reduce scheduling latency - Added TX_WIN32_TICKS_PER_INTERRUPT (default 5) to tx_initialize_low_level.c to fire the ThreadX timer interrupt N times per 1ms OS event, advancing the ThreadX system clock NĂ— faster in real time This accelerates all tick-based waits (tx_thread_sleep, timers) and reduces total USBX regression suite time from ~451s to ~351s Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Assisted-by: Codex (gpt-5.6-sol) --- .../win64/vs_2022/src/tx_initialize_low_level.c | 16 ++++++++++++++-- .../vs_2022/src/tx_thread_context_restore.c | 12 ++++++++---- ports/win64/vs_2022/src/tx_thread_schedule.c | 16 ++++++++++++---- ports/win64/vs_2022/src/tx_thread_stack_build.c | 7 ++++--- .../win64/vs_2022/src/tx_thread_system_return.c | 8 +++++--- 5 files changed, 43 insertions(+), 16 deletions(-) diff --git a/ports/win64/vs_2022/src/tx_initialize_low_level.c b/ports/win64/vs_2022/src/tx_initialize_low_level.c index b7e965758..b2859875d 100644 --- a/ports/win64/vs_2022/src/tx_initialize_low_level.c +++ b/ports/win64/vs_2022/src/tx_initialize_low_level.c @@ -358,8 +358,20 @@ VOID CALLBACK _tx_win32_timer_interrupt(UINT wTimerID, UINT msg, DWORD_PTR dwUse /* 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(); diff --git a/ports/win64/vs_2022/src/tx_thread_context_restore.c b/ports/win64/vs_2022/src/tx_thread_context_restore.c index 9786e7cbe..187b2a538 100644 --- a/ports/win64/vs_2022/src/tx_thread_context_restore.c +++ b/ports/win64/vs_2022/src/tx_thread_context_restore.c @@ -131,9 +131,11 @@ TX_THREAD *execute_thread; (execute_thread -> tx_thread_win32_suspension_type == 0)) { - /* Release the critical section while the scheduler runs. */ + /* 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); - WaitForSingleObject(_tx_win32_isr_semaphore, INFINITE); + 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) { @@ -161,9 +163,11 @@ TX_THREAD *execute_thread; if (execute_thread -> tx_thread_win32_suspension_type == 0) { - /* Release the critical section while the scheduler runs. */ + /* 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); - WaitForSingleObject(_tx_win32_isr_semaphore, INFINITE); + 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) { diff --git a/ports/win64/vs_2022/src/tx_thread_schedule.c b/ports/win64/vs_2022/src/tx_thread_schedule.c index d08305ee8..ea9ca5264 100644 --- a/ports/win64/vs_2022/src/tx_thread_schedule.c +++ b/ports/win64/vs_2022/src/tx_thread_schedule.c @@ -111,8 +111,13 @@ DWORD wait_status; /* Leave the critical section. */ _tx_win32_critical_section_release(&_tx_win32_critical_section); - /* Wait for the next scheduling state change. */ - WaitForSingleObject(_tx_win32_scheduler_wake_event, INFINITE); + /* 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(); } } @@ -185,8 +190,11 @@ DWORD wait_status; /* 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(); } } diff --git a/ports/win64/vs_2022/src/tx_thread_stack_build.c b/ports/win64/vs_2022/src/tx_thread_stack_build.c index c50f7754d..aabb21ac4 100644 --- a/ports/win64/vs_2022/src/tx_thread_stack_build.c +++ b/ports/win64/vs_2022/src/tx_thread_stack_build.c @@ -178,9 +178,10 @@ DWORD threadid; handoff point and is ready to be scheduled. */ ReleaseSemaphore(thread_ptr -> tx_thread_win32_thread_start_semaphore, 1, NULL); - /* 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); + /* Spin-poll for the scheduler to release this thread to run. + Matches the spin-poll pattern used in _tx_thread_system_return. */ + while (WaitForSingleObject(thread_ptr -> tx_thread_win32_thread_run_semaphore, 0) != WAIT_OBJECT_0) + SwitchToThread(); /* Acknowledge that the host thread is now able to execute ThreadX code. */ ReleaseSemaphore(thread_ptr -> tx_thread_win32_thread_start_semaphore, 1, NULL); diff --git a/ports/win64/vs_2022/src/tx_thread_system_return.c b/ports/win64/vs_2022/src/tx_thread_system_return.c index 7a8fc89c0..16bd3909a 100644 --- a/ports/win64/vs_2022/src/tx_thread_system_return.c +++ b/ports/win64/vs_2022/src/tx_thread_system_return.c @@ -167,9 +167,11 @@ 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-poll for the scheduler to grant this thread a new time-slice. + SwitchToThread() between polls keeps the CPU available to the scheduler + and timer without paying the full kernel-wake cost of INFINITE. */ + while (WaitForSingleObject(temp_run_semaphore, 0) != WAIT_OBJECT_0) + SwitchToThread(); /* Acknowledge that the thread is once again executing ThreadX code. */ ReleaseSemaphore(temp_thread_ptr -> tx_thread_win32_thread_start_semaphore, 1, NULL); From f5b4af9339ba4f0ec26c8db8a4adfc960f8ccb3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Tue, 28 Jul 2026 08:33:27 -0400 Subject: [PATCH 09/21] Added idle fast-forward to Win64 simulator for faster tests The Win64 ThreadX simulator advanced its clock on a real Windows waitable timer, so tests that sleep on tx_thread_sleep() consumed wall-clock time. This mirrors the Linux port's TX_LINUX_NO_IDLE_ENABLE: when no thread is ready to run, the scheduler kicks the timer thread to fire the next tick immediately instead of waiting for the wall clock, making idle periods CPU-bound. - Add auto-reset _tx_win32_timer_kick_event; the timer thread now waits on the periodic timer OR the kick event (WaitForMultipleObjects). - _tx_thread_schedule signals the kick when the system is idle (execute_ptr == NULL && system_state == 0), then waits briefly on the wake event for progress. - All changes guarded by TX_WIN32_NO_IDLE_ENABLE so the default port remains real-time; the flag is enabled only for the USBX test build. Reduces the USBX Win64 regression suite from ~748s to ~30s (430/430 tests pass), on par with the Linux suite. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Assisted-by: Codex (gpt-5.6-sol) --- ports/win64/vs_2022/inc/tx_port.h | 3 ++ .../vs_2022/src/tx_initialize_low_level.c | 35 +++++++++++++++++++ ports/win64/vs_2022/src/tx_thread_schedule.c | 14 ++++++++ 3 files changed, 52 insertions(+) diff --git a/ports/win64/vs_2022/inc/tx_port.h b/ports/win64/vs_2022/inc/tx_port.h index 213f4a2e0..ea7d71ac5 100644 --- a/ports/win64/vs_2022/inc/tx_port.h +++ b/ports/win64/vs_2022/inc/tx_port.h @@ -595,6 +595,9 @@ extern HANDLE _tx_win32_timer_handle; extern HANDLE _tx_win32_timer_thread_handle; extern HANDLE _tx_win32_isr_semaphore; extern UINT _tx_win32_timer_waiting; +#ifdef TX_WIN32_NO_IDLE_ENABLE +extern HANDLE _tx_win32_timer_kick_event; +#endif extern UINT _tx_win32_timer_id; extern LARGE_INTEGER _tx_win32_time_stamp; diff --git a/ports/win64/vs_2022/src/tx_initialize_low_level.c b/ports/win64/vs_2022/src/tx_initialize_low_level.c index b2859875d..382265398 100644 --- a/ports/win64/vs_2022/src/tx_initialize_low_level.c +++ b/ports/win64/vs_2022/src/tx_initialize_low_level.c @@ -51,6 +51,15 @@ 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 + /* 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 @@ -271,6 +280,20 @@ VOID _tx_initialize_low_level(VOID) } } +#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 Win64 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; @@ -388,7 +411,19 @@ static DWORD WINAPI _tx_win32_timer_thread_entry(LPVOID thread_input) /* Drive periodic simulated interrupts from a single thread. */ while (1) { +#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; + WaitForMultipleObjects(2, _wait_handles, FALSE, INFINITE); +#else WaitForSingleObject(_tx_win32_timer_handle, INFINITE); +#endif _tx_win32_timer_interrupt(0, 0, 0, 0, 0); _tx_win32_timer_start(); } diff --git a/ports/win64/vs_2022/src/tx_thread_schedule.c b/ports/win64/vs_2022/src/tx_thread_schedule.c index ea9ca5264..4d573590a 100644 --- a/ports/win64/vs_2022/src/tx_thread_schedule.c +++ b/ports/win64/vs_2022/src/tx_thread_schedule.c @@ -111,6 +111,19 @@ DWORD wait_status; /* Leave the critical section. */ _tx_win32_critical_section_release(&_tx_win32_critical_section); +#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 @@ -118,6 +131,7 @@ DWORD wait_status; during test runs. Drain any pending wake event to keep it clean. */ WaitForSingleObject(_tx_win32_scheduler_wake_event, 0); SwitchToThread(); +#endif } } From f43e0d680d332ec102a5d62a9bb2d71a79128441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Tue, 28 Jul 2026 11:39:53 -0400 Subject: [PATCH 10/21] Added idle fast-forward and tick batching to Win32 simulator Brings the Win32 (x86) ThreadX simulator to parity with the Win64 port so the USBX regression suite is CPU-bound rather than wall-clock-bound. Previously the Win32 port advanced its clock one tick per real 1ms periodic timer and blocked indefinitely on the wake event when idle. - Add TX_WIN32_TICKS_PER_INTERRUPT (default 5) tick batching in _tx_win32_timer_interrupt so the SuspendThread/ResumeThread overhead is amortized across N ticks per host timer event. - Add auto-reset _tx_win32_timer_kick_event; the timer thread now waits on the periodic timer OR the kick event (WaitForMultipleObjects), preserving the existing _tx_win32_exiting exit-cleanup checks. - _tx_thread_schedule signals the kick when the system is idle (execute_ptr == NULL && system_state == 0), then waits briefly on the wake event for progress, mirroring TX_LINUX_NO_IDLE_ENABLE. - Add tx_thread_extension_ptr, TX_TIMER_INTERNAL_EXTENSION and the timeout-setup macros to tx_port.h so NetXDuo/USBX default extension macros resolve on the ILP32 target (matching the Win64 port). - All fast-forward changes guarded by TX_WIN32_NO_IDLE_ENABLE so the default port remains real-time; the flag is enabled only for the USBX test build. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Assisted-by: Codex (gpt-5.6-sol) --- ports/win32/vs_2019/inc/tx_port.h | 19 ++++++- .../vs_2019/src/tx_initialize_low_level.c | 52 ++++++++++++++++++- ports/win32/vs_2019/src/tx_thread_schedule.c | 14 +++++ 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/ports/win32/vs_2019/inc/tx_port.h b/ports/win32/vs_2019/inc/tx_port.h index cf0f83e17..12967755a 100644 --- a/ports/win32/vs_2019/inc/tx_port.h +++ b/ports/win32/vs_2019/inc/tx_port.h @@ -343,7 +343,7 @@ void _tx_initialize_start_interrupts(void); 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 @@ -399,6 +399,20 @@ 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. */ @@ -523,6 +537,9 @@ extern HANDLE _tx_win32_timer_thread_handle; extern HANDLE _tx_win32_isr_semaphore; extern UINT _tx_win32_timer_id; 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); 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 6fdcd6dd0..c7ebdedc1 100644 --- a/ports/win32/vs_2019/src/tx_initialize_low_level.c +++ b/ports/win32/vs_2019/src/tx_initialize_low_level.c @@ -48,6 +48,15 @@ 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. */ @@ -262,6 +271,20 @@ VOID _tx_initialize_low_level(VOID) } } +#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; @@ -392,8 +415,20 @@ VOID CALLBACK _tx_win32_timer_interrupt(UINT wTimerID, UINT msg, DWORD_PTR dwUse /* 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(); @@ -411,8 +446,21 @@ static DWORD WINAPI _tx_win32_timer_thread_entry(LPVOID thread_input) 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); diff --git a/ports/win32/vs_2019/src/tx_thread_schedule.c b/ports/win32/vs_2019/src/tx_thread_schedule.c index 666ff37c6..afe236b9e 100644 --- a/ports/win32/vs_2019/src/tx_thread_schedule.c +++ b/ports/win32/vs_2019/src/tx_thread_schedule.c @@ -106,8 +106,22 @@ DWORD wait_status; /* Leave the critical section. */ _tx_win32_critical_section_release(&_tx_win32_critical_section); +#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 + /* Wait for the next scheduling state change. */ WaitForSingleObject(_tx_win32_scheduler_wake_event, INFINITE); +#endif } } From c7a1e494d8c12a4367f03829847c876c90017930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Wed, 29 Jul 2026 16:58:35 -0400 Subject: [PATCH 11/21] Fixed ThreadX win32 regression build and thread execution test Added to the win32 tx_port.h so uint8_t and related fixed-width types used by the regression test harness are available (matching win64). Broadened the timeout-pointer setup guard in threadx_thread_basic_execution_test from _WIN64 to _WIN32 so the win32 build also populates _tx_timer_expired_timer_ptr before calling _tx_thread_timeout. The win32 port defines TX_THREAD_TIMEOUT_POINTER_SETUP in terms of the expired-timer extension pointer (like win64/linux), so the direct-cast path crashed with an access violation. Fixed the lone win32 SEGFAULT; suite now 96/96. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Assisted-by: Codex (gpt-5.6-sol) --- ports/win32/vs_2019/inc/tx_port.h | 1 + test/tx/regression/threadx_thread_basic_execution_test.c | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ports/win32/vs_2019/inc/tx_port.h b/ports/win32/vs_2019/inc/tx_port.h index 12967755a..593724fc7 100644 --- a/ports/win32/vs_2019/inc/tx_port.h +++ b/ports/win32/vs_2019/inc/tx_port.h @@ -65,6 +65,7 @@ /* Define compiler library include files. */ +#include #include #include diff --git a/test/tx/regression/threadx_thread_basic_execution_test.c b/test/tx/regression/threadx_thread_basic_execution_test.c index c51a677c3..8372fcd9d 100644 --- a/test/tx/regression/threadx_thread_basic_execution_test.c +++ b/test/tx/regression/threadx_thread_basic_execution_test.c @@ -1,4 +1,6 @@ /***************************************************************************/ + +/* Some portions generated by Codex (gpt-5.6-sol). */ /* Copyright (c) 2024 Microsoft Corporation */ /* Copyright (c) 2026 Eclipse ThreadX contributors */ /* */ @@ -401,7 +403,7 @@ VOID (*temp_mutex_release)(TX_THREAD *thread_ptr); test_thread.tx_thread_timer.tx_timer_internal_list_head = TX_NULL; test_thread.tx_thread_suspending = TX_TRUE; test_thread.tx_thread_delayed_suspend = TX_TRUE; -#if defined(_WIN64) || defined(TX_TIMER_EXTENSION_PTR_DEFINED) +#if defined(_WIN32) || defined(TX_TIMER_EXTENSION_PTR_DEFINED) { TX_TIMER_INTERNAL timeout_timer; TX_TIMER_INTERNAL *saved_expired_timer_ptr; From b41f0b76495c78a4a65e84ec755f166e26220570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Wed, 5 Aug 2026 17:30:51 -0400 Subject: [PATCH 12/21] Increased the Windows simulator memory pool Raised the Win32 and Win64 simulator allocation to 256000 bytes so larger NetX Duo regression workloads do not overrun the host allocation. Assisted-by: Codex (gpt-5.6-sol) --- ports/win32/vs_2019/inc/tx_port.h | 2 +- ports/win64/vs_2022/inc/tx_port.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/win32/vs_2019/inc/tx_port.h b/ports/win32/vs_2019/inc/tx_port.h index 593724fc7..32c6e5002 100644 --- a/ports/win32/vs_2019/inc/tx_port.h +++ b/ports/win32/vs_2019/inc/tx_port.h @@ -550,7 +550,7 @@ VOID _tx_win32_scheduler_wake(VOID); #endif #ifndef TX_WIN32_MEMORY_SIZE -#define TX_WIN32_MEMORY_SIZE 64000 +#define TX_WIN32_MEMORY_SIZE 256000 #endif #ifndef TX_TIMER_PERIODIC diff --git a/ports/win64/vs_2022/inc/tx_port.h b/ports/win64/vs_2022/inc/tx_port.h index ea7d71ac5..6b12a94dc 100644 --- a/ports/win64/vs_2022/inc/tx_port.h +++ b/ports/win64/vs_2022/inc/tx_port.h @@ -603,7 +603,7 @@ extern LARGE_INTEGER _tx_win32_time_stamp; #ifndef TX_WIN32_MEMORY_SIZE -#define TX_WIN32_MEMORY_SIZE 64000 +#define TX_WIN32_MEMORY_SIZE 256000 #endif VOID _tx_win32_scheduler_wake(VOID); From 4b96b086c48f714bc368db444ae1ecc3620b78bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Tue, 11 Aug 2026 14:02:20 -0400 Subject: [PATCH 13/21] Optimized Windows simulator interrupt locking Replaced kernel mutex lockouts with Windows critical sections and skipped host priority queries on the normal ThreadX execution path. Assisted-by: Codex (gpt-5.6-sol) --- ports/win32/vs_2019/inc/tx_port.h | 2 +- .../vs_2019/src/tx_initialize_low_level.c | 8 ++- .../vs_2019/src/tx_thread_interrupt_control.c | 15 ++--- ports/win32/vs_2019/src/tx_thread_schedule.c | 58 ++++--------------- ports/win64/vs_2022/inc/tx_port.h | 7 ++- .../vs_2022/src/tx_initialize_low_level.c | 16 ++--- .../vs_2022/src/tx_thread_interrupt_control.c | 15 ++--- ports/win64/vs_2022/src/tx_thread_schedule.c | 54 ++++------------- 8 files changed, 44 insertions(+), 131 deletions(-) diff --git a/ports/win32/vs_2019/inc/tx_port.h b/ports/win32/vs_2019/inc/tx_port.h index 32c6e5002..c0ded1e00 100644 --- a/ports/win32/vs_2019/inc/tx_port.h +++ b/ports/win32/vs_2019/inc/tx_port.h @@ -420,7 +420,7 @@ struct TX_THREAD_STRUCT; 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; 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 c7ebdedc1..e531b1ca9 100644 --- a/ports/win32/vs_2019/src/tx_initialize_low_level.c +++ b/ports/win32/vs_2019/src/tx_initialize_low_level.c @@ -10,6 +10,7 @@ **************************************************************************/ // Some portions generated by Codex (gpt 5.4). +// Some portions generated by Codex (gpt-5.6-sol). /**************************************************************************/ /**************************************************************************/ @@ -211,7 +212,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 */ @@ -251,9 +252,9 @@ 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; @@ -324,6 +325,7 @@ static void _tx_win32_exit_cleanup(void) } + /* 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. */ 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 019aabefd..ca73b7fba 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 **************************************************************************/ +// Some portions generated by Codex (gpt-5.6-sol). + /**************************************************************************/ /**************************************************************************/ @@ -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 afe236b9e..4d108f4fe 100644 --- a/ports/win32/vs_2019/src/tx_thread_schedule.c +++ b/ports/win32/vs_2019/src/tx_thread_schedule.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Some portions generated by Codex (gpt-5.6-sol). + /**************************************************************************/ /**************************************************************************/ @@ -214,10 +216,6 @@ static VOID _tx_win32_semaphore_reset(HANDLE semaphore_handle) void _tx_win32_critical_section_obtain(TX_WIN32_CRITICAL_SECTION *critical_section) { - -TX_THREAD *thread_ptr; - - /* Is the protection owned? */ if (critical_section -> tx_win32_critical_section_owner == GetCurrentThreadId()) { @@ -228,15 +226,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; @@ -251,7 +244,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()) { @@ -269,24 +262,8 @@ 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); } } } @@ -302,7 +279,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()) { @@ -316,21 +293,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 @@ -339,4 +303,4 @@ void _tx_win32_critical_section_release_all(TX_WIN32_CRITICAL_SECTION *cri /* Increment the system error counter. */ _tx_win32_system_error++; } -} \ No newline at end of file +} diff --git a/ports/win64/vs_2022/inc/tx_port.h b/ports/win64/vs_2022/inc/tx_port.h index 6b12a94dc..169480282 100644 --- a/ports/win64/vs_2022/inc/tx_port.h +++ b/ports/win64/vs_2022/inc/tx_port.h @@ -13,6 +13,8 @@ * SPDX-License-Identifier: MIT and CC0-1.0 **************************************************************************/ +// Some portions generated by Codex (gpt-5.6-sol). + /**************************************************************************/ /**************************************************************************/ @@ -197,6 +199,7 @@ extern TEST_FLAG test_forced_mutex_timeout; + /* Define ThreadX basic types for this port. */ #define VOID void @@ -434,7 +437,7 @@ struct TX_THREAD_STRUCT; 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; @@ -634,5 +637,3 @@ VOID _tx_win32_scheduler_wake(VOID); - - diff --git a/ports/win64/vs_2022/src/tx_initialize_low_level.c b/ports/win64/vs_2022/src/tx_initialize_low_level.c index 382265398..b41477d75 100644 --- a/ports/win64/vs_2022/src/tx_initialize_low_level.c +++ b/ports/win64/vs_2022/src/tx_initialize_low_level.c @@ -14,6 +14,7 @@ **************************************************************************/ // Some portions generated by Codex (gpt 5.4). +// Some portions generated by Codex (gpt-5.6-sol). /**************************************************************************/ /**************************************************************************/ /** */ @@ -206,7 +207,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 */ @@ -246,18 +247,11 @@ 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; - if (_tx_win32_critical_section.tx_win32_critical_section_mutex_handle == NULL) - { - printf("ThreadX Win64 error creating critical section mutex!\n"); - while(1) - { - } - } /* Create the semaphore that regulates when the scheduler executes. */ _tx_win32_scheduler_semaphore = CreateSemaphore(NULL, 0, 1, NULL); @@ -430,6 +424,7 @@ static DWORD WINAPI _tx_win32_timer_thread_entry(LPVOID thread_input) } + VOID _tx_win32_scheduler_wake(VOID) { @@ -461,4 +456,3 @@ LARGE_INTEGER due_time; } } } - diff --git a/ports/win64/vs_2022/src/tx_thread_interrupt_control.c b/ports/win64/vs_2022/src/tx_thread_interrupt_control.c index ab50a3c9e..b38a0749b 100644 --- a/ports/win64/vs_2022/src/tx_thread_interrupt_control.c +++ b/ports/win64/vs_2022/src/tx_thread_interrupt_control.c @@ -13,6 +13,8 @@ * SPDX-License-Identifier: MIT and CC0-1.0 **************************************************************************/ +// Some portions generated by Codex (gpt-5.6-sol). + /**************************************************************************/ /**************************************************************************/ /** */ @@ -96,8 +98,6 @@ UINT _tx_thread_interrupt_control(UINT new_posture) { UINT old_posture; -HANDLE threadhandle; -int threadpriority; DWORD threadid; TX_THREAD *thread_ptr; @@ -122,22 +122,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 @@ -208,4 +202,3 @@ TX_THREAD *thread_ptr; return(old_posture); } - diff --git a/ports/win64/vs_2022/src/tx_thread_schedule.c b/ports/win64/vs_2022/src/tx_thread_schedule.c index 4d573590a..f6bef9495 100644 --- a/ports/win64/vs_2022/src/tx_thread_schedule.c +++ b/ports/win64/vs_2022/src/tx_thread_schedule.c @@ -14,6 +14,7 @@ **************************************************************************/ // Some portions generated by Codex (gpt 5.4). +// Some portions generated by Codex (gpt-5.6-sol). /**************************************************************************/ /**************************************************************************/ @@ -213,6 +214,7 @@ DWORD wait_status; } + static VOID _tx_win32_semaphore_reset(HANDLE semaphore_handle) { @@ -238,17 +240,9 @@ void _tx_win32_critical_section_obtain(TX_WIN32_CRITICAL_SECTION *critical_se { /* Get the Win32 critical section. */ - if (WaitForSingleObject(critical_section -> tx_win32_critical_section_mutex_handle, INFINITE) != WAIT_OBJECT_0) - { - - /* Increment the system error counter and stop when the mutex cannot be acquired. */ - _tx_win32_system_error++; - while(1) - { - } - } + 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; @@ -263,7 +257,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()) { @@ -281,21 +275,8 @@ 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++; - } + /* Finally, release the critical section. */ + LeaveCriticalSection(&critical_section -> tx_win32_critical_section_lock); } } } @@ -311,7 +292,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()) { @@ -325,21 +306,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 @@ -349,5 +317,3 @@ void _tx_win32_critical_section_release_all(TX_WIN32_CRITICAL_SECTION *cri _tx_win32_system_error++; } } - - From 2223751c1de68cc51752e1bd95b13ad48c347484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Tue, 11 Aug 2026 14:17:51 -0400 Subject: [PATCH 14/21] Aligned Win32 scheduler handoffs with Win64 Replaced repeated blocking scheduler, application-thread, and timer-ISR semaphore waits with the spin-yield handoff pattern already validated by the Win64 port. Assisted-by: Codex (gpt-5.6-sol) --- .../vs_2019/src/tx_thread_context_restore.c | 15 ++++++++++----- ports/win32/vs_2019/src/tx_thread_schedule.c | 16 ++++++++++++---- ports/win32/vs_2019/src/tx_thread_stack_build.c | 10 ++++++---- .../win32/vs_2019/src/tx_thread_system_return.c | 11 +++++++---- 4 files changed, 35 insertions(+), 17 deletions(-) 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 0a37b1900..ae559bed8 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 **************************************************************************/ +// Some portions generated by Codex (gpt-5.6-sol). + /**************************************************************************/ /**************************************************************************/ @@ -126,9 +128,11 @@ TX_THREAD *execute_thread; (execute_thread -> tx_thread_win32_suspension_type == 0)) { - /* Release the critical section while the scheduler runs. */ + /* 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); - WaitForSingleObject(_tx_win32_isr_semaphore, INFINITE); + 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) { @@ -156,9 +160,11 @@ TX_THREAD *execute_thread; if (execute_thread -> tx_thread_win32_suspension_type == 0) { - /* Release the critical section while the scheduler runs. */ + /* 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); - WaitForSingleObject(_tx_win32_isr_semaphore, INFINITE); + 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) { @@ -171,4 +177,3 @@ TX_THREAD *execute_thread; /* Leave Win32 critical section. */ _tx_win32_critical_section_release_all(&_tx_win32_critical_section); } - diff --git a/ports/win32/vs_2019/src/tx_thread_schedule.c b/ports/win32/vs_2019/src/tx_thread_schedule.c index 4d108f4fe..1fa11c596 100644 --- a/ports/win32/vs_2019/src/tx_thread_schedule.c +++ b/ports/win32/vs_2019/src/tx_thread_schedule.c @@ -121,8 +121,13 @@ DWORD wait_status; WaitForSingleObject(_tx_win32_scheduler_wake_event, 2); #else - /* Wait for the next scheduling state change. */ - WaitForSingleObject(_tx_win32_scheduler_wake_event, INFINITE); + /* 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 } } @@ -196,8 +201,11 @@ DWORD wait_status; /* 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(); } } 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 e9dfbc793..f6ab7076d 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 **************************************************************************/ +// Some portions generated by Codex (gpt-5.6-sol). + /**************************************************************************/ /**************************************************************************/ @@ -173,9 +175,10 @@ DWORD threadid; handoff point and is ready to be scheduled. */ ReleaseSemaphore(thread_ptr -> tx_thread_win32_thread_start_semaphore, 1, NULL); - /* 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); + /* Spin-poll for the scheduler to release this thread to run. + Matches the spin-poll pattern used in _tx_thread_system_return. */ + while (WaitForSingleObject(thread_ptr -> tx_thread_win32_thread_run_semaphore, 0) != WAIT_OBJECT_0) + SwitchToThread(); /* Acknowledge that the host thread is now able to execute ThreadX code. */ ReleaseSemaphore(thread_ptr -> tx_thread_win32_thread_start_semaphore, 1, NULL); @@ -202,4 +205,3 @@ DWORD threadid; 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 fcc011baf..05259fec4 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 **************************************************************************/ +// Some portions generated by Codex (gpt-5.6-sol). + /**************************************************************************/ /**************************************************************************/ @@ -162,9 +164,11 @@ 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-poll for the scheduler to grant this thread a new time-slice. + SwitchToThread() between polls keeps the CPU available to the scheduler + and timer without paying the full kernel-wake cost of INFINITE. */ + while (WaitForSingleObject(temp_run_semaphore, 0) != WAIT_OBJECT_0) + SwitchToThread(); /* Acknowledge that the thread is once again executing ThreadX code. */ ReleaseSemaphore(temp_thread_ptr -> tx_thread_win32_thread_start_semaphore, 1, NULL); @@ -207,4 +211,3 @@ DWORD threadid; _tx_win32_critical_section_release(&_tx_win32_critical_section); } } - From 0f1b1d970edd611524e884d8414e1e4a05f56974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Tue, 11 Aug 2026 17:23:16 -0400 Subject: [PATCH 15/21] Bounded Win32 application-thread handoff spins Kept immediate scheduler handoffs responsive while allowing dormant application threads to block after a short bounded spin. This avoids host CPU starvation in multi-threaded FileX workloads without restoring the original per-handoff latency. Assisted-by: Codex (gpt-5.6-sol) --- ports/win32/vs_2019/inc/tx_port.h | 4 ++++ .../win32/vs_2019/src/tx_thread_stack_build.c | 19 +++++++++++++++--- .../vs_2019/src/tx_thread_system_return.c | 20 +++++++++++++++---- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/ports/win32/vs_2019/inc/tx_port.h b/ports/win32/vs_2019/inc/tx_port.h index c0ded1e00..86bd93f6f 100644 --- a/ports/win32/vs_2019/inc/tx_port.h +++ b/ports/win32/vs_2019/inc/tx_port.h @@ -549,6 +549,10 @@ VOID _tx_win32_scheduler_wake(VOID); #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 256000 #endif 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 f6ab7076d..feead3b74 100644 --- a/ports/win32/vs_2019/src/tx_thread_stack_build.c +++ b/ports/win32/vs_2019/src/tx_thread_stack_build.c @@ -167,6 +167,7 @@ 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; @@ -175,10 +176,22 @@ DWORD threadid; handoff point and is ready to be scheduled. */ ReleaseSemaphore(thread_ptr -> tx_thread_win32_thread_start_semaphore, 1, NULL); - /* Spin-poll for the scheduler to release this thread to run. - Matches the spin-poll pattern used in _tx_thread_system_return. */ + /* 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) - SwitchToThread(); + { + 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); 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 05259fec4..52f65b4f8 100644 --- a/ports/win32/vs_2019/src/tx_thread_system_return.c +++ b/ports/win32/vs_2019/src/tx_thread_system_return.c @@ -85,6 +85,7 @@ UINT temp_thread_state; HANDLE threadhandle; int threadpriority; DWORD threadid; +ULONG handoff_spin_count; /* Enter Win32 critical section. */ @@ -164,11 +165,22 @@ DWORD threadid; ExitThread(0); } - /* Spin-poll for the scheduler to grant this thread a new time-slice. - SwitchToThread() between polls keeps the CPU available to the scheduler - and timer without paying the full kernel-wake cost of 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) - SwitchToThread(); + { + 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); From 7acbd95873e64010feb558671a7f2a829a531c59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Tue, 1 Sep 2026 15:28:05 -0400 Subject: [PATCH 16/21] Added required Codex disclosures Recorded the current Codex session in each refreshed Windows simulator source file that did not already contain the exact required disclosure. Assisted-by: Codex (gpt-5.6-sol) --- ports/win32/vs_2019/src/tx_thread_context_save.c | 3 ++- ports/win64/vs_2022/src/tx_thread_context_restore.c | 2 +- ports/win64/vs_2022/src/tx_thread_stack_build.c | 2 +- ports/win64/vs_2022/src/tx_thread_system_return.c | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) 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 12810c358..4574b8277 100644 --- a/ports/win32/vs_2019/src/tx_thread_context_save.c +++ b/ports/win32/vs_2019/src/tx_thread_context_save.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Some portions generated by Codex (gpt-5.6-sol). + /**************************************************************************/ /**************************************************************************/ @@ -118,4 +120,3 @@ TX_THREAD *thread_ptr; /* Exit Win32 critical section. */ _tx_win32_critical_section_release(&_tx_win32_critical_section); } - diff --git a/ports/win64/vs_2022/src/tx_thread_context_restore.c b/ports/win64/vs_2022/src/tx_thread_context_restore.c index 187b2a538..f860d61f0 100644 --- a/ports/win64/vs_2022/src/tx_thread_context_restore.c +++ b/ports/win64/vs_2022/src/tx_thread_context_restore.c @@ -14,6 +14,7 @@ **************************************************************************/ // Some portions generated by Codex (gpt 5.4). +// Some portions generated by Codex (gpt-5.6-sol). /**************************************************************************/ /**************************************************************************/ @@ -181,4 +182,3 @@ TX_THREAD *execute_thread; _tx_win32_critical_section_release_all(&_tx_win32_critical_section); } - diff --git a/ports/win64/vs_2022/src/tx_thread_stack_build.c b/ports/win64/vs_2022/src/tx_thread_stack_build.c index aabb21ac4..9c1b5dfc0 100644 --- a/ports/win64/vs_2022/src/tx_thread_stack_build.c +++ b/ports/win64/vs_2022/src/tx_thread_stack_build.c @@ -14,6 +14,7 @@ **************************************************************************/ // Some portions generated by Codex (gpt 5.4). +// Some portions generated by Codex (gpt-5.6-sol). /**************************************************************************/ /**************************************************************************/ @@ -209,4 +210,3 @@ DWORD threadid; return EXIT_SUCCESS; } - diff --git a/ports/win64/vs_2022/src/tx_thread_system_return.c b/ports/win64/vs_2022/src/tx_thread_system_return.c index 16bd3909a..9b1fa210a 100644 --- a/ports/win64/vs_2022/src/tx_thread_system_return.c +++ b/ports/win64/vs_2022/src/tx_thread_system_return.c @@ -14,6 +14,7 @@ **************************************************************************/ // Some portions generated by Codex (gpt 5.4). +// Some portions generated by Codex (gpt-5.6-sol). /**************************************************************************/ /**************************************************************************/ @@ -215,4 +216,3 @@ DWORD threadid; } } - From 3f33633f7c61d5742922adcfe7b876784ec8bbf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Wed, 2 Sep 2026 08:11:01 -0400 Subject: [PATCH 17/21] Fixed trace entry test on 64-bit ports Assisted-by: Codex (gpt-5.6-sol) --- .../threadx_trace_entry_update_test.c | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/test/tx/regression/threadx_trace_entry_update_test.c b/test/tx/regression/threadx_trace_entry_update_test.c index d827497af..3d4b55b09 100644 --- a/test/tx/regression/threadx_trace_entry_update_test.c +++ b/test/tx/regression/threadx_trace_entry_update_test.c @@ -73,7 +73,7 @@ static TX_SEMAPHORE semaphore_0; static UCHAR trace_buffer[16384]; -/* Four blocks of 20 bytes: 100 / (20 + sizeof(void *)) on a 32-bit build. */ +/* Enough storage for several 20-byte blocks on both 32-bit and 64-bit builds. */ static UCHAR block_pool_area[100]; static UCHAR byte_pool_area[512]; @@ -213,14 +213,22 @@ void *byte_ptr; UINT i; - /* Empty the block pool. The first of these takes the immediate-success path - through tx_block_allocate, which carries the first update block. */ + /* Empty the block pool. The first allocation takes the immediate-success + path through tx_block_allocate, which carries the first update block. + The exact capacity is port-dependent because each block has a pointer- + sized header, so allocate until the pool reports that it is empty. */ held_block = TX_NULL; - for (i = 0; i < 4; i++) + for (i = 0; i < 6u; i++) { status = tx_block_allocate(&block_pool_0, &block_ptr, TX_NO_WAIT); + if (status == TX_NO_MEMORY) + { + + break; + } + if (status != TX_SUCCESS) { @@ -236,6 +244,12 @@ UINT i; } } + if ((status != TX_NO_MEMORY) || (held_block == TX_NULL)) + { + + error++; + } + /* The pool is empty now, so this suspends. It completes in thread 1's context when the block comes back, which is the second update block in tx_block_allocate -- and the suspend and resume it goes through carry the From 9e4ef09dde3abb100a61e81d20f82e0b8e7c4e9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Wed, 2 Sep 2026 13:30:36 -0400 Subject: [PATCH 18/21] Ported SMP teardown diagnostics to Windows Assisted-by: Codex (gpt-5.6-sol) --- test/smp/regression/testcontrol.c | 102 ++++++++++++++++++++++++++---- 1 file changed, 91 insertions(+), 11 deletions(-) diff --git a/test/smp/regression/testcontrol.c b/test/smp/regression/testcontrol.c index c0ba34636..4bdaa3665 100644 --- a/test/smp/regression/testcontrol.c +++ b/test/smp/regression/testcontrol.c @@ -11,6 +11,7 @@ /* This is the test control routine of the ThreadX kernel. All tests are dispatched from this routine. */ // Some portions generated by Codex (gpt 5.5). +// Some portions generated by Codex (gpt-5.6-sol). #define TX_THREAD_SMP_SOURCE_CODE @@ -27,11 +28,17 @@ #include "tx_event_flags.h" #include #include +#ifdef _WIN32 +#include +#include +#include +#else #include #include #include #include #include +#endif #define TEST_STACK_SIZE 6144 @@ -1364,9 +1371,10 @@ TX_THREAD *thread_ptr; that takes milliseconds, so bounding it cannot turn a slow pass into a failure. - The report is written with write() rather than printf() deliberately. A - wedged thread may hold the stdio lock, and a watchdog that blocked on that - lock would reproduce the silent timeout it exists to replace. + The report is written directly to the standard-error descriptor rather + than with printf() deliberately. A wedged thread may hold the stdio lock, + and a watchdog that blocked on that lock would reproduce the silent timeout + it exists to replace. TX_TEST_TEARDOWN_TIMEOUT overrides the bound, in seconds; zero disables the watchdog. TX_TEST_TEARDOWN_TRACE echoes every stage as it is reached and @@ -1382,6 +1390,26 @@ static volatile UINT test_teardown_armed = TX_FALSE; static UINT test_teardown_timeout = TEST_TEARDOWN_TIMEOUT_DEFAULT; static UINT test_teardown_trace = TX_FALSE; +#ifdef _WIN32 +#define TEST_TEARDOWN_DEFERRED_PREEMPT(thread_ptr) ((thread_ptr) -> tx_thread_win32_deferred_preempt) +#define TEST_TEARDOWN_SUSPENSION_TYPE(thread_ptr) ((thread_ptr) -> tx_thread_win32_suspension_type) +#else +#define TEST_TEARDOWN_DEFERRED_PREEMPT(thread_ptr) ((thread_ptr) -> tx_thread_linux_deferred_preempt) +#define TEST_TEARDOWN_SUSPENSION_TYPE(thread_ptr) ((thread_ptr) -> tx_thread_linux_suspension_type) +#endif + + +/* Write diagnostics directly to standard error without taking the stdio lock. */ +static void test_teardown_write(const char *buffer, size_t length) +{ + +#ifdef _WIN32 + (void) _write(2, buffer, (unsigned int) length); +#else + (void) write(2, buffer, length); +#endif +} + /* Record how far teardown has progressed, and echo it when tracing is on. */ static void test_teardown_stage(const char *stage) @@ -1391,9 +1419,9 @@ static void test_teardown_stage(const char *stage) if (test_teardown_trace != TX_FALSE) { - (void) write(2, "[teardown] ", 11); - (void) write(2, stage, strlen(stage)); - (void) write(2, "\n", 1); + test_teardown_write("[teardown] ", 11u); + test_teardown_write(stage, strlen(stage)); + test_teardown_write("\n", 1u); } } @@ -1432,7 +1460,7 @@ TX_THREAD *thread_ptr; (UINT) _tx_thread_preempt_disable, (ULONG) _tx_thread_created_count); if (length > 0) - (void) write(2, buffer, (size_t) length); + test_teardown_write(buffer, (size_t) length); for (core = 0; core < ((UINT) TX_THREAD_SMP_MAX_CORES); core++) { @@ -1444,7 +1472,7 @@ TX_THREAD *thread_ptr; test_teardown_thread_name(_tx_thread_current_ptr[core]), test_teardown_thread_name(_tx_thread_execute_ptr[core])); if (length > 0) - (void) write(2, buffer, (size_t) length); + test_teardown_write(buffer, (size_t) length); } /* Walk the created list, stopping at the head, and after a fixed number of @@ -1463,10 +1491,10 @@ TX_THREAD *thread_ptr; thread_ptr -> tx_thread_inherit_priority, thread_ptr -> tx_thread_smp_core_mapped, (ULONG) thread_ptr -> tx_thread_smp_core_control, - thread_ptr -> tx_thread_linux_deferred_preempt, - thread_ptr -> tx_thread_linux_suspension_type); + TEST_TEARDOWN_DEFERRED_PREEMPT(thread_ptr), + TEST_TEARDOWN_SUSPENSION_TYPE(thread_ptr)); if (length > 0) - (void) write(2, buffer, (size_t) length); + test_teardown_write(buffer, (size_t) length); thread_ptr = thread_ptr -> tx_thread_created_next; if (thread_ptr == _tx_thread_created_ptr) @@ -1485,6 +1513,41 @@ static void test_teardown_abort(const char *reason) /* Watch an armed teardown, and only an armed one. */ +#ifdef _WIN32 +static unsigned __stdcall test_teardown_watchdog(void *input) +{ + +ULONG waited_ms; +ULONG limit_ms; + + + (void) input; + limit_ms = ((ULONG) test_teardown_timeout) * ((ULONG) 1000); + waited_ms = ((ULONG) 0); + + while (1) + { + + Sleep((DWORD) TEST_TEARDOWN_POLL_MS); + + /* Nothing to watch while teardown is stood down. */ + if (test_teardown_armed == TX_FALSE) + { + waited_ms = ((ULONG) 0); + } + else + { + waited_ms = waited_ms + ((ULONG) TEST_TEARDOWN_POLL_MS); + if (waited_ms >= limit_ms) + { + test_teardown_abort("teardown did not complete"); + } + } + } + + return 0u; +} +#else static void *test_teardown_watchdog(void *input) { @@ -1529,6 +1592,7 @@ ULONG limit_ms; return input; } +#endif /* Start the watchdog, stood down. Called before any test runs, so that no @@ -1536,7 +1600,11 @@ ULONG limit_ms; static void test_teardown_watchdog_start(void) { +#ifdef _WIN32 +uintptr_t watchdog_id; +#else pthread_t watchdog_id; +#endif char *value; @@ -1550,15 +1618,27 @@ char *value; /* Tracing wants the stages in the log next to the output around them, which block buffering would otherwise discard on a kill. */ test_teardown_trace = TX_TRUE; +#ifdef _WIN32 + setvbuf(stdout, TX_NULL, _IONBF, 0u); +#else setvbuf(stdout, TX_NULL, _IOLBF, 0); +#endif } /* A timeout of zero turns the watchdog off. */ if (test_teardown_timeout == ((UINT) 0)) return; +#ifdef _WIN32 + watchdog_id = _beginthreadex(TX_NULL, 0u, test_teardown_watchdog, TX_NULL, 0u, TX_NULL); + if (watchdog_id != ((uintptr_t) 0)) + { + (void) CloseHandle((HANDLE) watchdog_id); + } +#else if (pthread_create(&watchdog_id, TX_NULL, test_teardown_watchdog, TX_NULL) == 0) pthread_detach(watchdog_id); +#endif } From 2ba4913f0ed56fcf2a24148fe2c629905d4cb1b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Thu, 3 Sep 2026 08:25:24 -0400 Subject: [PATCH 19/21] Added the required Codex disclosure to the trace regression Assisted-by: Codex (gpt-5.6-sol) --- test/tx/regression/threadx_trace_entry_update_test.c | 1 + 1 file changed, 1 insertion(+) diff --git a/test/tx/regression/threadx_trace_entry_update_test.c b/test/tx/regression/threadx_trace_entry_update_test.c index 3d4b55b09..6c6d96143 100644 --- a/test/tx/regression/threadx_trace_entry_update_test.c +++ b/test/tx/regression/threadx_trace_entry_update_test.c @@ -12,6 +12,7 @@ * * SPDX-License-Identifier: MIT and CC0-1.0 **************************************************************************/ +// Some portions generated by Codex (gpt-5.6-sol). /* This test drives the trace entry update paths -- the blocks guarded by TX_ENABLE_EVENT_TRACE that go back and patch a trace entry after the call From 1a0247b7e59d5cc74a3f7c2d3aeb3e5c806732f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Fri, 11 Sep 2026 13:24:06 -0400 Subject: [PATCH 20/21] Fixed thread-transition tests for MSVC Used MSVC forced-include and undefine options while preserving the existing GCC-family command line. Assisted-by: Codex (gpt-5.6-sol) --- .../tx/cmake/thread_transition/CMakeLists.txt | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/test/tx/cmake/thread_transition/CMakeLists.txt b/test/tx/cmake/thread_transition/CMakeLists.txt index f414ed675..037a6cb96 100644 --- a/test/tx/cmake/thread_transition/CMakeLists.txt +++ b/test/tx/cmake/thread_transition/CMakeLists.txt @@ -1,3 +1,5 @@ +# Some portions generated by Codex (gpt-5.6-sol). + cmake_minimum_required(VERSION 3.13 FATAL_ERROR) cmake_policy(SET CMP0057 NEW) @@ -56,11 +58,22 @@ set(transition_sources # opposite, because calling the public names and letting tx_api.h decide what they # bind to is exactly what the TX_DISABLE_ERROR_CHECKING configuration is here to # demonstrate. +if(MSVC) + set(transition_kernel_compile_options + "/FI${SOURCE_DIR}/threadx_thread_transition_host_test_port.h") + set(transition_undefine_options /UTX_ENABLE_EVENT_TRACE + /UTX_ENABLE_EVENT_LOG) +else() + set(transition_kernel_compile_options + "-include;${SOURCE_DIR}/threadx_thread_transition_host_test_port.h") + set(transition_undefine_options -UTX_ENABLE_EVENT_TRACE + -UTX_ENABLE_EVENT_LOG) +endif() + set_source_files_properties( ${transition_kernel_sources} DIRECTORY ${CMAKE_CURRENT_LIST_DIR} - PROPERTIES COMPILE_OPTIONS - "-include;${SOURCE_DIR}/threadx_thread_transition_host_test_port.h") + PROPERTIES COMPILE_OPTIONS "${transition_kernel_compile_options}") # Each entry is a test-name suffix, a colon, and the feature macros that define the # configuration, separated by "|". A semicolon cannot be used as that separator: it @@ -103,12 +116,11 @@ foreach(configuration ${transition_configurations}) # lost by turning tracing off here. Event logging is turned off for the same # reason; no configuration of the tree enables it. # - # The -U flags have to reach the test source as well as the kernel sources, + # The undefine options have to reach the test source as well as the kernel sources, # because TX_ENABLE_EVENT_TRACE is visible to tx_api.h and the two must agree on # what the headers declare. Target compile options land after the directory's - # -D flags on the command line, which is what makes the -U effective. - target_compile_options(${test_name} PRIVATE -UTX_ENABLE_EVENT_TRACE - -UTX_ENABLE_EVENT_LOG) + # definition flags on the command line, which is what makes them effective. + target_compile_options(${test_name} PRIVATE ${transition_undefine_options}) add_test(${CMAKE_BUILD_TYPE}::${test_name} ${test_name}) From 649556fa58ea49d49fd0422f7a9c970a238be105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Fri, 11 Sep 2026 13:39:00 -0400 Subject: [PATCH 21/21] Shortened MSVC thread-transition target paths Assisted-by: Codex (gpt-5.6-sol) --- test/tx/cmake/thread_transition/CMakeLists.txt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/test/tx/cmake/thread_transition/CMakeLists.txt b/test/tx/cmake/thread_transition/CMakeLists.txt index 037a6cb96..a56592382 100644 --- a/test/tx/cmake/thread_transition/CMakeLists.txt +++ b/test/tx/cmake/thread_transition/CMakeLists.txt @@ -93,16 +93,20 @@ foreach(configuration ${transition_configurations}) string(REPLACE "|" ";" configuration_macros ${configuration_macro_text}) set(test_name threadx_thread_transition_${configuration_name}_test) + # Keep the internal target name short. CMake includes it in every object path, + # and the descriptive test name can otherwise reach the legacy MSVC MAX_PATH + # boundary in a normally nested Windows worktree. + set(test_target tx_tt_${configuration_name}) - add_executable(${test_name} ${transition_sources}) + add_executable(${test_target} ${transition_sources}) target_include_directories( - ${test_name} + ${test_target} PRIVATE ${SOURCE_DIR} ${REPO_ROOT}/common/inc ${REPO_ROOT}/ports/${THREADX_ARCH}/${THREADX_TOOLCHAIN}/inc) - target_compile_definitions(${test_name} PRIVATE ${configuration_macros}) + target_compile_definitions(${test_target} PRIVATE ${configuration_macros}) # This directory is configured once per build configuration of the tree, so # these executables inherit whichever feature macros that configuration sets -- @@ -120,8 +124,8 @@ foreach(configuration ${transition_configurations}) # because TX_ENABLE_EVENT_TRACE is visible to tx_api.h and the two must agree on # what the headers declare. Target compile options land after the directory's # definition flags on the command line, which is what makes them effective. - target_compile_options(${test_name} PRIVATE ${transition_undefine_options}) + target_compile_options(${test_target} PRIVATE ${transition_undefine_options}) - add_test(${CMAKE_BUILD_TYPE}::${test_name} ${test_name}) + add_test(${CMAKE_BUILD_TYPE}::${test_name} ${test_target}) endforeach()