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
15 changes: 15 additions & 0 deletions core/src/plc_app/include/iec_python.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

#include <stddef.h>
#include <sys/types.h>
#include <unistd.h>

#ifdef __cplusplus
extern "C"
Expand Down Expand Up @@ -80,6 +81,20 @@ extern "C"
void python_loader_set_loggers(void (*log_info_func)(const char *, ...),
void (*log_error_func)(const char *, ...));

/**
* @brief Cleanup all Python function blocks
*
* This function must be called before unloading libplc.so to:
* 1. Terminate all Python subprocesses (SIGTERM, then SIGKILL)
* 2. Wait for all runner threads to exit
* 3. Unmap and unlink shared memory regions
* 4. Remove Python script files
*
* Failure to call this before dlclose() will cause a crash because
* the runner threads' code lives in the shared library.
*/
void python_blocks_cleanup(void);

#ifdef __cplusplus
}
#endif
Expand Down
12 changes: 12 additions & 0 deletions core/src/plc_app/plc_state_manager.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "image_tables.h"
#include "journal_buffer.h"
#include "plc_state_manager.h"
#include "plcapp_manager.h"
#include "scan_cycle_manager.h"
#include "utils/log.h"
#include "utils/utils.h"
Expand Down Expand Up @@ -206,6 +207,17 @@ int unload_plc_program(PluginManager *pm)
image_tables_clear_null_pointers();
plugin_mutex_give(&plugin_driver->buffer_mutex);

// Cleanup Python function blocks BEFORE unloading the shared library
// This terminates Python subprocesses and joins runner threads to prevent
// crash when dlclose() unmaps the code while threads are still running
void (*python_cleanup)(void);
*(void **)(&python_cleanup) =
plugin_manager_get_symbol(pm, "python_blocks_cleanup");
Comment on lines +214 to +215

Copilot AI Jan 28, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The double cast *(void **)(&python_cleanup) is used to suppress compiler warnings about converting between function and object pointers, but this is a POSIX extension not guaranteed by C standard. A more portable pattern would be to use a union to perform the type-punning, though this may be acceptable for this codebase if targeting POSIX systems exclusively.

Suggested change
*(void **)(&python_cleanup) =
plugin_manager_get_symbol(pm, "python_blocks_cleanup");
union
{
void *obj;
void (*func)(void);
} sym;
sym.obj = plugin_manager_get_symbol(pm, "python_blocks_cleanup");
python_cleanup = sym.func;

Copilot uses AI. Check for mistakes.
if (python_cleanup)
{
python_cleanup();
}

// Destroy the plugin manager
plugin_manager_destroy(pm);
plc_program = NULL;
Expand Down
Loading