Add platform-specific protections against race conditions when
module files are written rapidly (compilation, copying, etc).
Linux (inotify):
- Deduplicate events within a poll cycle to prevent double-unload
- When duplicate RELOAD events occur, earlier events are skipped
and only the final event is processed
BSD (kqueue):
- Add file readiness check with flock() before processing mtime changes
- Files that aren't ready have their mtime reset, skipping the reload
All Unix platforms:
- Use atomic .tmp + rename for module copying to prevent loading
partially-written files
Writes that occur during an active reload or when files aren't
ready are skipped. A subsequent write is required to trigger
detection of those changes.
Fixes segfault caused by processing duplicate reload events
(Linux) or copying incomplete files mid-write (BSD).
Add strict error handling for stk initialization and platform operations.
stk_init() now fails fast on critical errors and returns typed error codes
that users can handle programmatically.
Changes:
- Add STK_INIT_* error codes for init failures (memory, tmpdir, watch)
- Add STK_PLATFORM_* error codes for platform operation results
- Check platform_mkdir() and fail if temp directory cannot be created
- Check platform_directory_watch_start() and fail if watch cannot initialize
- Add error_cleanup path in platform_directory_watch_start() to properly
free resources on critical Windows failures.
- Replace all platform error checks with STK_PLATFORM_OPERATION_SUCCESS
- Log FATAL errors with when critical operations fail
- Add warning logs for non-critical failures
Critical failures now return specific error codes:
- STK_INIT_MEMORY_ERROR: Module memory allocation failed
- STK_INIT_TMPDIR_ERROR: Cannot create temp directory
- STK_INIT_WATCH_ERROR: Cannot start directory watching
Individual module load failures remain non-fatal and are handled logged.
Modules that fail to load no longer crash or leak memory:
- Check return values from stk_module_load_init() and stk_module_load()
- Log errors with specific failure reasons (library load, symbol lookup, init)
- Track successful_loads counter separately from file_count
- Only increment module_count for modules that actually loaded
- Trim allocated arrays when some modules fail to load
- Continue loading other modules when one fails
This prevents crashes from accessing uninitialized module slots and
avoids memory leaks from over-allocation.
* Types: Corrected stk.c to use stk_init_mod_func and stk_shutdown_mod_func instead of generic types.
* Errors: Replaced -3 placeholder with STK_MOD_INIT_FAILURE.
* Cleanup: Moved typedefs in module.c for consistency.
* Define Constants: Added STK_MOD_LIBRARY_LOAD_ERROR and STK_MOD_SYMBOL_NOT_FOUND_ERROR to the public header.
* Update module.c: Swapped out the -1 and -2 placeholders for the new named constants in stk_module_load.
* Add STK_MOD_INIT_SUCCESS and STK_MOD_INIT_FAILURE macros to stk.h.
* Update stk_module_load to validate module initialization before finalizing the load.
* Unload library if the init func fails, return error.
* Define stk_init_mod_func as int (*)(void) and stk_shutdown_mod_func as void (*)(void).
* Update stk_inits and stk_shutdowns arrays to use the new specific function pointer types.
* Update the symbol lookup union in stk_module_load to support distinct init and shutdown signatures.
* Adjust memory allocation and reallocation logic in stk_module_init_memory and stk_module_realloc_memory to match new type sizes.
- Define STK_PATH_SEP macro to handle Windows and Unix path separators.
- Refactor extract_module_id to use STK_PATH_SEP.
- Simplify stk_module_load by delegating module ID extraction to extract_module_id.
* Resolve STATUS_INVALID_IMAGE_FORMAT on Windows by preventing race conditions during file I/O.
* is_file_ready: Uses GENERIC_WRITE to block if any process (compiler, copy, etc.) is writing to the source. Added GetFileSize check to ensure headers are flushed.
* platform_copy_file: Copies to .tmp and uses MoveFileExA for an atomic swap, hiding the file from the loader until completion.
* Refactor: Unified Win32 and POSIX logic with a single exit point.
Implement complete hot-reload logic in stk_poll() supporting reload,
load, and unload operations. Module arrays dynamically grow/shrink
with automatic defragmentation to maintain tight memory layout.
- Grow arrays when loads exceed unloads
- Fill holes left by unloads with new modules
- Defragment and trim arrays when unloads exceed loads
- Unload before realloc to minimize peak memory usage
- Replace sprintf with safe C89 build_path() helper
* Set all module slot fields (handle, init, shutdown, id) to NULL/empty
after unloading to prevent use-after-free and enable proper hole
detection during array defragmentation.
* Implement stk_module_realloc_memory() to grow or shrink module arrays during hot-reload operations.
* All-or-nothing strategy with fallback to original pointers on partial realloc failure.
Replace the placeholder TODO logs in the polling loop with logic to
resolve filesystem events into specific module indices.
- Centralizing State: Refactored is_module_loaded to is_mod_loaded to
check against the global stk_module_ids array, removing the need to
pass local buffers and preparing for unified lifetime management.
- Standardizing Identity: Updated extract_module_id to use a
consistent output buffer.
- Categorized Event Processing: Implemented a two-pass approach in
stk_poll to count event types (LOAD, UNLOAD, RELOAD) and allocate
tracking arrays, replacing the previous stubbed switch statement.
- Mapping Events to Indices: The poll loop now resolves filenames
back to their specific loaded indices via is_mod_loaded to
identify exactly which mod_id and index require action.
- Improved Flow Control: Introduced a finish_stk_poll label to
ensure consistent cleanup and return values when no events are
detected or processing is complete.
* Introduced stk_initialized flag.
* Updated all configuration setters (stk_set_mod_dir, stk_set_tmp_dir_name, stk_set_module_init_fn, and stk_set_module_shutdown_fn) to silently return if the library is already initialized.
* Ensures internal state consistency by locking paths and function entry points once stk_init has been called.
* Added functions to set mod dir, temp dir name, and module init/shutdown function names.
* Replaced hardcoded string literals with configurable static buffers in module.c.
* Improved string safety by replacing sprintf with strncpy/strncat.
* Updated stk_init local path buffers to handle maximum combined path lengths.
- Change %zu to %lu in stk_init to support older msvcrt.dll (Windows 7/XP)
- Update pluralization logic to correctly handle "0 mods" vs "1 mod"
- Add temporary stk_log calls to stk_poll for monitoring module events
Refactor memory allocation patterns:
- Replace realloc-in-loop with count-then-allocate pattern across all platforms
- Eliminate arbitrary buffer sizes (e.g., malloc(8 * ...)) in favor of exact counts
- Reduce allocation overhead by pre-counting items before malloc
Fix Windows file watching:
- Replace unreliable FindFirstChangeNotification with directory handle approach
- Add is_file_ready() to prevent events while compiler is still writing files
- Preserve timestamps when file is locked to retry on next poll
- Fix do-while loop in platform_directory_init_scan (was skipping first file)
Fix Linux inotify event handling:
- Consolidate DELETE+CREATE pairs into single RELOAD event
- Prevents duplicate events when compiler uses temp-file-and-rename pattern
Fix BSD/macOS kqueue implementation:
- Remove realloc loops from update_watches() and watch initialization
- Pre-count files before allocating file descriptor arrays
All platforms now correctly handle:
- Compiler overwrites (temp file operations)
- Manual copy/move operations
- Explicit file deletions
Tested on Linux, Windows 10, and FreeBSD.
Unlike inotify, Windows and BSD require manual state snapshots to detect
specific file changes. This refactor standardizes that manual handling
to ensure it is resource-safe and easy to follow.
- Fix memory leaks in platform_directory_watch_check where temporary
buffers (new_snaps, file_list) were not reliably freed on 'phantom'
triggers or error paths.
- Unify control flow using a 'goto' cleanup pattern to ensure
deterministic resource deallocation.
- Synchronizes the manual snapshot comparison logic between Windows
and BSD to ensure identical LOAD/RELOAD/UNLOAD event behavior.
- Simplifies the platform_directory_init_scan logic by removing
redundant directory rewinds and nested checks.
- Add WIN32_LEAN_AND_MEAN to optimize Windows header inclusion.
- Align internal API signatures (is_module_loaded) for consistency.
- Include <stdint.h> so Windows does not complain about int types.
- Cast GetProcAddress return through intptr_t to satisfy -Wpedantic.
- Moved work_path for *nix only macros.
- Move extract_module_id and is_valid_module_file to module.c
- Update is_module_loaded to return module index instead of uint8_t
- Fix platform.c event checks to handle index-based return (>= 0)
- Centralize STK_MODULE_EXT definitions in stk.h
Add foundation for cross-platform hot-reload system by isolating
loaded modules from source files using a temporary directory.
Changes:
- Add configurable tmp directory parameter to stk_init()
(defaults to mods/.tmp/ if not specified)
- Copy all modules from mods/ to .tmp/ on initialization
- Load modules exclusively from .tmp/ directory
- Clean up .tmp/ directory on shutdown
- Add cross-platform file operations:
* platform_mkdir() - create directories
* platform_copy_file() - copy files
* platform_remove_file() - delete files
* platform_remove_dir() - delete directory and contents
- Improve BSD kqueue implementation to detect file overwrites
(adds individual file watches with NOTE_WRITE)
This isolates the loaded shared libraries from source files,
preventing segfaults when users overwrite mods using cp/copy
operations. The actual reload logic remains unimplemented
(marked as TODO in stk_poll switch cases).
- Use fixed STK_PATH_MAX and STK_MOD_ID_BUFFER throughout for predictable memory
- Filter by platform-specific extensions (.so/.dll/.dylib) with compile-time length
- Add RELOAD event detection and is_module_loaded() helper
- Maintain feature parity across all platforms
- Split Makefile into gmake.mk (Linux/Windows) and bmake.mk (BSD/macOS)
- Added config.mk for shared variables.
- Added build.sh and build.bat dispatchers.
- Retains old build behavior with dependency tracking.
- Added kqueue directory watching code for other unix like OSes
(FreeBSD, OpenBSD, MacOS, etc)
- Since much of the code for Linux and other *nix OSes was the same,
some refactoring was done to not have duplicate code.
- Use STK_MOD_LOAD/STK_MOD_UNLOAD for Windows events
- Map FILE_ACTION_ADDED/MODIFIED/RENAMED_NEW_NAME to load
- Map FILE_ACTION_REMOVED/RENAMED_OLD_NAME to unload