From a70bb6a4af09e7ae22d13b5c936da3a667462bf1 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Fri, 21 Aug 2026 08:04:16 +1000 Subject: [PATCH 01/11] Modernise Pantheios.Extras.AtExit version macros for 0.1.2-alpha1. Replace the legacy named-constant PANTHEIOS_EXTRAS_ATEXIT_VER with the computed MAJOR/MINOR/PATCH/ALPHABETA form used by BDUT and WilliamsCRC. --- CHANGES.md | 5 ++++ NEWS.md | 1 + include/pantheios/extras/atexit.h | 6 ++-- include/pantheios/extras/atexit/atexit.h | 38 ++++++++++++++++++------ 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0bc7be0..f344d4f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,11 @@ # Pantheios.Extras.AtExit - Changes +## 0.1.2-alpha1 - 21st August 2026 + +* Modernised library version macros to computed `PANTHEIOS_EXTRAS_ATEXIT_VER` (`VER_MAJOR` / `VER_MINOR` / `VER_PATCH` / `VER_ALPHABETA`, with `VER_REVISION` alias) targeting **0.1.2-alpha1**; + + ## 0.1.1 - 16th August 2026 * Recovered **Pantheios.Extras.AtExit** from Hautacam (`pantheios/extras/AtExit/0.1`, Created 30th December 2011) as a nested compiled C library; diff --git a/NEWS.md b/NEWS.md index fad4113..6ded513 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,6 +10,7 @@ | Date | News Item | | ---------------- | -------------------------------------------------------------------------------- | +| 21st August 2026 | Pantheios.Extras.AtExit 0.1.2-alpha1 | | 16th August 2026 | Pantheios.Extras.AtExit 0.1.1 recovered and released | diff --git a/include/pantheios/extras/atexit.h b/include/pantheios/extras/atexit.h index e708ff5..9b377ae 100644 --- a/include/pantheios/extras/atexit.h +++ b/include/pantheios/extras/atexit.h @@ -4,7 +4,7 @@ * Purpose: Main C header for the Pantheios.Extras.AtExit library. * * Created: 30th December 2011 - * Updated: 16th August 2026 + * Updated: 21st August 2026 * * Home: http://www.pantheios.org/ * @@ -55,8 +55,8 @@ #ifndef PANTHEIOS_DOCUMENTATION_SKIP_SECTION # define PANTHEIOS_EXTRAS_ATEXIT_VER_PANTHEIOS_EXTRAS_H_ATEXIT_MAJOR 1 # define PANTHEIOS_EXTRAS_ATEXIT_VER_PANTHEIOS_EXTRAS_H_ATEXIT_MINOR 0 -# define PANTHEIOS_EXTRAS_ATEXIT_VER_PANTHEIOS_EXTRAS_H_ATEXIT_REVISION 3 -# define PANTHEIOS_EXTRAS_ATEXIT_VER_PANTHEIOS_EXTRAS_H_ATEXIT_EDIT 4 +# define PANTHEIOS_EXTRAS_ATEXIT_VER_PANTHEIOS_EXTRAS_H_ATEXIT_REVISION 4 +# define PANTHEIOS_EXTRAS_ATEXIT_VER_PANTHEIOS_EXTRAS_H_ATEXIT_EDIT 5 #endif /* !PANTHEIOS_DOCUMENTATION_SKIP_SECTION */ /* ///////////////////////////////////////////////////////////////////////// diff --git a/include/pantheios/extras/atexit/atexit.h b/include/pantheios/extras/atexit/atexit.h index a478ae7..98fdd5e 100644 --- a/include/pantheios/extras/atexit/atexit.h +++ b/include/pantheios/extras/atexit/atexit.h @@ -4,7 +4,7 @@ * Purpose: Header file for Pantheios.Extras.AtExit. * * Created: 30th December 2011 - * Updated: 16th August 2026 + * Updated: 21st August 2026 * * Home: http://www.pantheios.org/ * @@ -54,9 +54,9 @@ #ifndef PANTHEIOS_DOCUMENTATION_SKIP_SECTION # define PANTHEIOS_EXTRAS_ATEXIT_VER_PANTHEIOS_EXTRAS_ATEXIT_H_ATEXIT_MAJOR 1 -# define PANTHEIOS_EXTRAS_ATEXIT_VER_PANTHEIOS_EXTRAS_ATEXIT_H_ATEXIT_MINOR 1 -# define PANTHEIOS_EXTRAS_ATEXIT_VER_PANTHEIOS_EXTRAS_ATEXIT_H_ATEXIT_REVISION 1 -# define PANTHEIOS_EXTRAS_ATEXIT_VER_PANTHEIOS_EXTRAS_ATEXIT_H_ATEXIT_EDIT 5 +# define PANTHEIOS_EXTRAS_ATEXIT_VER_PANTHEIOS_EXTRAS_ATEXIT_H_ATEXIT_MINOR 2 +# define PANTHEIOS_EXTRAS_ATEXIT_VER_PANTHEIOS_EXTRAS_ATEXIT_H_ATEXIT_REVISION 0 +# define PANTHEIOS_EXTRAS_ATEXIT_VER_PANTHEIOS_EXTRAS_ATEXIT_H_ATEXIT_EDIT 6 #endif /* !PANTHEIOS_DOCUMENTATION_SKIP_SECTION */ /** \def PANTHEIOS_EXTRAS_ATEXIT_VER_MAJOR @@ -67,23 +67,43 @@ * The minor version number of Pantheios.Extras.AtExit */ -/** \def PANTHEIOS_EXTRAS_ATEXIT_VER_REVISION - * The revision version number of Pantheios.Extras.AtExit +/** \def PANTHEIOS_EXTRAS_ATEXIT_VER_PATCH + * The patch version number of Pantheios.Extras.AtExit + */ + +/** \def PANTHEIOS_EXTRAS_ATEXIT_VER_ALPHABETA + * The alpha/beta designator of Pantheios.Extras.AtExit, or 0xFF for a + * non-prerelease */ /** \def PANTHEIOS_EXTRAS_ATEXIT_VER * The current composite version number of Pantheios.Extras.AtExit */ +/** \def PANTHEIOS_EXTRAS_ATEXIT_VER_REVISION + * Alias of PANTHEIOS_EXTRAS_ATEXIT_VER_PATCH (for CMake scrapers / older + * consumers) + */ + #ifndef PANTHEIOS_DOCUMENTATION_SKIP_SECTION # define PANTHEIOS_EXTRAS_ATEXIT_VER_0_1_1 0x000101ff +# define PANTHEIOS_EXTRAS_ATEXIT_VER_0_1_2_ALPHA_1 0x00010241 #endif /* !PANTHEIOS_DOCUMENTATION_SKIP_SECTION */ #define PANTHEIOS_EXTRAS_ATEXIT_VER_MAJOR 0 #define PANTHEIOS_EXTRAS_ATEXIT_VER_MINOR 1 -#define PANTHEIOS_EXTRAS_ATEXIT_VER_REVISION 1 - -#define PANTHEIOS_EXTRAS_ATEXIT_VER PANTHEIOS_EXTRAS_ATEXIT_VER_0_1_1 +#define PANTHEIOS_EXTRAS_ATEXIT_VER_PATCH 2 +#define PANTHEIOS_EXTRAS_ATEXIT_VER_ALPHABETA 0x41 + +#define PANTHEIOS_EXTRAS_ATEXIT_VER \ + (0\ + | ( PANTHEIOS_EXTRAS_ATEXIT_VER_MAJOR << 24 ) \ + | ( PANTHEIOS_EXTRAS_ATEXIT_VER_MINOR << 16 ) \ + | ( PANTHEIOS_EXTRAS_ATEXIT_VER_PATCH << 8 ) \ + | ( PANTHEIOS_EXTRAS_ATEXIT_VER_ALPHABETA << 0 ) \ + ) + +#define PANTHEIOS_EXTRAS_ATEXIT_VER_REVISION PANTHEIOS_EXTRAS_ATEXIT_VER_PATCH #ifdef __cplusplus extern "C" { From 74f76561130cdfb8d51444437499c848ff6a949f Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Fri, 21 Aug 2026 08:04:36 +1000 Subject: [PATCH 02/11] .vimrc --- .vimrc | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 .vimrc diff --git a/.vimrc b/.vimrc new file mode 100644 index 0000000..1bf33a0 --- /dev/null +++ b/.vimrc @@ -0,0 +1,70 @@ +" Synesis C/C++ project .vimrc — aligned with .sis/.vscode/c_cxx/settings.json + +set nocompatible +filetype indent plugin on +syntax enable +set autoindent +set backspace=indent,eol,start +set hlsearch +set incsearch +set number + +" files.insertFinalNewline +set eol +set fixeol + +" editor.renderWhitespace: all +set list +set listchars=tab:->,trail:-,extends:>,precedes:<,nbsp:+ + +" editor.detectIndentation: false — global defaults (editor.tabSize: 4, insertSpaces: true) +set colorcolumn=76 +set expandtab +set shiftwidth=4 +set softtabstop=4 +set tabstop=4 + +" colorcolumn draws a full-column tint in Vim (not a VS Code-style 1px line). +" Keep it subtle via the ColorColumn highlight group; reapply after colorscheme changes. +if has('termguicolors') + " set termguicolors +endif + +function! s:ConfigureColorColumn() abort + highlight ColorColumn ctermbg=236 guibg=#2a2a2a cterm=NONE gui=NONE +endfunction + +call s:ConfigureColorColumn() +autocmd ColorScheme * call s:ConfigureColorColumn() + +" files.trimTrailingWhitespace +autocmd BufWritePre * %s/\s\+$//e + +augroup sis_c_cxx + autocmd! + + " [c] / [cpp] + autocmd FileType c,cpp setlocal expandtab tabstop=4 shiftwidth=4 softtabstop=4 colorcolumn=60,64,68,72,76 + + " [rust] + autocmd FileType rs setlocal expandtab tabstop=4 shiftwidth=4 softtabstop=4 colorcolumn=76 + + " [cmake] + autocmd FileType cmake setlocal noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 + + " [shellscript] + autocmd FileType sh,bash,zsh setlocal expandtab tabstop=2 shiftwidth=2 softtabstop=2 colorcolumn=60,76 + + " [bat] + autocmd FileType bat,dosbatch setlocal expandtab tabstop=4 shiftwidth=4 softtabstop=4 colorcolumn=60,76 + + " [json] / [markdown] / [yaml] / [ruby] + autocmd FileType json,markdown,yaml,ruby setlocal expandtab tabstop=2 shiftwidth=2 softtabstop=2 + + " [python] + autocmd FileType python setlocal expandtab tabstop=4 shiftwidth=4 softtabstop=4 colorcolumn=60,76 + + " [toml] + autocmd FileType toml setlocal noexpandtab tabstop=2 shiftwidth=2 softtabstop=2 +augroup END + From 044c0d0215837e1a5f67554e0e52df7fb00bb8d0 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Fri, 21 Aug 2026 08:15:17 +1000 Subject: [PATCH 03/11] Add AtExit CTest/Windows runners and tidy C source basics. Bring helper parity with ctest_cmake.sh and run_all_unit_tests.cmd, and align EOF blank lines plus the unit-test argv form with gold C libraries. --- .github/workflows/ci.yml | 4 +- CHANGES.md | 2 + ctest_cmake.sh | 108 ++++++++++ examples/c/example.c.1/example.c.1.c | 1 + include/pantheios/extras/atexit.h | 1 + include/pantheios/extras/atexit/atexit.h | 1 + run_all_unit_tests.cmd | 188 ++++++++++++++++++ src/atexit.api.c | 1 + .../test.scratch.atexit/test.scratch.atexit.c | 1 + .../test.unit.atexit.api.c | 3 +- 10 files changed, 307 insertions(+), 3 deletions(-) create mode 100755 ctest_cmake.sh create mode 100644 run_all_unit_tests.cmd diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f985e9..68f2398 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,10 +3,10 @@ name: CI on: push: branches: - - boilerplate + - master - dev + - boilerplate - idiomatic - - master - rc1 - rc2 - rc3 diff --git a/CHANGES.md b/CHANGES.md index f344d4f..30a190c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,8 @@ ## 0.1.2-alpha1 - 21st August 2026 * Modernised library version macros to computed `PANTHEIOS_EXTRAS_ATEXIT_VER` (`VER_MAJOR` / `VER_MINOR` / `VER_PATCH` / `VER_ALPHABETA`, with `VER_REVISION` alias) targeting **0.1.2-alpha1**; +* Added **ctest_cmake.sh** and **run_all_unit_tests.cmd**; +* Ensured C/C++ sources end with a blank line after the end-of-file marker, and used `char* argv[]` in the unit-test `main`; ## 0.1.1 - 16th August 2026 diff --git a/ctest_cmake.sh b/ctest_cmake.sh new file mode 100755 index 0000000..24c2172 --- /dev/null +++ b/ctest_cmake.sh @@ -0,0 +1,108 @@ +#! /bin/bash + +ScriptPath=$0 +Dir=$(cd $(dirname "$ScriptPath"); pwd) +Basename=$(basename "$ScriptPath") +CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} +[[ -n "$MSYSTEM" ]] && DefaultMakeCmd=mingw32-make.exe || DefaultMakeCmd=make +MakeCmd=${SIS_CMAKE_MAKE_COMMAND:-${SIS_CMAKE_COMMAND:-$DefaultMakeCmd}} +ProjectNameFile="$Dir/.sis/project_name.txt" +ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") + +CMakeVerbose= +RunMake=1 + + +# ########################################################## +# command-line handling + +while [[ $# -gt 0 ]]; do + + case $1 in + --no-make|-M) + + RunMake=0 + ;; + --verbose|-V) + + CMakeVerbose=--verbose + ;; + --help) + + [ -f "$Dir/.sis/script_info_lines.txt" ] && cat "$Dir/.sis/script_info_lines.txt" + cat << EOF +Runs CMake's CTest test program(s) + +$ScriptPath [ ... flags/options ... ] + +Flags/options: + + behaviour: + + -M + --no-make + does not execute CMake and make before running tests + + -V + --verbose + verbose test output + + + standard flags: + + --help + displays this help and terminates + +EOF + + exit 0 + ;; + *) + + >&2 echo "$ScriptPath: unrecognised argument '$1'; use --help for usage" + + exit 1 + ;; + esac + + shift +done + + +# ########################################################## +# main() + +status=0 + +if [ $RunMake -ne 0 ]; then + + echo "Executing build for ${ProjectName} (via command \`$MakeCmd\`) and then running all component and unit test programs" + + mkdir -p $CMakeDir || exit 1 + + cd $CMakeDir + + $MakeCmd + status=$? + + cd ->/dev/null +else + + if [ ! -d "$CMakeDir" ] || [ ! -f "$CMakeDir/CMakeCache.txt" ] || [ ! -d "$CMakeDir/CMakeFiles" ]; then + + >&2 echo "$ScriptPath: cannot run in '--no-make' mode without a previous successful build step" + fi +fi + +if [ $status -eq 0 ]; then + + echo "Running CMake tests" + + ctest --test-dir $CMakeDir $CMakeVerbose + status=$? +fi + +exit $status + + +# ############################## end of file ############################# # diff --git a/examples/c/example.c.1/example.c.1.c b/examples/c/example.c.1/example.c.1.c index 56b4978..8e1ff7b 100644 --- a/examples/c/example.c.1/example.c.1.c +++ b/examples/c/example.c.1/example.c.1.c @@ -60,3 +60,4 @@ main(void) /* ///////////////////////////// end of file //////////////////////////// */ + diff --git a/include/pantheios/extras/atexit.h b/include/pantheios/extras/atexit.h index 9b377ae..4f75829 100644 --- a/include/pantheios/extras/atexit.h +++ b/include/pantheios/extras/atexit.h @@ -76,3 +76,4 @@ #endif /* !PANTHEIOS_EXTRAS_ATEXIT_INCL_PANTHEIOS_EXTRAS_H_ATEXIT */ /* ///////////////////////////// end of file //////////////////////////// */ + diff --git a/include/pantheios/extras/atexit/atexit.h b/include/pantheios/extras/atexit/atexit.h index 98fdd5e..d6dd9e4 100644 --- a/include/pantheios/extras/atexit/atexit.h +++ b/include/pantheios/extras/atexit/atexit.h @@ -185,3 +185,4 @@ pantheios_extras_atexit_add( #endif /* !PANTHEIOS_EXTRAS_ATEXIT_INCL_PANTHEIOS_EXTRAS_ATEXIT_H_ATEXIT */ /* ///////////////////////////// end of file //////////////////////////// */ + diff --git a/run_all_unit_tests.cmd b/run_all_unit_tests.cmd new file mode 100644 index 0000000..b68c97e --- /dev/null +++ b/run_all_unit_tests.cmd @@ -0,0 +1,188 @@ +@echo off + +SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION + +SET SCRIPT_DIRECTORY=%~dp0 +SET SCRIPT_PATH_DOC=%~n0[%~x0] +IF DEFINED SIS_CMAKE_BUILD_DIR ( + + SET "CMAKE_DIR=%SIS_CMAKE_BUILD_DIR%" +) ELSE ( + + SET "CMAKE_DIR=%SCRIPT_DIRECTORY%_build" +) + +SET ListOnly=0 +SET Verbose=0 +SET UnitOnly=0 +SET ComponentOnly=0 +SET status=0 + +FOR %%a IN (%*) DO ( + IF /I {--help}=={%%a} ( + IF EXIST "%SCRIPT_DIRECTORY%.sis\script_info_lines.txt" ( + + type "%SCRIPT_DIRECTORY%.sis\script_info_lines.txt" + ) + ECHO ^ + +Runs all ^(matching^) unit-test and/or component-test programs ^ + +^ + +%SCRIPT_PATH_DOC% [ ... flags/options ... ] ^ + +^ + +Flags/options: ^ + + behaviour: ^ + +^ + + -l ^ + + --list-only ^ + + lists the target programs but does not execute them ^ + +^ + + -M ^ + + --no-make ^ + + accepted for parity with the .sh script ^(build is not invoked^) ^ + +^ + + --unit-only ^ + + runs only unit-test programs ^(test.unit.* / test_unit*^) ^ + +^ + + --component-only ^ + + runs only component-test programs ^(test.component.* / test_component*^) ^ + +^ + + -v ^ + + --verbose ^ + + lists each test program before executing it ^ + +^ + + standard flags: ^ + +^ + + --help ^ + + displays this help and terminates ^ + + + EXIT /B 0 + ) ELSE IF /I {-l}=={%%a} ( + SET ListOnly=1 + ) ELSE IF /I {--list-only}=={%%a} ( + SET ListOnly=1 + ) ELSE IF /I {-M}=={%%a} ( + REM no-op: this .cmd never invokes the build + ) ELSE IF /I {--no-make}=={%%a} ( + REM no-op: this .cmd never invokes the build + ) ELSE IF /I {--unit-only}=={%%a} ( + SET UnitOnly=1 + ) ELSE IF /I {--component-only}=={%%a} ( + SET ComponentOnly=1 + ) ELSE IF /I {-v}=={%%a} ( + SET Verbose=1 + ) ELSE IF /I {--verbose}=={%%a} ( + SET Verbose=1 + ) ELSE ( + ECHO %SCRIPT_PATH_DOC%: unrecognised argument '%%a'; use --help for usage 1>&2 + + EXIT /B 1 + ) +) + +IF !UnitOnly! EQU 1 IF !ComponentOnly! EQU 1 ( + + ECHO %SCRIPT_PATH_DOC%: --unit-only and --component-only are mutually exclusive 1>&2 + + EXIT /B 1 +) + +IF NOT EXIST "%CMAKE_DIR%" ( + + ECHO %SCRIPT_PATH_DOC%: CMake build directory '%CMAKE_DIR%' does not exist 1>&2 + + EXIT /B 1 +) + +SET "ProjectName=" +FOR /F "usebackq delims=" %%p IN ("%SCRIPT_DIRECTORY%.sis\project_name.txt") DO SET "ProjectName=%%p" + +IF NOT DEFINED ProjectName ( + + ECHO %SCRIPT_PATH_DOC%: could not read project name from .sis\project_name.txt 1>&2 + + EXIT /B 1 +) + +IF !UnitOnly! EQU 1 ( + + SET "TestKindDescription=unit test" +) ELSE IF !ComponentOnly! EQU 1 ( + + SET "TestKindDescription=component test" +) ELSE ( + + SET "TestKindDescription=component and unit test" +) + +IF !ListOnly! EQU 1 ( + + ECHO Listing all !ProjectName! !TestKindDescription! programs +) ELSE ( + + ECHO Running all !ProjectName! !TestKindDescription! programs +) + +IF !UnitOnly! EQU 1 ( + + SET "FindPattern=test\.unit\..*\.exe$" +) ELSE IF !ComponentOnly! EQU 1 ( + + SET "FindPattern=test\.component\..*\.exe$" +) ELSE ( + + SET "FindPattern=test\.unit\..*\.exe$ test\.component\..*\.exe$" +) + +FOR /F "usebackq delims=" %%f IN (`DIR /A:-D /B /S "%CMAKE_DIR%\*.exe" 2^>NUL ^| FINDSTR /I /R "!FindPattern!"`) DO ( + IF !ListOnly! EQU 1 ( + + ECHO would execute %%f: + ) ELSE ( + + IF !Verbose! EQU 1 ( + + ECHO executing %%f: + ) + + "%%f" + IF ERRORLEVEL 1 ( + + SET status=1 + + GOTO :done + ) + ) +) + +:done +EXIT /B !status! diff --git a/src/atexit.api.c b/src/atexit.api.c index 65d9b2d..dc82a62 100644 --- a/src/atexit.api.c +++ b/src/atexit.api.c @@ -248,3 +248,4 @@ pantheios_extras_atexit_log_string_( } /* ///////////////////////////// end of file //////////////////////////// */ + diff --git a/test/scratch/test.scratch.atexit/test.scratch.atexit.c b/test/scratch/test.scratch.atexit/test.scratch.atexit.c index 92db08f..fd43c5d 100644 --- a/test/scratch/test.scratch.atexit/test.scratch.atexit.c +++ b/test/scratch/test.scratch.atexit/test.scratch.atexit.c @@ -48,3 +48,4 @@ main(void) /* ///////////////////////////// end of file //////////////////////////// */ + diff --git a/test/unit/test.unit.atexit.api/test.unit.atexit.api.c b/test/unit/test.unit.atexit.api/test.unit.atexit.api.c index 2c8b6f6..e844c5a 100644 --- a/test/unit/test.unit.atexit.api/test.unit.atexit.api.c +++ b/test/unit/test.unit.atexit.api/test.unit.atexit.api.c @@ -100,7 +100,7 @@ test_init_after_uninit_fails(void) int -main(int argc, char** argv) +main(int argc, char* argv[]) { int retCode = EXIT_SUCCESS; int verbosity = 2; @@ -128,3 +128,4 @@ main(int argc, char** argv) /* ///////////////////////////// end of file //////////////////////////// */ + From d5bcf6d7d9205e095f113e6d9b8d21cb7662059f Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Fri, 21 Aug 2026 08:22:29 +1000 Subject: [PATCH 04/11] Record AtExit functionality-review items in TODO.md. Capture high/medium/low contract, locking, and test gaps from the in-depth review so they are tracked before implementation. --- TODO.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/TODO.md b/TODO.md index 4d4c98b..2e9b1ff 100644 --- a/TODO.md +++ b/TODO.md @@ -3,6 +3,30 @@ ## Functional improvements + +### High + +* [ ] Document (and optionally enforce) **`add` after `uninit`**: the libc `atexit` hook stays registered, so later **`add`** callbacks still run at process exit; docs currently imply the hook is a permanent no-op after drain; +* [ ] Document (or change) drain-while-locked: callbacks run under the spinlock, so a callback that calls **`add`** / **`uninit`** deadlocks; either forbid re-entrancy or copy the list, unlock, then invoke; + + +### Medium + +* [ ] Serialise **`init`**: `s_initialised` is a plain `int` and `init` is not under the lock, so two threads can register two `atexit` hooks; +* [ ] Align **`reserved0`** / **`reserved1`**: header says they must be `NULL` / `0`, but the implementation ignores them — `assert` or drop the requirement; +* [ ] Stop treating **`atexit()`** failure as an `errno` / **`strerror()`** code; `EBUSY` / `ENOMEM` are errno values, `atexit` failure often is not; + + +### Low + +* [ ] Document **`add` without `init`**: the list grows, but process exit will not drain it unless **`uninit`** is called; +* [ ] Initialise C11 `atomic_int s_mx` with **`ATOMIC_VAR_INIT(0)`** if compilers warn on `= 0`; +* [ ] Tests: **`add`** without **`init`** then **`uninit`**; **`add`** after **`uninit`**; version-macro unit test; keep process-exit coverage as scratch (or one automated case); +* [ ] Example: check **`add`** return values; + + +### Enhancements + * [ ] C++ RAII / `scope` wrapper; * [ ] Unix-only pthread synchronisation scratch (with a Windows equivalent if revived); From ac872d9b7b353ebc0a12c20483328a19abe3475e Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Fri, 21 Aug 2026 08:25:11 +1000 Subject: [PATCH 05/11] Align AtExit scratch naming and park remaining helper/API work. Rename the scratch program to main.c, and record the MSYSTEM/MinGW helper deferral plus the postponed functionality findings in TODO.md. --- CHANGES.md | 2 ++ TODO.md | 3 ++- test/scratch/test.scratch.atexit/CMakeLists.txt | 2 +- .../test.scratch.atexit/{test.scratch.atexit.c => main.c} | 5 ++--- 4 files changed, 7 insertions(+), 5 deletions(-) rename test/scratch/test.scratch.atexit/{test.scratch.atexit.c => main.c} (93%) diff --git a/CHANGES.md b/CHANGES.md index 30a190c..1849ef6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,8 @@ * Modernised library version macros to computed `PANTHEIOS_EXTRAS_ATEXIT_VER` (`VER_MAJOR` / `VER_MINOR` / `VER_PATCH` / `VER_ALPHABETA`, with `VER_REVISION` alias) targeting **0.1.2-alpha1**; * Added **ctest_cmake.sh** and **run_all_unit_tests.cmd**; * Ensured C/C++ sources end with a blank line after the end-of-file marker, and used `char* argv[]` in the unit-test `main`; +* Renamed the scratch program implementation to **main.c**; +* Recorded functionality-review items in **TODO.md**; deferred applying them, and deferred the helper **MSYSTEM**/MinGW rewrite until the **HELPER-SCRIPTS-C-CXX.md** contract is frozen; ## 0.1.1 - 16th August 2026 diff --git a/TODO.md b/TODO.md index 2e9b1ff..763afa2 100644 --- a/TODO.md +++ b/TODO.md @@ -38,7 +38,8 @@ ## Packaging improvements -* \ +* [ ] Defer rewriting helper `MSYSTEM` → `mingw32-make` / MinGW-generator detection until the **HELPER-SCRIPTS-C-CXX.md** contract is frozen (same defect class as other Synesis C/C++ helpers); +* [ ] Split the unit-test implementation from its entry TU (then name the entry **entry.c**); until then keep the single-file **test.unit.atexit.api.c**; diff --git a/test/scratch/test.scratch.atexit/CMakeLists.txt b/test/scratch/test.scratch.atexit/CMakeLists.txt index 91d1d67..67169b4 100644 --- a/test/scratch/test.scratch.atexit/CMakeLists.txt +++ b/test/scratch/test.scratch.atexit/CMakeLists.txt @@ -1 +1 @@ -define_example_program(test.scratch.atexit test.scratch.atexit.c) +define_example_program(test.scratch.atexit main.c) diff --git a/test/scratch/test.scratch.atexit/test.scratch.atexit.c b/test/scratch/test.scratch.atexit/main.c similarity index 93% rename from test/scratch/test.scratch.atexit/test.scratch.atexit.c rename to test/scratch/test.scratch.atexit/main.c index fd43c5d..e554600 100644 --- a/test/scratch/test.scratch.atexit/test.scratch.atexit.c +++ b/test/scratch/test.scratch.atexit/main.c @@ -1,10 +1,10 @@ /* ///////////////////////////////////////////////////////////////////////// - * File: test.scratch.atexit.c + * File: main.c * * Purpose: Scratch test for Pantheios.Extras.AtExit. * * Created: 30th December 2011 - * Updated: 16th August 2026 + * Updated: 21st August 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -48,4 +48,3 @@ main(void) /* ///////////////////////////// end of file //////////////////////////// */ - From 0f313011205b899658ab8c2fbec752dd31785bd5 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Fri, 21 Aug 2026 08:30:37 +1000 Subject: [PATCH 06/11] Retarget Pantheios.Extras.AtExit to 0.1.3 for 23rd August 2026. Ship the current work as a release (0xFF alphabeta) rather than 0.1.2-alpha1, and align CHANGES.md and NEWS.md to that date. --- CHANGES.md | 6 ++++++ NEWS.md | 1 + TODO.md | 2 +- include/pantheios/extras/atexit/atexit.h | 14 +++++++++----- src/atexit.api.c | 5 ++++- 5 files changed, 21 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1849ef6..6e38187 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,12 @@ # Pantheios.Extras.AtExit - Changes +## 0.1.3 - 23rd August 2026 + +* Retargeted the current line from **0.1.2-alpha1** to **0.1.3**; +* Asserted **`pantheios_extras_atexit_init()`** `reserved0` / `reserved1` as `NULL` / `0`; + + ## 0.1.2-alpha1 - 21st August 2026 * Modernised library version macros to computed `PANTHEIOS_EXTRAS_ATEXIT_VER` (`VER_MAJOR` / `VER_MINOR` / `VER_PATCH` / `VER_ALPHABETA`, with `VER_REVISION` alias) targeting **0.1.2-alpha1**; diff --git a/NEWS.md b/NEWS.md index 6ded513..5cc0f5a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,6 +10,7 @@ | Date | News Item | | ---------------- | -------------------------------------------------------------------------------- | +| 23rd August 2026 | Pantheios.Extras.AtExit 0.1.3 | | 21st August 2026 | Pantheios.Extras.AtExit 0.1.2-alpha1 | | 16th August 2026 | Pantheios.Extras.AtExit 0.1.1 recovered and released | diff --git a/TODO.md b/TODO.md index 763afa2..5fb89a8 100644 --- a/TODO.md +++ b/TODO.md @@ -13,7 +13,7 @@ ### Medium * [ ] Serialise **`init`**: `s_initialised` is a plain `int` and `init` is not under the lock, so two threads can register two `atexit` hooks; -* [ ] Align **`reserved0`** / **`reserved1`**: header says they must be `NULL` / `0`, but the implementation ignores them — `assert` or drop the requirement; +* [x] ~~~Align **`reserved0`** / **`reserved1`**: header says they must be `NULL` / `0`, but the implementation ignores them — `assert` or drop the requirement;~~~ ✅ * [ ] Stop treating **`atexit()`** failure as an `errno` / **`strerror()`** code; `EBUSY` / `ENOMEM` are errno values, `atexit` failure often is not; diff --git a/include/pantheios/extras/atexit/atexit.h b/include/pantheios/extras/atexit/atexit.h index d6dd9e4..696e8f1 100644 --- a/include/pantheios/extras/atexit/atexit.h +++ b/include/pantheios/extras/atexit/atexit.h @@ -4,7 +4,7 @@ * Purpose: Header file for Pantheios.Extras.AtExit. * * Created: 30th December 2011 - * Updated: 21st August 2026 + * Updated: 23rd August 2026 * * Home: http://www.pantheios.org/ * @@ -55,8 +55,8 @@ #ifndef PANTHEIOS_DOCUMENTATION_SKIP_SECTION # define PANTHEIOS_EXTRAS_ATEXIT_VER_PANTHEIOS_EXTRAS_ATEXIT_H_ATEXIT_MAJOR 1 # define PANTHEIOS_EXTRAS_ATEXIT_VER_PANTHEIOS_EXTRAS_ATEXIT_H_ATEXIT_MINOR 2 -# define PANTHEIOS_EXTRAS_ATEXIT_VER_PANTHEIOS_EXTRAS_ATEXIT_H_ATEXIT_REVISION 0 -# define PANTHEIOS_EXTRAS_ATEXIT_VER_PANTHEIOS_EXTRAS_ATEXIT_H_ATEXIT_EDIT 6 +# define PANTHEIOS_EXTRAS_ATEXIT_VER_PANTHEIOS_EXTRAS_ATEXIT_H_ATEXIT_REVISION 2 +# define PANTHEIOS_EXTRAS_ATEXIT_VER_PANTHEIOS_EXTRAS_ATEXIT_H_ATEXIT_EDIT 8 #endif /* !PANTHEIOS_DOCUMENTATION_SKIP_SECTION */ /** \def PANTHEIOS_EXTRAS_ATEXIT_VER_MAJOR @@ -88,12 +88,13 @@ #ifndef PANTHEIOS_DOCUMENTATION_SKIP_SECTION # define PANTHEIOS_EXTRAS_ATEXIT_VER_0_1_1 0x000101ff # define PANTHEIOS_EXTRAS_ATEXIT_VER_0_1_2_ALPHA_1 0x00010241 +# define PANTHEIOS_EXTRAS_ATEXIT_VER_0_1_3 0x000103ff #endif /* !PANTHEIOS_DOCUMENTATION_SKIP_SECTION */ #define PANTHEIOS_EXTRAS_ATEXIT_VER_MAJOR 0 #define PANTHEIOS_EXTRAS_ATEXIT_VER_MINOR 1 -#define PANTHEIOS_EXTRAS_ATEXIT_VER_PATCH 2 -#define PANTHEIOS_EXTRAS_ATEXIT_VER_ALPHABETA 0x41 +#define PANTHEIOS_EXTRAS_ATEXIT_VER_PATCH 3 +#define PANTHEIOS_EXTRAS_ATEXIT_VER_ALPHABETA 0xFF #define PANTHEIOS_EXTRAS_ATEXIT_VER \ (0\ @@ -131,6 +132,9 @@ extern "C" { * \warning Failure to call this function will mean that no callbacks * registered by pantheios_extras_atexit_add() will be invoked at process * exit (they may still be invoked by pantheios_extras_atexit_uninit()). + * + * \pre (NULL == reserved0) + * \pre (0 == reserved1) */ int pantheios_extras_atexit_init( diff --git a/src/atexit.api.c b/src/atexit.api.c index dc82a62..3337ba5 100644 --- a/src/atexit.api.c +++ b/src/atexit.api.c @@ -4,7 +4,7 @@ * Purpose: Implementation file for Pantheios.Extras.AtExit * * Created: 30th December 2011 - * Updated: 16th August 2026 + * Updated: 21st August 2026 * * Home: http://www.pantheios.org/ * @@ -169,6 +169,9 @@ pantheios_extras_atexit_init( { int r; + assert(NULL == reserved0); + assert(0 == reserved1); + ((void)reserved0); ((void)reserved1); From b363425932a6d13220ef40d8c9679b2275a57ee4 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Fri, 21 Aug 2026 08:51:29 +1000 Subject: [PATCH 07/11] Cover AtExit add failures in the example and extend unit tests. Check pantheios_extras_atexit_add results in example.c.1, and add coverage for add-without-init, add-after-uninit, and version macros. --- .vscode/settings.json | 182 ++++++++++++++++++ CHANGES.md | 2 + README.md | 1 + TODO.md | 4 +- examples/c/example.c.1/example.c.1.c | 35 +++- test/unit/CMakeLists.txt | 1 + .../test.unit.atexit.api.c | 38 +++- test/unit/test.unit.version/CMakeLists.txt | 1 + .../test.unit.version/test.unit.version.c | 68 +++++++ 9 files changed, 322 insertions(+), 10 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 test/unit/test.unit.version/CMakeLists.txt create mode 100644 test/unit/test.unit.version/test.unit.version.c diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3fe51d2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,182 @@ +{ + "[bat]": { + "editor.insertSpaces": true, + "editor.rulers": [ 60, 76 ], + "editor.tabSize": 4, + }, + "[c]": { + "editor.insertSpaces": true, + "editor.rulers": [ 60, 64, 68, 72, 76 ], + "editor.tabSize": 4, + }, + "[cmake]": { + "editor.insertSpaces": false, + "editor.tabSize": 4, + }, + "[cpp]": { + "editor.insertSpaces": true, + "editor.rulers": [ 60, 64, 68, 72, 76 ], + "editor.tabSize": 4, + }, + "[json]": { + "editor.insertSpaces": true, + "editor.tabSize": 2, + }, + "[markdown]": { + "editor.insertSpaces": true, + "editor.tabSize": 2, + }, + "[python]": { + "diffEditor.ignoreTrimWhitespace": false, + "editor.insertSpaces": true, + "editor.rulers": [ 51, 60, 61, 76 ], + "editor.tabSize": 4, + }, + "[ruby]": { + "editor.insertSpaces": true, + "editor.tabSize": 2, + }, + "[shellscript]": { + "editor.insertSpaces": true, + "editor.rulers": [ 60, 76 ], + "editor.tabSize": 2, + }, + "[toml]": { + "editor.insertSpaces": false, + "editor.tabSize": 2, + }, + "[yaml]": { + "editor.insertSpaces": true, + "editor.tabSize": 2, + }, + "cmake.configureOnOpen": false, + "editor.detectIndentation": false, + "editor.insertSpaces": false, + "editor.renderWhitespace": "all", + "editor.rulers": [ 76 ], + "editor.tabSize": 2, + "files.associations": { + "__bit_reference": "cpp", + "__bits": "cpp", + "__config": "cpp", + "__debug": "cpp", + "__errc": "cpp", + "__functional_03": "cpp", + "__functional_base": "cpp", + "__hash_table": "cpp", + "__locale": "cpp", + "__memory": "cpp", + "__mutex_base": "cpp", + "__node_handle": "cpp", + "__nullptr": "cpp", + "__split_buffer": "cpp", + "__string": "cpp", + "__threading_support": "cpp", + "__tree": "cpp", + "__tuple": "cpp", + "__verbose_abort": "cpp", + "algorithm": "cpp", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "charconv": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "complex": "cpp", + "concepts": "cpp", + "console_functions.h": "c", + "corecrt.h": "c", + "crtdefs.h": "c", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "exception": "cpp", + "execution": "cpp", + "fcntl.h": "c", + "format": "cpp", + "forward_list": "cpp", + "functional": "cpp", + "implicit_link.h": "c", + "initializer_list": "cpp", + "io.h": "c", + "iomanip": "cpp", + "ios": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "iterator": "cpp", + "limits": "cpp", + "list": "cpp", + "locale": "cpp", + "map": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "mutex": "cpp", + "new": "cpp", + "numbers": "cpp", + "numeric": "cpp", + "optional": "cpp", + "ostream": "cpp", + "queue": "cpp", + "random": "cpp", + "ranges": "cpp", + "ratio": "cpp", + "semaphore": "cpp", + "set": "cpp", + "setenv.h": "c", + "shwild.h": "c", + "span": "cpp", + "sstream": "cpp", + "stack": "cpp", + "stdexcept": "cpp", + "stdio.h": "c", + "stop_token": "cpp", + "streambuf": "cpp", + "string": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "terse-api.h": "c", + "text_encoding": "cpp", + "thread": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "typeinfo": "cpp", + "uio.h": "c", + "unixem.h": "c", + "unordered_map": "cpp", + "util.h": "c", + "utility": "cpp", + "variant": "cpp", + "vector": "cpp", + "xfacet": "cpp", + "xhash": "cpp", + "xiosbase": "cpp", + "xlocale": "cpp", + "xlocbuf": "cpp", + "xlocinfo": "cpp", + "xlocmes": "cpp", + "xlocmon": "cpp", + "xlocnum": "cpp", + "xloctime": "cpp", + "xmemory": "cpp", + "xstring": "cpp", + "xtests.internal.string.c": "cpp", + "xtr1common": "cpp", + "xtree": "cpp", + "xutility": "cpp", + }, + "files.insertFinalNewline": true, + "files.trimTrailingWhitespace": true, + "git.mergeEditor": false, +} diff --git a/CHANGES.md b/CHANGES.md index 6e38187..b9b686e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,8 @@ * Retargeted the current line from **0.1.2-alpha1** to **0.1.3**; * Asserted **`pantheios_extras_atexit_init()`** `reserved0` / `reserved1` as `NULL` / `0`; +* Example **example.c.1** checks **`add`** return values; unit tests cover **`add`** without **`init`**, **`add`** after **`uninit`**, and version macros; +* Added a **README.md** rationale for why libc `atexit()` is insufficient and what this library is for; ## 0.1.2-alpha1 - 21st August 2026 diff --git a/README.md b/README.md index 80d9f03..5518989 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Standalone C library that registers multiple `atexit`-style callbacks (function ## Table of Contents - [Introduction](#introduction) + - [Why at-exit functionality](#why-at-exit-functionality) - [Dependencies](#dependencies) - [Installation](#installation) - [Components](#components) diff --git a/TODO.md b/TODO.md index 5fb89a8..65503d5 100644 --- a/TODO.md +++ b/TODO.md @@ -21,8 +21,8 @@ * [ ] Document **`add` without `init`**: the list grows, but process exit will not drain it unless **`uninit`** is called; * [ ] Initialise C11 `atomic_int s_mx` with **`ATOMIC_VAR_INIT(0)`** if compilers warn on `= 0`; -* [ ] Tests: **`add`** without **`init`** then **`uninit`**; **`add`** after **`uninit`**; version-macro unit test; keep process-exit coverage as scratch (or one automated case); -* [ ] Example: check **`add`** return values; +* [x] ~~~Tests: **`add`** without **`init`** then **`uninit`**; **`add`** after **`uninit`**; version-macro unit test; keep process-exit coverage as scratch (or one automated case);~~~ ✅ +* [x] ~~~Example: check **`add`** return values;~~~ ✅ ### Enhancements diff --git a/examples/c/example.c.1/example.c.1.c b/examples/c/example.c.1/example.c.1.c index 8e1ff7b..6adcd2b 100644 --- a/examples/c/example.c.1/example.c.1.c +++ b/examples/c/example.c.1/example.c.1.c @@ -5,7 +5,7 @@ * callbacks (LIFO) and drain them via uninit(). * * Created: 30th December 2011 - * Updated: 16th August 2026 + * Updated: 21st August 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -30,6 +30,28 @@ fn2(void* param) } +static int +add( + void (*pfn)(void* param) +, void* param +) +{ + int const r = pantheios_extras_atexit_add(pfn, param); + + if (0 != r) + { + fprintf( + stderr + , "failed to add Pantheios.Extras.AtExit callback : %s (%d)\n" + , strerror(r) + , r + ); + } + + return r; +} + + int main(void) { @@ -48,10 +70,13 @@ main(void) } /* LIFO: last add is invoked first by uninit() / atexit */ - pantheios_extras_atexit_add(fn1, (void*)1); - pantheios_extras_atexit_add(fn2, (void*)2); - pantheios_extras_atexit_add(fn1, (void*)3); - pantheios_extras_atexit_add(fn2, (void*)4); + if (0 != add(fn1, (void*)1) || + 0 != add(fn2, (void*)2) || + 0 != add(fn1, (void*)3) || + 0 != add(fn2, (void*)4)) + { + return EXIT_FAILURE; + } pantheios_extras_atexit_uninit(); diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 78d5028..cbcd22d 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(test.unit.atexit.api) +add_subdirectory(test.unit.version) # ############################## end of file ############################# # diff --git a/test/unit/test.unit.atexit.api/test.unit.atexit.api.c b/test/unit/test.unit.atexit.api/test.unit.atexit.api.c index e844c5a..d0dc38f 100644 --- a/test/unit/test.unit.atexit.api/test.unit.atexit.api.c +++ b/test/unit/test.unit.atexit.api/test.unit.atexit.api.c @@ -4,7 +4,7 @@ * Purpose: Unit tests for Pantheios.Extras.AtExit. * * Created: 16th August 2026 - * Updated: 16th August 2026 + * Updated: 21st August 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -23,6 +23,8 @@ static int s_tag1 = 1; static int s_tag2 = 2; static int s_tag3 = 3; static int s_tag7 = 7; +static int s_tag_pre = 11; +static int s_tag_post = 13; static void @@ -37,6 +39,19 @@ record(void* param) } +static void +test_add_without_init_then_uninit(void) +{ + s_ncalls = 0; + + XTESTS_TEST_INTEGER_EQUAL(0, pantheios_extras_atexit_add(record, &s_tag_pre)); + + pantheios_extras_atexit_uninit(); + + XTESTS_TEST_INTEGER_EQUAL(1, s_ncalls); + XTESTS_TEST_INTEGER_EQUAL(11, s_calls[0]); +} + static void test_init_succeeds(void) { @@ -98,6 +113,19 @@ test_init_after_uninit_fails(void) XTESTS_TEST_INTEGER_EQUAL(EBUSY, r); } +static void +test_add_after_uninit_then_uninit_again(void) +{ + s_ncalls = 0; + + XTESTS_TEST_INTEGER_EQUAL(0, pantheios_extras_atexit_add(record, &s_tag_post)); + + pantheios_extras_atexit_uninit(); + + XTESTS_TEST_INTEGER_EQUAL(1, s_ncalls); + XTESTS_TEST_INTEGER_EQUAL(13, s_calls[0]); +} + int main(int argc, char* argv[]) @@ -109,14 +137,18 @@ main(int argc, char* argv[]) if (XTESTS_START_RUNNER("test.unit.atexit.api", verbosity)) { - /* Order matters: first case performs the single process init. - * Later cases add/drain the list; process atexit is then a no-op. + /* Order matters: add-without-init must run before the single + * process init. Later cases add/drain the list; process atexit + * is then a no-op unless add-after-uninit leaves nodes (those + * are drained again before exit). */ + XTESTS_RUN_CASE(test_add_without_init_then_uninit); XTESTS_RUN_CASE(test_init_succeeds); XTESTS_RUN_CASE(test_second_init_fails); XTESTS_RUN_CASE(test_uninit_invokes_lifo); XTESTS_RUN_CASE(test_uninit_is_idempotent_for_callbacks); XTESTS_RUN_CASE(test_init_after_uninit_fails); + XTESTS_RUN_CASE(test_add_after_uninit_then_uninit_again); XTESTS_PRINT_RESULTS(); diff --git a/test/unit/test.unit.version/CMakeLists.txt b/test/unit/test.unit.version/CMakeLists.txt new file mode 100644 index 0000000..af5addc --- /dev/null +++ b/test/unit/test.unit.version/CMakeLists.txt @@ -0,0 +1 @@ +define_automated_test_program(test.unit.version test.unit.version.c) diff --git a/test/unit/test.unit.version/test.unit.version.c b/test/unit/test.unit.version/test.unit.version.c new file mode 100644 index 0000000..10ea1a4 --- /dev/null +++ b/test/unit/test.unit.version/test.unit.version.c @@ -0,0 +1,68 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: test.unit.version.c + * + * Purpose: Unit tests for Pantheios.Extras.AtExit version macros. + * + * Created: 21st August 2026 + * Updated: 21st August 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + +#include + +#include + +#include + + +static void +test_version_components(void) +{ + XTESTS_TEST_INTEGER_EQUAL(0, PANTHEIOS_EXTRAS_ATEXIT_VER_MAJOR); + XTESTS_TEST_INTEGER_EQUAL(1, PANTHEIOS_EXTRAS_ATEXIT_VER_MINOR); + XTESTS_TEST_INTEGER_EQUAL(3, PANTHEIOS_EXTRAS_ATEXIT_VER_PATCH); + XTESTS_TEST_INTEGER_EQUAL(0xFF, PANTHEIOS_EXTRAS_ATEXIT_VER_ALPHABETA); + XTESTS_TEST_INTEGER_EQUAL(PANTHEIOS_EXTRAS_ATEXIT_VER_PATCH, PANTHEIOS_EXTRAS_ATEXIT_VER_REVISION); +} + +static void +test_version_composite(void) +{ + int const computed = + (0 + | ( PANTHEIOS_EXTRAS_ATEXIT_VER_MAJOR << 24 ) + | ( PANTHEIOS_EXTRAS_ATEXIT_VER_MINOR << 16 ) + | ( PANTHEIOS_EXTRAS_ATEXIT_VER_PATCH << 8 ) + | ( PANTHEIOS_EXTRAS_ATEXIT_VER_ALPHABETA << 0 ) + ); + + XTESTS_TEST_INTEGER_EQUAL(computed, PANTHEIOS_EXTRAS_ATEXIT_VER); + XTESTS_TEST_INTEGER_EQUAL(PANTHEIOS_EXTRAS_ATEXIT_VER_0_1_3, PANTHEIOS_EXTRAS_ATEXIT_VER); + XTESTS_TEST_INTEGER_EQUAL(0x000103ff, PANTHEIOS_EXTRAS_ATEXIT_VER); +} + + +int +main(int argc, char* argv[]) +{ + int retCode = EXIT_SUCCESS; + int verbosity = 2; + + XTESTS_COMMANDLINE_PARSEVERBOSITY(argc, argv, &verbosity); + + if (XTESTS_START_RUNNER("test.unit.version", verbosity)) + { + XTESTS_RUN_CASE(test_version_components); + XTESTS_RUN_CASE(test_version_composite); + + XTESTS_PRINT_RESULTS(); + + XTESTS_END_RUNNER_UPDATE_EXITCODE(&retCode); + } + + return retCode; +} + + +/* ///////////////////////////// end of file //////////////////////////// */ From 12c80ee37b81e4eadf2b1f4e99a8e1475aff31a6 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Fri, 21 Aug 2026 08:57:24 +1000 Subject: [PATCH 08/11] Explain in the AtExit README why libc atexit is not enough. Describe the missing parameter, scarce shared slots, and lack of unregister, and how one hook plus a LIFO list addresses that. --- README.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5518989..c5560e6 100644 --- a/README.md +++ b/README.md @@ -29,13 +29,26 @@ Standalone C library that registers multiple `atexit`-style callbacks (function ## Introduction -**Pantheios.Extras.AtExit** is a small compiled **C** library in the [Pantheios](http://pantheios.org/) extras namespace. It is **not** a Pantheios (or STLSoft) dependency: the core target needs only the C standard library. +**Pantheios.Extras.AtExit** is a small compiled **C** library in the [Pantheios](http://pantheios.org/) extras namespace. Unlike most/all of the other Pantheios Extras libraries, it is **not** a Pantheios (or STLSoft) dependency: the core target needs only the C standard library. -It extends libc `atexit()` with an explicit callback list so client code can register many `(function, void* param)` pairs that are invoked in **LIFO** order — either when `pantheios_extras_atexit_uninit()` drains the list, or later via the single libc `atexit` hook registered at init. +Its raison d'être is to provide a richer alternative to the standard C library's `atexit()`, with an explicit callback list so client code can register many `(function, void* param)` pairs that are invoked in **LIFO** order — either when `pantheios_extras_atexit_uninit()` drains the list, or later via the single libc `atexit` hook registered at init. Importantly. each callback is also accompanied by a `void*` parameter that is given back to the callback when it is invoked, thereby enabling stateful cleanup. `pantheios_extras_atexit_init()` must be called at most once per process. Later calls fail (`EBUSY`) even after `uninit()`, because libc `atexit` handlers cannot be unregistered. After a drain, the registered hook is a no-op. +### Why at-exit functionality + +C programs often need last-chance cleanup: flushing diagnostics, releasing process-wide resources, or tearing down library state that has no natural owner once `main` has returned. libc `atexit()` is the portable hook for that, but it is a poor *unit of currency* for libraries and layered applications: + +* Handlers are `void (*)(void)`. Any context must live in globals, which couples unrelated components and makes reuse harder; +* The number of handlers is small and shared (`ATEXIT_MAX`, often 32). A library that registers one slot per subsystem, sink, or module can exhaust the table for the rest of the process; +* There is no unregister. A component that is done *before* process exit cannot drop its handler, and a second registration is another scarce slot; + +**Pantheios.Extras.AtExit** exists so that many callers can each register `(function, void* param)` without consuming a libc slot per callback. The library takes **one** `atexit` registration at `init` and maintains its own LIFO list. `uninit` drains that list early when the process is still in a well-defined state; if `uninit` is not used, the same list runs from the libc hook at exit. + +That is the same protocol as other **Pantheios.Extras** helpers: keep the core logging library free of this concern, and give C clients a small, stdlib-only facility instead of rolling an ad-hoc static list in every program. + + ### Dependencies | Component | Implemented in | Use in | Dependencies | From 1de6bba2e5dbd955789013ad7deb2bad19b007ec Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Mon, 31 Aug 2026 21:04:03 +1000 Subject: [PATCH 09/11] squash-commit --- NEWS.md | 2 +- examples/c/example.c.1/example.c.1.c | 10 +++++++++- include/pantheios/extras/atexit/atexit.h | 2 +- src/atexit.api.c | 4 ++-- test/scratch/test.scratch.atexit/main.c | 10 +++++++++- 5 files changed, 22 insertions(+), 6 deletions(-) diff --git a/NEWS.md b/NEWS.md index 5cc0f5a..1311512 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,7 +10,7 @@ | Date | News Item | | ---------------- | -------------------------------------------------------------------------------- | -| 23rd August 2026 | Pantheios.Extras.AtExit 0.1.3 | +| 31st August 2026 | Pantheios.Extras.AtExit 0.1.3 | | 21st August 2026 | Pantheios.Extras.AtExit 0.1.2-alpha1 | | 16th August 2026 | Pantheios.Extras.AtExit 0.1.1 recovered and released | diff --git a/examples/c/example.c.1/example.c.1.c b/examples/c/example.c.1/example.c.1.c index 6adcd2b..4e87cfe 100644 --- a/examples/c/example.c.1/example.c.1.c +++ b/examples/c/example.c.1/example.c.1.c @@ -59,10 +59,18 @@ main(void) if (0 != r) { +#ifdef _MSC_VER + char error_message[256]; + + strerror_s(error_message, sizeof(error_message), r); +#else + char const* error_message = strerror(r); +#endif + fprintf( stderr , "failed to initialise Pantheios.Extras.AtExit : %s (%d)\n" - , strerror(r) + , error_message , r ); diff --git a/include/pantheios/extras/atexit/atexit.h b/include/pantheios/extras/atexit/atexit.h index 696e8f1..ce115ce 100644 --- a/include/pantheios/extras/atexit/atexit.h +++ b/include/pantheios/extras/atexit/atexit.h @@ -4,7 +4,7 @@ * Purpose: Header file for Pantheios.Extras.AtExit. * * Created: 30th December 2011 - * Updated: 23rd August 2026 + * Updated: 31st August 2026 * * Home: http://www.pantheios.org/ * diff --git a/src/atexit.api.c b/src/atexit.api.c index 3337ba5..e4cd1c1 100644 --- a/src/atexit.api.c +++ b/src/atexit.api.c @@ -4,7 +4,7 @@ * Purpose: Implementation file for Pantheios.Extras.AtExit * * Created: 30th December 2011 - * Updated: 21st August 2026 + * Updated: 31st August 2026 * * Home: http://www.pantheios.org/ * @@ -72,7 +72,7 @@ typedef int pantheios_extras_atexit_atomic_int_t; # include typedef atomic_int pantheios_extras_atexit_atomic_int_t; # define PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_INC_(p) (atomic_fetch_add((p), 1) + 1) -# define PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_DEC_(p) (atomic_fetch_sub((p), 1) - 1) +# define PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_DEC_(p) ((void)atomic_fetch_sub((p), 1)) #elif defined(__GNUC__) || defined(__clang__) typedef int pantheios_extras_atexit_atomic_int_t; # define PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_INC_(p) __sync_add_and_fetch((p), 1) diff --git a/test/scratch/test.scratch.atexit/main.c b/test/scratch/test.scratch.atexit/main.c index e554600..e81da3b 100644 --- a/test/scratch/test.scratch.atexit/main.c +++ b/test/scratch/test.scratch.atexit/main.c @@ -30,10 +30,18 @@ main(void) if (0 != r) { +#ifdef _MSC_VER + char error_message[256]; + + strerror_s(error_message, sizeof(error_message), r); +#else + char const* error_message = strerror(r); +#endif + fprintf( stderr , "failed to initialise Pantheios.Extras.AtExit : %s (%d)\n" - , strerror(r) + , error_message , r ); From 0bc70a12841e902bc43d0d256b0452137cc761b8 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Mon, 31 Aug 2026 21:07:18 +1000 Subject: [PATCH 10/11] chore --- src/atexit.api.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/atexit.api.c b/src/atexit.api.c index e4cd1c1..6624e74 100644 --- a/src/atexit.api.c +++ b/src/atexit.api.c @@ -55,28 +55,29 @@ * atomics */ -#if defined(_WIN32) +#if 0 +#elif defined(_WIN32) # include typedef LONG pantheios_extras_atexit_atomic_int_t; -# define PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_INC_(p) InterlockedIncrement((p)) -# define PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_DEC_(p) InterlockedDecrement((p)) +# define PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_INC_(p) InterlockedIncrement((p)) +# define PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_DEC_(p) InterlockedDecrement((p)) #elif defined(__STDC_NO_ATOMICS__) # if defined(__GNUC__) || defined(__clang__) typedef int pantheios_extras_atexit_atomic_int_t; -# define PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_INC_(p) __sync_add_and_fetch((p), 1) -# define PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_DEC_(p) __sync_sub_and_fetch((p), 1) +# define PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_INC_(p) __sync_add_and_fetch((p), 1) +# define PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_DEC_(p) __sync_sub_and_fetch((p), 1) # else # error Pantheios.Extras.AtExit requires C11 atomics, GCC/Clang __sync, or Windows Interlocked # endif #elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) # include typedef atomic_int pantheios_extras_atexit_atomic_int_t; -# define PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_INC_(p) (atomic_fetch_add((p), 1) + 1) -# define PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_DEC_(p) ((void)atomic_fetch_sub((p), 1)) +# define PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_INC_(p) (atomic_fetch_add((p), 1) + 1) +# define PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_DEC_(p) ((void)atomic_fetch_sub((p), 1)) #elif defined(__GNUC__) || defined(__clang__) typedef int pantheios_extras_atexit_atomic_int_t; -# define PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_INC_(p) __sync_add_and_fetch((p), 1) -# define PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_DEC_(p) __sync_sub_and_fetch((p), 1) +# define PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_INC_(p) __sync_add_and_fetch((p), 1) +# define PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_DEC_(p) __sync_sub_and_fetch((p), 1) #else # error Pantheios.Extras.AtExit requires C11 atomics, GCC/Clang __sync, or Windows Interlocked #endif From 423126d6436a8248d8cec6be7fe8696ea7cc2aff Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Mon, 31 Aug 2026 21:11:07 +1000 Subject: [PATCH 11/11] fix --- examples/c/example.c.1/example.c.1.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/examples/c/example.c.1/example.c.1.c b/examples/c/example.c.1/example.c.1.c index 4e87cfe..78d173d 100644 --- a/examples/c/example.c.1/example.c.1.c +++ b/examples/c/example.c.1/example.c.1.c @@ -5,7 +5,7 @@ * callbacks (LIFO) and drain them via uninit(). * * Created: 30th December 2011 - * Updated: 21st August 2026 + * Updated: 31st August 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -40,10 +40,18 @@ add( if (0 != r) { +#ifdef _MSC_VER + char error_message[256]; + + strerror_s(error_message, sizeof(error_message), r); +#else + char const* error_message = strerror(r); +#endif + fprintf( stderr , "failed to add Pantheios.Extras.AtExit callback : %s (%d)\n" - , strerror(r) + , error_message , r ); }