Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 182 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -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,
}
8 changes: 8 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
# Pantheios.Extras.AtExit - Changes <!-- omit in toc -->


## 0.1.3 - 31st 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`;
* 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

* 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**;
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

| Date | News Item |
| ---------------- | -------------------------------------------------------------------------------- |
| 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 |

Expand Down
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Standalone C library that registers multiple `atexit`-style callbacks (function
## Table of Contents <!-- omit in toc -->

- [Introduction](#introduction)
- [Why at-exit functionality](#why-at-exit-functionality)
- [Dependencies](#dependencies)
- [Installation](#installation)
- [Components](#components)
Expand All @@ -28,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 |
Expand Down
6 changes: 3 additions & 3 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@
### 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;


### 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;
* [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
Expand Down
43 changes: 38 additions & 5 deletions examples/c/example.c.1/example.c.1.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* callbacks (LIFO) and drain them via uninit().
*
* Created: 30th December 2011
* Updated: 16th August 2026
* Updated: 31st August 2026
*
* ////////////////////////////////////////////////////////////////////// */

Expand All @@ -30,6 +30,36 @@ fn2(void* param)
}


static int
add(
void (*pfn)(void* param)
, void* param
)
{
int const r = pantheios_extras_atexit_add(pfn, param);

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"
, error_message
, r
);
}

return r;
}


int
main(void)
{
Expand All @@ -56,10 +86,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();

Expand Down
14 changes: 9 additions & 5 deletions include/pantheios/extras/atexit/atexit.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Purpose: Header file for Pantheios.Extras.AtExit.
*
* Created: 30th December 2011
* Updated: 21st August 2026
* Updated: 31st August 2026
*
* Home: http://www.pantheios.org/
*
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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\
Expand Down Expand Up @@ -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(
Expand Down
Loading
Loading