diff --git a/crates/owlry-core/src/plugins/runtime_loader.rs b/crates/owlry-core/src/plugins/runtime_loader.rs index 606d443..4206742 100644 --- a/crates/owlry-core/src/plugins/runtime_loader.rs +++ b/crates/owlry-core/src/plugins/runtime_loader.rs @@ -10,6 +10,7 @@ //! Note: This module is infrastructure for the runtime architecture. Full integration //! is pending Phase 5 (AUR Packaging) when runtime packages will be available. +use std::mem::ManuallyDrop; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -69,8 +70,11 @@ pub struct ScriptRuntimeVTable { pub struct LoadedRuntime { /// Runtime name (for logging) name: &'static str, - /// Keep library alive - _library: Arc, + /// Keep library alive — wrapped in ManuallyDrop so we never call dlclose(). + /// dlclose() unmaps the library code; any thread-local destructors inside the + /// library then SIGSEGV when they try to run against the unmapped addresses. + /// Runtime libraries live for the process lifetime, so leaking the handle is safe. + _library: ManuallyDrop>, /// Runtime vtable vtable: &'static ScriptRuntimeVTable, /// Runtime handle (state) @@ -138,7 +142,7 @@ impl LoadedRuntime { Ok(Self { name, - _library: library, + _library: ManuallyDrop::new(library), vtable, handle, providers, @@ -166,6 +170,8 @@ impl LoadedRuntime { impl Drop for LoadedRuntime { fn drop(&mut self) { (self.vtable.drop)(self.handle); + // Do NOT drop _library: ManuallyDrop ensures dlclose() is never called. + // See field comment for rationale. } } diff --git a/crates/owlry-lua/src/loader.rs b/crates/owlry-lua/src/loader.rs index 00778ae..edee503 100644 --- a/crates/owlry-lua/src/loader.rs +++ b/crates/owlry-lua/src/loader.rs @@ -186,6 +186,10 @@ pub fn discover_plugins( match PluginManifest::load(&manifest_path) { Ok(manifest) => { + // Skip plugins whose entry point is not a Lua file + if !manifest.plugin.entry.ends_with(".lua") { + continue; + } let id = manifest.plugin.id.clone(); if plugins.contains_key(&id) { eprintln!(