Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fb0d8adb8f | |||
| 2c4d27f915 | |||
| 96fb957991 | |||
| 0cbee45ad2 | |||
| 142a61a843 | |||
| f83f2051ef |
+31
-1
@@ -7,6 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.0.4] - 2026-02-11
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Linux**: Fixed segfault from invalid module indices during extremely rapid file changes
|
||||||
|
- Added validation check to skip stale UNLOAD/RELOAD events for already-unloaded modules
|
||||||
|
- Prevents is_mod_loaded() returning -1 from being used as array index (SIZE_MAX)
|
||||||
|
- Fixed event count mismatch where loops would run more iterations than valid indices populated
|
||||||
|
- Completes the Linux hot-reload stability fixes from v0.0.2
|
||||||
|
|
||||||
|
## [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 +53,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
|
||||||
|
|||||||
@@ -173,14 +173,15 @@ stk_init();
|
|||||||
|
|
||||||
## Project Status
|
## Project Status
|
||||||
|
|
||||||
**Current Version:** 0.0.1 (Pre-release)
|
**Current Version:** 0.0.4 (Pre-release)
|
||||||
|
|
||||||
This is an early release proving the core hot-reload foundation. Phase 1 is still in progress.
|
This is a bugfix release completing the Linux hot-reload stability improvements. 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
|
||||||
|
- Robust hot-reload even during extremely rapid file changes (Linux fixes in 0.0.2-0.0.4)
|
||||||
|
|
||||||
### In Progress (Phase 1)
|
### In Progress (Phase 1)
|
||||||
- Complete logging system (log levels, verbosity, output configuration)
|
- Complete logging system (log levels, verbosity, output configuration)
|
||||||
|
|||||||
@@ -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 4
|
||||||
|
|
||||||
#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
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -172,8 +172,7 @@ size_t stk_poll(void)
|
|||||||
char (*file_list)[STK_PATH_MAX] = NULL;
|
char (*file_list)[STK_PATH_MAX] = NULL;
|
||||||
stk_module_event_t *events = NULL;
|
stk_module_event_t *events = NULL;
|
||||||
size_t i, file_count = 0, reload_count = 0, load_count = 0,
|
size_t i, file_count = 0, reload_count = 0, load_count = 0,
|
||||||
unload_count = 0, reload_index = 0, load_index = 0,
|
unload_count = 0;
|
||||||
unload_index = 0;
|
|
||||||
int *reloaded_mod_indices = NULL, *reloaded_mod_file_indices = NULL,
|
int *reloaded_mod_indices = NULL, *reloaded_mod_file_indices = NULL,
|
||||||
*unloaded_mod_indices = NULL, *loaded_mod_indices = NULL;
|
*unloaded_mod_indices = NULL, *loaded_mod_indices = NULL;
|
||||||
size_t remaining_loads, new_capacity, holes_to_fill;
|
size_t remaining_loads, new_capacity, holes_to_fill;
|
||||||
@@ -208,20 +207,31 @@ size_t stk_poll(void)
|
|||||||
unloaded_mod_indices = malloc(unload_count * sizeof(int));
|
unloaded_mod_indices = malloc(unload_count * sizeof(int));
|
||||||
loaded_mod_indices = malloc(load_count * sizeof(int));
|
loaded_mod_indices = malloc(load_count * sizeof(int));
|
||||||
|
|
||||||
|
reload_count = 0;
|
||||||
|
unload_count = 0;
|
||||||
|
load_count = 0;
|
||||||
|
|
||||||
for (i = 0; i < file_count; ++i) {
|
for (i = 0; i < file_count; ++i) {
|
||||||
|
int mod_index;
|
||||||
extract_module_id(file_list[i], mod_id);
|
extract_module_id(file_list[i], mod_id);
|
||||||
switch (events[i]) {
|
switch (events[i]) {
|
||||||
case STK_MOD_LOAD:
|
case STK_MOD_LOAD:
|
||||||
loaded_mod_indices[load_index++] = i;
|
loaded_mod_indices[load_count++] = i;
|
||||||
break;
|
break;
|
||||||
case STK_MOD_RELOAD:
|
case STK_MOD_RELOAD:
|
||||||
reloaded_mod_file_indices[reload_index] = i;
|
mod_index = is_mod_loaded(mod_id);
|
||||||
reloaded_mod_indices[reload_index++] =
|
if (mod_index >= 0) {
|
||||||
is_mod_loaded(mod_id);
|
reloaded_mod_file_indices[reload_count] = i;
|
||||||
|
reloaded_mod_indices[reload_count] = mod_index;
|
||||||
|
reload_count++;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case STK_MOD_UNLOAD:
|
case STK_MOD_UNLOAD:
|
||||||
unloaded_mod_indices[unload_index++] =
|
mod_index = is_mod_loaded(mod_id);
|
||||||
is_mod_loaded(mod_id);
|
if (mod_index >= 0) {
|
||||||
|
unloaded_mod_indices[unload_count] = mod_index;
|
||||||
|
unload_count++;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -242,9 +252,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 +268,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",
|
||||||
|
|||||||
Reference in New Issue
Block a user