4 Commits

Author SHA1 Message Date
anth64 96fb957991 chore: bump version to 0.0.3 2026-02-09 23:03:35 +01:00
anth64 0cbee45ad2 fix(platform): replace strncpy with memmove to silence -Wrestrict warning 2026-02-09 22:53:42 +01:00
anth64 142a61a843 chore: bump version to 0.0.2 2026-02-09 22:32:55 +01:00
anth64 f83f2051ef fix(linux): prevent segfaults during rapid module reloads
Fixes multiple issues causing segfaults when hot-reloading modules
on Linux, particularly when file changes are detected rapidly:

- Enable is_file_ready() check on Linux to prevent loading partially-written
  shared libraries (previously only used on Windows/BSD)
- Fix event deduplication on Linux to actually remove duplicate inotify events
  instead of just marking them, preventing double-free on same module
- Reorder reload operations to unload old module only after successfully
  copying new version, avoiding invalid state when copy fails

Changes:
- platform.c: Remove __linux__ guards around is_file_ready() function
- platform.c: Add compaction step after deduplication to remove -1 entries
- stk.c: Move module unload to after platform_copy_file() in reload loop

These changes make Linux hot-reload as robust as Windows/BSD implementations.
2026-02-09 22:25:02 +01:00
5 changed files with 138 additions and 107 deletions
+22 -1
View File
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [0.0.3] - 2026-02-10
### Fixed
- **Compilation**: Fixed GCC `-Wrestrict` warning in Linux directory watching code by replacing `strncpy` with `memmove` for overlapping memory operations
- Ensures clean compilation without warnings while maintaining identical runtime behavior
- Uses semantically correct function for moving memory within the same buffer
## [0.0.2] - 2026-02-09
### Fixed
- **Linux**: Fixed segfaults during rapid module reloads when file changes are detected in quick succession
- Enabled file readiness checks on Linux (previously only used on Windows/BSD) to prevent loading partially-written shared libraries
- Fixed inotify event deduplication to actually remove duplicate events instead of just marking them
- Reordered reload operations to only unload old module after successfully copying new version, preventing invalid state when copy fails
- **All platforms**: Improved reload safety by deferring module unload until after successful file copy
### Changed
- Made `is_file_ready()` check available on all Unix platforms (was previously excluded on Linux)
## [0.0.1] - 2026-02-01 ## [0.0.1] - 2026-02-01
### Added ### Added
@@ -25,5 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Dependency management and versioning not yet implemented - Dependency management and versioning not yet implemented
- API is unstable and subject to change in future releases - API is unstable and subject to change in future releases
[Unreleased]: https://github.com/anth64/stk/compare/v0.0.1...HEAD [Unreleased]: https://github.com/anth64/stk/compare/v0.0.3...HEAD
[0.0.3]: https://github.com/anth64/stk/releases/tag/v0.0.3
[0.0.2]: https://github.com/anth64/stk/releases/tag/v0.0.2
[0.0.1]: https://github.com/anth64/stk/releases/tag/v0.0.1 [0.0.1]: https://github.com/anth64/stk/releases/tag/v0.0.1
+3 -2
View File
@@ -173,14 +173,15 @@ stk_init();
## Project Status ## Project Status
**Current Version:** 0.0.1 (Pre-release) **Current Version:** 0.0.3 (Pre-release)
This is an early release proving the core hot-reload foundation. Phase 1 is still in progress. This is an early bugfix release improving compilation warnings on Linux. Phase 1 is still in progress.
### What Works ### What Works
- Cross-platform module loading and hot-reloading - Cross-platform module loading and hot-reloading
- File watching (inotify/kqueue/FindFirstFile) - File watching (inotify/kqueue/FindFirstFile)
- Basic error handling - Basic error handling
- Stable hot-reload even during rapid file changes
### In Progress (Phase 1) ### In Progress (Phase 1)
- Complete logging system (log levels, verbosity, output configuration) - Complete logging system (log levels, verbosity, output configuration)
+1 -1
View File
@@ -3,7 +3,7 @@
#define STK_VERSION_MAJOR 0 #define STK_VERSION_MAJOR 0
#define STK_VERSION_MINOR 0 #define STK_VERSION_MINOR 0
#define STK_VERSION_PATCH 1 #define STK_VERSION_PATCH 3
#define STK_STRINGIFY_HELPER(x) #x #define STK_STRINGIFY_HELPER(x) #x
#define STK_STRINGIFY(x) STK_STRINGIFY_HELPER(x) #define STK_STRINGIFY(x) STK_STRINGIFY_HELPER(x)
+26 -16
View File
@@ -32,7 +32,6 @@ int is_mod_loaded(const char *module_name);
uint8_t is_valid_module_file(const char *filename); uint8_t is_valid_module_file(const char *filename);
void extract_module_id(const char *path, char *out_id); void extract_module_id(const char *path, char *out_id);
#ifndef __linux__
static uint8_t is_file_ready(const char *dir_path, const char *filename) static uint8_t is_file_ready(const char *dir_path, const char *filename)
{ {
char full_path[STK_PATH_MAX_OS]; char full_path[STK_PATH_MAX_OS];
@@ -80,6 +79,7 @@ static uint8_t is_file_ready(const char *dir_path, const char *filename)
#endif #endif
} }
#ifndef __linux__
typedef struct { typedef struct {
char filename[STK_PATH_MAX]; char filename[STK_PATH_MAX];
#ifdef _WIN32 #ifdef _WIN32
@@ -146,7 +146,6 @@ uint8_t platform_copy_file(const char *from, const char *to)
FILE *src = NULL, *dst = NULL; FILE *src = NULL, *dst = NULL;
char tmp_path[STK_PATH_MAX_OS]; char tmp_path[STK_PATH_MAX_OS];
size_t n; size_t n;
#ifndef __linux__
char dir_path[STK_PATH_MAX_OS]; char dir_path[STK_PATH_MAX_OS];
const char *filename; const char *filename;
@@ -163,7 +162,6 @@ uint8_t platform_copy_file(const char *from, const char *to)
if (!is_file_ready(dir_path, filename)) if (!is_file_ready(dir_path, filename))
goto done; goto done;
#endif
sprintf(tmp_path, "%s.tmp", to); sprintf(tmp_path, "%s.tmp", to);
@@ -286,11 +284,10 @@ void *platform_get_symbol(void *h, const char *s)
#endif #endif
} }
char (*platform_directory_init_scan(const char *dir_path, char (*platform_directory_init_scan(const char *dir_path, size_t *out_count))
size_t *out_count))[STK_PATH_MAX] [STK_PATH_MAX] {
{
size_t count = 0, i = 0, name_len; size_t count = 0, i = 0, name_len;
char(*list)[STK_PATH_MAX] = NULL; char (*list)[STK_PATH_MAX] = NULL;
#ifdef _WIN32 #ifdef _WIN32
WIN32_FIND_DATAA fd; WIN32_FIND_DATAA fd;
HANDLE h; HANDLE h;
@@ -338,9 +335,9 @@ char (*platform_directory_init_scan(const char *dir_path,
FindClose(h); FindClose(h);
goto exit; goto exit;
create_and_exit: create_and_exit:
platform_mkdir(dir_path); platform_mkdir(dir_path);
exit: exit:
*out_count = i; *out_count = i;
return list; return list;
#else #else
@@ -353,7 +350,7 @@ exit:
if (!d) if (!d)
goto create_and_exit; goto create_and_exit;
count_loop: count_loop:
e = readdir(d); e = readdir(d);
if (!e) if (!e)
goto count_done; goto count_done;
@@ -368,7 +365,7 @@ count_loop:
count++; count++;
goto count_loop; goto count_loop;
count_done: count_done:
if (count == 0) if (count == 0)
goto close_and_exit; goto close_and_exit;
@@ -377,7 +374,7 @@ count_done:
if (!list) if (!list)
goto close_and_exit; goto close_and_exit;
fill_loop: fill_loop:
e = readdir(d); e = readdir(d);
if (!e || i >= count) if (!e || i >= count)
goto close_and_exit; goto close_and_exit;
@@ -397,17 +394,17 @@ fill_loop:
list[i - 1][name_len] = '\0'; list[i - 1][name_len] = '\0';
goto fill_loop; goto fill_loop;
create_and_exit: create_and_exit:
platform_mkdir(dir_path); platform_mkdir(dir_path);
*out_count = 0; *out_count = 0;
return NULL; return NULL;
close_and_exit: close_and_exit:
closedir(d); closedir(d);
*out_count = i; *out_count = i;
return list; return list;
#endif #endif
} }
#if !defined(__linux__) && !defined(_WIN32) #if !defined(__linux__) && !defined(_WIN32)
static void update_watches(platform_watch_context_t *ctx) static void update_watches(platform_watch_context_t *ctx)
@@ -665,7 +662,7 @@ stk_module_event_t *platform_directory_watch_check(
int fd = (int)(long)handle; int fd = (int)(long)handle;
char buf[STK_EVENT_BUFFER]; char buf[STK_EVENT_BUFFER];
ssize_t len; ssize_t len;
size_t index = 0, count = 0, i; size_t index = 0, count = 0, i, write_index;
stk_module_event_t *evs; stk_module_event_t *evs;
char *ptr, *end; char *ptr, *end;
struct inotify_event *e; struct inotify_event *e;
@@ -763,6 +760,19 @@ stk_module_event_t *platform_directory_watch_check(
} }
} }
write_index = 0;
for (i = 0; i < index; ++i) {
if (evs[i] != -1) {
if (write_index != i) {
evs[write_index] = evs[i];
memmove((*file_list)[write_index],
(*file_list)[i], STK_PATH_MAX);
}
write_index++;
}
}
index = write_index;
*out_count = index; *out_count = index;
return evs; return evs;
+2 -3
View File
@@ -242,9 +242,6 @@ begin_operations:
for (i = 0; i < unload_count; ++i) for (i = 0; i < unload_count; ++i)
stk_module_unload(unloaded_mod_indices[i]); stk_module_unload(unloaded_mod_indices[i]);
for (i = 0; i < reload_count; ++i)
stk_module_unload(reloaded_mod_indices[i]);
for (i = 0; i < reload_count; ++i) { for (i = 0; i < reload_count; ++i) {
int file_index = reloaded_mod_file_indices[i]; int file_index = reloaded_mod_file_indices[i];
int mod_index = reloaded_mod_indices[i]; int mod_index = reloaded_mod_indices[i];
@@ -261,6 +258,8 @@ begin_operations:
continue; continue;
} }
stk_module_unload(mod_index);
load_result = stk_module_load(tmp_path, mod_index); load_result = stk_module_load(tmp_path, mod_index);
if (load_result != STK_MOD_INIT_SUCCESS) { if (load_result != STK_MOD_INIT_SUCCESS) {
stk_log(stderr, "Failed to reload module %s: %s", stk_log(stderr, "Failed to reload module %s: %s",