feat: Implement plugin singleton pattern with shutdown support
- Add singleton support for plugin boxes (e.g., CounterBox) - Implement shutdown_plugins_v2() for controlled plugin lifecycle - Plugin instances now shared across multiple new() calls - Shutdown properly releases and allows re-initialization - All singleton E2E tests passing ✅ ChatGPT5による高度なプラグインライフサイクル管理実装 - シングルトンパターンでプラグインインスタンス共有 - 明示的なshutdownでリソース解放と再初期化対応 - Nyashの統一ライフサイクルポリシー維持 Note: ast.rs test failures are due to rapid development pace - tests need updating for new BoxDeclaration fields (private_fields, public_fields) 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -564,7 +564,7 @@ impl VM {
|
||||
if let Some(plugin) = box_nyash.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
|
||||
let loader = crate::runtime::get_global_loader_v2();
|
||||
let loader = loader.read().map_err(|_| VMError::InvalidInstruction("Plugin loader lock poisoned".into()))?;
|
||||
match loader.invoke_instance_method(&plugin.box_type, method, plugin.instance_id, &arg_values) {
|
||||
match loader.invoke_instance_method(&plugin.box_type, method, plugin.instance_id(), &arg_values) {
|
||||
Ok(Some(result_box)) => {
|
||||
if let Some(dst_id) = dst {
|
||||
self.set_value(*dst_id, VMValue::from_nyash_box(result_box));
|
||||
|
||||
@ -59,7 +59,13 @@ impl ArrayBox {
|
||||
let idx = idx_box.value as usize;
|
||||
let items = self.items.read().unwrap();
|
||||
match items.get(idx) {
|
||||
Some(item) => item.clone_box(),
|
||||
Some(item) => {
|
||||
#[cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
||||
if item.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>().is_some() {
|
||||
return item.share_box();
|
||||
}
|
||||
item.clone_box()
|
||||
}
|
||||
None => Box::new(crate::boxes::null_box::NullBox::new()),
|
||||
}
|
||||
} else {
|
||||
@ -228,7 +234,13 @@ impl ArrayBox {
|
||||
// Create slice
|
||||
let slice_items: Vec<Box<dyn NyashBox>> = items[start_idx..end_idx]
|
||||
.iter()
|
||||
.map(|item| item.clone_box())
|
||||
.map(|item| {
|
||||
#[cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
||||
if item.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>().is_some() {
|
||||
return item.share_box();
|
||||
}
|
||||
item.clone_box()
|
||||
})
|
||||
.collect();
|
||||
|
||||
Box::new(ArrayBox::new_with_elements(slice_items))
|
||||
@ -241,7 +253,13 @@ impl Clone for ArrayBox {
|
||||
// ディープコピー(独立インスタンス)
|
||||
let items_guard = self.items.read().unwrap();
|
||||
let cloned_items: Vec<Box<dyn NyashBox>> = items_guard.iter()
|
||||
.map(|item| item.clone_box()) // 要素もディープコピー
|
||||
.map(|item| {
|
||||
#[cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
||||
if item.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>().is_some() {
|
||||
return item.share_box();
|
||||
}
|
||||
item.clone_box()
|
||||
}) // 要素もディープコピー(ハンドルは共有)
|
||||
.collect();
|
||||
|
||||
ArrayBox {
|
||||
|
||||
@ -135,7 +135,13 @@ impl MapBox {
|
||||
pub fn get(&self, key: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
let key_str = key.to_string_box().value;
|
||||
match self.data.read().unwrap().get(&key_str) {
|
||||
Some(value) => value.clone_box(),
|
||||
Some(value) => {
|
||||
#[cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
||||
if value.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>().is_some() {
|
||||
return value.share_box();
|
||||
}
|
||||
value.clone_box()
|
||||
}
|
||||
None => Box::new(StringBox::new(&format!("Key not found: {}", key_str))),
|
||||
}
|
||||
}
|
||||
@ -310,4 +316,4 @@ impl Debug for MapBox {
|
||||
.field("keys", &data.keys().collect::<Vec<_>>())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,6 +47,10 @@ pub struct BoxTypeConfig {
|
||||
|
||||
/// Method definitions
|
||||
pub methods: HashMap<String, MethodDefinition>,
|
||||
|
||||
/// Singleton service flag (keep one shared instance alive in loader)
|
||||
#[serde(default)]
|
||||
pub singleton: bool,
|
||||
}
|
||||
|
||||
/// Method definition (simplified - no argument info needed)
|
||||
|
||||
@ -575,7 +575,14 @@ impl NyashInterpreter {
|
||||
/// local変数スタックを保存・復元(関数呼び出し時)
|
||||
pub(super) fn save_local_vars(&self) -> HashMap<String, Box<dyn NyashBox>> {
|
||||
self.local_vars.iter()
|
||||
.map(|(k, v)| (k.clone(), (**v).clone_box())) // Deref Arc to get the Box
|
||||
.map(|(k, v)| {
|
||||
let b: &dyn NyashBox = &**v;
|
||||
#[cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
||||
if b.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>().is_some() {
|
||||
return (k.clone(), b.share_box());
|
||||
}
|
||||
(k.clone(), b.clone_box())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@ -593,12 +600,7 @@ impl NyashInterpreter {
|
||||
let _ = instance.fini();
|
||||
eprintln!("🔄 Scope exit: Called fini() on local variable '{}' (InstanceBox)", name);
|
||||
}
|
||||
// プラグインBoxの場合
|
||||
#[cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
||||
if let Some(plugin) = (**value).as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
|
||||
plugin.call_fini();
|
||||
eprintln!("🔄 Scope exit: Called fini() on local variable '{}' (PluginBox)", name);
|
||||
}
|
||||
// プラグインBoxは共有ハンドルの可能性が高いため自動finiしない(明示呼び出しのみ)
|
||||
// ビルトインBoxは元々finiメソッドを持たないので呼ばない
|
||||
// (StringBox、IntegerBox等はリソース管理不要)
|
||||
}
|
||||
@ -612,7 +614,14 @@ impl NyashInterpreter {
|
||||
/// outbox変数スタックを保存・復元(static関数呼び出し時)
|
||||
pub(super) fn save_outbox_vars(&self) -> HashMap<String, Box<dyn NyashBox>> {
|
||||
self.outbox_vars.iter()
|
||||
.map(|(k, v)| (k.clone(), (**v).clone_box())) // Deref Arc to get the Box
|
||||
.map(|(k, v)| {
|
||||
let b: &dyn NyashBox = &**v;
|
||||
#[cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
||||
if b.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>().is_some() {
|
||||
return (k.clone(), b.share_box());
|
||||
}
|
||||
(k.clone(), b.clone_box())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@ -624,12 +633,7 @@ impl NyashInterpreter {
|
||||
let _ = instance.fini();
|
||||
eprintln!("🔄 Scope exit: Called fini() on outbox variable '{}' (InstanceBox)", name);
|
||||
}
|
||||
// プラグインBoxの場合
|
||||
#[cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
||||
if let Some(plugin) = (**value).as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
|
||||
plugin.call_fini();
|
||||
eprintln!("🔄 Scope exit: Called fini() on outbox variable '{}' (PluginBox)", name);
|
||||
}
|
||||
// プラグインBoxは共有ハンドルの可能性が高いため自動finiしない
|
||||
// ビルトインBoxは元々finiメソッドを持たないので呼ばない(要修正)
|
||||
}
|
||||
|
||||
|
||||
@ -692,7 +692,7 @@ impl NyashInterpreter {
|
||||
}
|
||||
let loader = crate::runtime::get_global_loader_v2();
|
||||
let loader = loader.read().unwrap();
|
||||
match loader.invoke_instance_method(&plugin.box_type, method, plugin.instance_id, &arg_values) {
|
||||
match loader.invoke_instance_method(&plugin.box_type, method, plugin.instance_id(), &arg_values) {
|
||||
Ok(Some(result_box)) => return Ok(result_box),
|
||||
Ok(None) => return Ok(Box::new(VoidBox::new())),
|
||||
Err(_) => {}
|
||||
@ -782,7 +782,7 @@ impl NyashInterpreter {
|
||||
if let Some(plugin) = plugin_ref.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
|
||||
let mut arg_values: Vec<Box<dyn NyashBox>> = Vec::new();
|
||||
for arg in arguments { arg_values.push(self.execute_expression(arg)?); }
|
||||
match loader.invoke_instance_method(&plugin.box_type, method, plugin.instance_id, &arg_values) {
|
||||
match loader.invoke_instance_method(&plugin.box_type, method, plugin.instance_id(), &arg_values) {
|
||||
Ok(Some(result_box)) => return Ok(result_box),
|
||||
Ok(None) => return Ok(Box::new(crate::box_trait::VoidBox::new())),
|
||||
Err(e) => {
|
||||
@ -818,23 +818,23 @@ impl NyashInterpreter {
|
||||
{
|
||||
// 親がユーザー定義に見つからない場合は、プラグインとして試行
|
||||
// 現在のインスタンスから __plugin_content を参照
|
||||
if let Some(plugin_shared) = current_instance.get_field_legacy("__plugin_content") {
|
||||
// 引数を評価(ロックは既に解放済みの設計)
|
||||
let plugin_ref = &*plugin_shared;
|
||||
if let Some(plugin) = plugin_ref.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
|
||||
let mut arg_values: Vec<Box<dyn NyashBox>> = Vec::new();
|
||||
for arg in arguments {
|
||||
arg_values.push(self.execute_expression(arg)?);
|
||||
if let Some(plugin_shared) = current_instance.get_field_legacy("__plugin_content") {
|
||||
// 引数を評価(ロックは既に解放済みの設計)
|
||||
let plugin_ref = &*plugin_shared;
|
||||
if let Some(plugin) = plugin_ref.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
|
||||
let mut arg_values: Vec<Box<dyn NyashBox>> = Vec::new();
|
||||
for arg in arguments {
|
||||
arg_values.push(self.execute_expression(arg)?);
|
||||
}
|
||||
let loader = crate::runtime::get_global_loader_v2();
|
||||
let loader = loader.read().unwrap();
|
||||
match loader.invoke_instance_method(&plugin.box_type, method, plugin.instance_id(), &arg_values) {
|
||||
Ok(Some(result_box)) => return Ok(result_box),
|
||||
Ok(None) => return Ok(Box::new(VoidBox::new())),
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
let loader = crate::runtime::get_global_loader_v2();
|
||||
let loader = loader.read().unwrap();
|
||||
match loader.invoke_instance_method(&plugin.box_type, method, plugin.instance_id, &arg_values) {
|
||||
Ok(Some(result_box)) => return Ok(result_box),
|
||||
Ok(None) => return Ok(Box::new(VoidBox::new())),
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 親クラスのBox宣言を取得(ユーザー定義Boxの場合)
|
||||
@ -1003,7 +1003,7 @@ impl NyashInterpreter {
|
||||
}
|
||||
let loader_guard = crate::runtime::plugin_loader_v2::get_global_loader_v2();
|
||||
let loader = loader_guard.read().map_err(|_| RuntimeError::RuntimeFailure { message: "Plugin loader lock poisoned".into() })?;
|
||||
match loader.invoke_instance_method(&plugin_box.box_type, method, plugin_box.instance_id, &arg_values) {
|
||||
match loader.invoke_instance_method(&plugin_box.box_type, method, plugin_box.instance_id(), &arg_values) {
|
||||
Ok(Some(result_box)) => Ok(result_box),
|
||||
Ok(None) => Ok(Box::new(VoidBox::new())),
|
||||
Err(e) => Err(RuntimeError::RuntimeFailure { message: format!("Plugin method {} failed: {:?}", method, e) }),
|
||||
|
||||
@ -34,18 +34,71 @@ mod enabled {
|
||||
|
||||
/// v2 Plugin Box wrapper - temporary implementation
|
||||
#[derive(Debug)]
|
||||
pub struct PluginBoxV2 {
|
||||
pub box_type: String,
|
||||
pub struct PluginHandleInner {
|
||||
pub type_id: u32,
|
||||
pub invoke_fn: unsafe extern "C" fn(u32, u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32,
|
||||
pub instance_id: u32,
|
||||
/// Optional fini method_id from nyash.toml (None if not provided)
|
||||
pub fini_method_id: Option<u32>,
|
||||
finalized: std::sync::atomic::AtomicBool,
|
||||
}
|
||||
|
||||
impl Drop for PluginHandleInner {
|
||||
fn drop(&mut self) {
|
||||
// Finalize exactly once when the last shared handle is dropped
|
||||
if let Some(fini_id) = self.fini_method_id {
|
||||
if !self.finalized.swap(true, std::sync::atomic::Ordering::SeqCst) {
|
||||
let tlv_args: [u8; 4] = [1, 0, 0, 0];
|
||||
let mut out: [u8; 4] = [0; 4];
|
||||
let mut out_len: usize = out.len();
|
||||
unsafe {
|
||||
(self.invoke_fn)(
|
||||
self.type_id,
|
||||
fini_id,
|
||||
self.instance_id,
|
||||
tlv_args.as_ptr(),
|
||||
tlv_args.len(),
|
||||
out.as_mut_ptr(),
|
||||
&mut out_len,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PluginHandleInner {
|
||||
/// Explicitly finalize this handle now (idempotent)
|
||||
pub fn finalize_now(&self) {
|
||||
if let Some(fini_id) = self.fini_method_id {
|
||||
if !self.finalized.swap(true, std::sync::atomic::Ordering::SeqCst) {
|
||||
let tlv_args: [u8; 4] = [1, 0, 0, 0];
|
||||
let mut out: [u8; 4] = [0; 4];
|
||||
let mut out_len: usize = out.len();
|
||||
unsafe {
|
||||
(self.invoke_fn)(
|
||||
self.type_id,
|
||||
fini_id,
|
||||
self.instance_id,
|
||||
tlv_args.as_ptr(),
|
||||
tlv_args.len(),
|
||||
out.as_mut_ptr(),
|
||||
&mut out_len,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PluginBoxV2 {
|
||||
pub box_type: String,
|
||||
pub inner: std::sync::Arc<PluginHandleInner>,
|
||||
}
|
||||
|
||||
impl BoxCore for PluginBoxV2 {
|
||||
fn box_id(&self) -> u64 {
|
||||
self.instance_id as u64
|
||||
self.inner.instance_id as u64
|
||||
}
|
||||
|
||||
fn parent_type_id(&self) -> Option<std::any::TypeId> {
|
||||
@ -53,7 +106,7 @@ mod enabled {
|
||||
}
|
||||
|
||||
fn fmt_box(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "{}({})", self.box_type, self.instance_id)
|
||||
write!(f, "{}({})", self.box_type, self.inner.instance_id)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
@ -75,7 +128,7 @@ mod enabled {
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
eprintln!("🔍 DEBUG: PluginBoxV2::clone_box called for {} (id={})", self.box_type, self.instance_id);
|
||||
eprintln!("🔍 DEBUG: PluginBoxV2::clone_box called for {} (id={})", self.box_type, self.inner.instance_id);
|
||||
|
||||
// Clone means creating a new instance by calling birth()
|
||||
let mut output_buffer = vec![0u8; 1024];
|
||||
@ -83,8 +136,8 @@ mod enabled {
|
||||
let tlv_args = vec![1u8, 0, 0, 0]; // version=1, argc=0
|
||||
|
||||
let result = unsafe {
|
||||
(self.invoke_fn)(
|
||||
self.type_id,
|
||||
(self.inner.invoke_fn)(
|
||||
self.inner.type_id,
|
||||
0, // method_id=0 (birth)
|
||||
0, // instance_id=0 (static call)
|
||||
tlv_args.as_ptr(),
|
||||
@ -103,13 +156,16 @@ mod enabled {
|
||||
|
||||
eprintln!("🎉 clone_box success: created new {} instance_id={}", self.box_type, new_instance_id);
|
||||
|
||||
// Return new PluginBoxV2 with new instance_id
|
||||
// Return new PluginBoxV2 with new instance_id (separate inner handle)
|
||||
Box::new(PluginBoxV2 {
|
||||
box_type: self.box_type.clone(),
|
||||
type_id: self.type_id,
|
||||
invoke_fn: self.invoke_fn,
|
||||
instance_id: new_instance_id,
|
||||
fini_method_id: self.fini_method_id,
|
||||
inner: std::sync::Arc::new(PluginHandleInner {
|
||||
type_id: self.inner.type_id,
|
||||
invoke_fn: self.inner.invoke_fn,
|
||||
instance_id: new_instance_id,
|
||||
fini_method_id: self.inner.fini_method_id,
|
||||
finalized: std::sync::atomic::AtomicBool::new(false),
|
||||
}),
|
||||
})
|
||||
} else {
|
||||
eprintln!("❌ clone_box failed: birth() returned error code {}", result);
|
||||
@ -119,7 +175,7 @@ mod enabled {
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> crate::box_trait::StringBox {
|
||||
StringBox::new(format!("{}({})", self.box_type, self.instance_id))
|
||||
StringBox::new(format!("{}({})", self.box_type, self.inner.instance_id))
|
||||
}
|
||||
|
||||
fn equals(&self, _other: &dyn NyashBox) -> crate::box_trait::BoolBox {
|
||||
@ -127,43 +183,19 @@ mod enabled {
|
||||
}
|
||||
|
||||
fn share_box(&self) -> Box<dyn NyashBox> {
|
||||
eprintln!("🔍 DEBUG: PluginBoxV2::share_box called for {} (id={})", self.box_type, self.instance_id);
|
||||
eprintln!("🔍 DEBUG: PluginBoxV2::share_box called for {} (id={})", self.box_type, self.inner.instance_id);
|
||||
|
||||
// Share means returning a new Box with the same instance_id
|
||||
Box::new(PluginBoxV2 {
|
||||
box_type: self.box_type.clone(),
|
||||
type_id: self.type_id,
|
||||
invoke_fn: self.invoke_fn,
|
||||
instance_id: self.instance_id, // Same instance_id - this is sharing!
|
||||
fini_method_id: self.fini_method_id,
|
||||
inner: self.inner.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PluginBoxV2 {
|
||||
/// Call fini() on this plugin instance if configured
|
||||
pub fn call_fini(&self) {
|
||||
if let Some(fini_id) = self.fini_method_id {
|
||||
// Empty TLV args
|
||||
let tlv_args: [u8; 4] = [1, 0, 0, 0];
|
||||
let mut out: [u8; 4] = [0; 4];
|
||||
let mut out_len: usize = out.len();
|
||||
let rc = unsafe {
|
||||
(self.invoke_fn)(
|
||||
self.type_id,
|
||||
fini_id,
|
||||
self.instance_id,
|
||||
tlv_args.as_ptr(),
|
||||
tlv_args.len(),
|
||||
out.as_mut_ptr(),
|
||||
&mut out_len,
|
||||
)
|
||||
};
|
||||
if rc != 0 {
|
||||
eprintln!("⚠️ PluginBoxV2::fini failed for {} id={} rc={}", self.box_type, self.instance_id, rc);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn instance_id(&self) -> u32 { self.inner.instance_id }
|
||||
pub fn finalize_now(&self) { self.inner.finalize_now() }
|
||||
}
|
||||
|
||||
/// Plugin loader v2
|
||||
@ -175,6 +207,9 @@ impl PluginBoxV2 {
|
||||
pub config: Option<NyashConfigV2>,
|
||||
/// Path to the loaded nyash.toml (absolute), used for consistent re-reads
|
||||
config_path: Option<String>,
|
||||
|
||||
/// Singleton instances: (lib_name, box_type) -> shared handle
|
||||
singletons: RwLock<HashMap<(String,String), std::sync::Arc<PluginHandleInner>>>,
|
||||
}
|
||||
|
||||
impl PluginLoaderV2 {
|
||||
@ -196,6 +231,7 @@ impl PluginBoxV2 {
|
||||
plugins: RwLock::new(HashMap::new()),
|
||||
config: None,
|
||||
config_path: None,
|
||||
singletons: RwLock::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -225,10 +261,59 @@ impl PluginBoxV2 {
|
||||
eprintln!("Warning: Failed to load plugin {}: {:?}", lib_name, e);
|
||||
}
|
||||
}
|
||||
// Pre-birth singletons configured in nyash.toml
|
||||
let cfg_path = self.config_path.as_ref().map(|s| s.as_str()).unwrap_or("nyash.toml");
|
||||
let toml_content = std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?;
|
||||
let toml_value: toml::Value = toml::from_str(&toml_content).map_err(|_| BidError::PluginError)?;
|
||||
for (lib_name, lib_def) in &config.libraries {
|
||||
for box_name in &lib_def.boxes {
|
||||
if let Some(bc) = config.get_box_config(lib_name, box_name, &toml_value) {
|
||||
if bc.singleton {
|
||||
let _ = self.ensure_singleton_handle(lib_name, box_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ensure a singleton handle is created and stored
|
||||
fn ensure_singleton_handle(&self, lib_name: &str, box_type: &str) -> BidResult<()> {
|
||||
// Fast path: already present
|
||||
if self.singletons.read().unwrap().contains_key(&(lib_name.to_string(), box_type.to_string())) {
|
||||
return Ok(());
|
||||
}
|
||||
// Create via birth
|
||||
let cfg_path = self.config_path.as_ref().map(|s| s.as_str()).unwrap_or("nyash.toml");
|
||||
let toml_content = std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?;
|
||||
let toml_value: toml::Value = toml::from_str(&toml_content).map_err(|_| BidError::PluginError)?;
|
||||
let config = self.config.as_ref().ok_or(BidError::PluginError)?;
|
||||
let plugins = self.plugins.read().unwrap();
|
||||
let plugin = plugins.get(lib_name).ok_or(BidError::PluginError)?;
|
||||
let box_conf = config.get_box_config(lib_name, box_type, &toml_value).ok_or(BidError::InvalidType)?;
|
||||
let type_id = box_conf.type_id;
|
||||
// Call birth
|
||||
let mut output_buffer = vec![0u8; 1024];
|
||||
let mut output_len = output_buffer.len();
|
||||
let tlv_args = vec![1u8, 0, 0, 0];
|
||||
let birth_result = unsafe {
|
||||
(plugin.invoke_fn)(type_id, 0, 0, tlv_args.as_ptr(), tlv_args.len(), output_buffer.as_mut_ptr(), &mut output_len)
|
||||
};
|
||||
if birth_result != 0 || output_len < 4 { return Err(BidError::PluginError); }
|
||||
let instance_id = u32::from_le_bytes([output_buffer[0], output_buffer[1], output_buffer[2], output_buffer[3]]);
|
||||
let fini_id = box_conf.methods.get("fini").map(|m| m.method_id);
|
||||
let handle = std::sync::Arc::new(PluginHandleInner {
|
||||
type_id,
|
||||
invoke_fn: plugin.invoke_fn,
|
||||
instance_id,
|
||||
fini_method_id: fini_id,
|
||||
finalized: std::sync::atomic::AtomicBool::new(false),
|
||||
});
|
||||
self.singletons.write().unwrap().insert((lib_name.to_string(), box_type.to_string()), handle);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Perform an external host call (env.* namespace) or return an error if unsupported
|
||||
/// Returns Some(Box) for a value result, or None for void-like calls
|
||||
pub fn extern_call(
|
||||
@ -344,12 +429,12 @@ impl PluginBoxV2 {
|
||||
|
||||
// Plugin Handle (BoxRef): tag=8, size=8
|
||||
if let Some(p) = a.as_any().downcast_ref::<PluginBoxV2>() {
|
||||
eprintln!("[PluginLoaderV2] arg[{}]: PluginBoxV2({}, id={}) -> Handle(tag=8)", idx, p.box_type, p.instance_id);
|
||||
eprintln!("[PluginLoaderV2] arg[{}]: PluginBoxV2({}, id={}) -> Handle(tag=8)", idx, p.box_type, p.inner.instance_id);
|
||||
buf.push(8u8); // tag
|
||||
buf.push(0u8); // reserved
|
||||
buf.extend_from_slice(&(8u16).to_le_bytes());
|
||||
buf.extend_from_slice(&p.type_id.to_le_bytes());
|
||||
buf.extend_from_slice(&p.instance_id.to_le_bytes());
|
||||
buf.extend_from_slice(&p.inner.type_id.to_le_bytes());
|
||||
buf.extend_from_slice(&p.inner.instance_id.to_le_bytes());
|
||||
continue;
|
||||
}
|
||||
// Integer: prefer i32
|
||||
@ -436,10 +521,13 @@ impl PluginBoxV2 {
|
||||
let fini_id = ret_conf.methods.get("fini").map(|m| m.method_id);
|
||||
let pbox = PluginBoxV2 {
|
||||
box_type: ret_box.to_string(),
|
||||
type_id: r_type,
|
||||
invoke_fn: ret_plugin.invoke_fn,
|
||||
instance_id: r_inst,
|
||||
fini_method_id: fini_id,
|
||||
inner: std::sync::Arc::new(PluginHandleInner {
|
||||
type_id: r_type,
|
||||
invoke_fn: ret_plugin.invoke_fn,
|
||||
instance_id: r_inst,
|
||||
fini_method_id: fini_id,
|
||||
finalized: std::sync::atomic::AtomicBool::new(false),
|
||||
}),
|
||||
};
|
||||
return Ok(Some(Box::new(pbox) as Box<dyn NyashBox>));
|
||||
}
|
||||
@ -521,7 +609,7 @@ impl PluginBoxV2 {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
/// Create a Box instance
|
||||
pub fn create_box(&self, box_type: &str, _args: &[Box<dyn NyashBox>]) -> BidResult<Box<dyn NyashBox>> {
|
||||
eprintln!("🔍 create_box called for: {}", box_type);
|
||||
@ -538,6 +626,23 @@ impl PluginBoxV2 {
|
||||
BidError::InvalidType
|
||||
})?;
|
||||
|
||||
// If singleton, return the pre-birthed shared handle
|
||||
let cfg_path = self.config_path.as_ref().map(|s| s.as_str()).unwrap_or("nyash.toml");
|
||||
if let Ok(toml_content) = std::fs::read_to_string(cfg_path) {
|
||||
if let Ok(toml_value) = toml::from_str::<toml::Value>(&toml_content) {
|
||||
if let Some(bc) = config.get_box_config(lib_name, box_type, &toml_value) {
|
||||
if bc.singleton {
|
||||
// ensure created
|
||||
let _ = self.ensure_singleton_handle(lib_name, box_type);
|
||||
if let Some(inner) = self.singletons.read().unwrap().get(&(lib_name.to_string(), box_type.to_string())) {
|
||||
let plugin_box = PluginBoxV2 { box_type: box_type.to_string(), inner: inner.clone() };
|
||||
return Ok(Box::new(plugin_box));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eprintln!("🔍 Found library: {} for box type: {}", lib_name, box_type);
|
||||
|
||||
// Get loaded plugin
|
||||
@ -552,7 +657,6 @@ impl PluginBoxV2 {
|
||||
|
||||
// Get type_id from config - read actual nyash.toml content
|
||||
eprintln!("🔍 Reading nyash.toml for type configuration...");
|
||||
let cfg_path = self.config_path.as_ref().map(|s| s.as_str()).unwrap_or("nyash.toml");
|
||||
let (type_id, fini_method_id) = if let Ok(toml_content) = std::fs::read_to_string(cfg_path) {
|
||||
eprintln!("🔍 nyash.toml read successfully");
|
||||
if let Ok(toml_value) = toml::from_str::<toml::Value>(&toml_content) {
|
||||
@ -616,14 +720,25 @@ impl PluginBoxV2 {
|
||||
// Create v2 plugin box wrapper with actual instance_id
|
||||
let plugin_box = PluginBoxV2 {
|
||||
box_type: box_type.to_string(),
|
||||
type_id,
|
||||
invoke_fn: plugin.invoke_fn,
|
||||
instance_id,
|
||||
fini_method_id,
|
||||
inner: std::sync::Arc::new(PluginHandleInner {
|
||||
type_id,
|
||||
invoke_fn: plugin.invoke_fn,
|
||||
instance_id,
|
||||
fini_method_id,
|
||||
finalized: std::sync::atomic::AtomicBool::new(false),
|
||||
}),
|
||||
};
|
||||
|
||||
Ok(Box::new(plugin_box))
|
||||
}
|
||||
|
||||
/// Shutdown singletons: finalize and clear all singleton handles
|
||||
pub fn shutdown_singletons(&self) {
|
||||
let mut map = self.singletons.write().unwrap();
|
||||
for (_, handle) in map.drain() {
|
||||
handle.finalize_now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Global loader instance
|
||||
@ -647,6 +762,14 @@ impl PluginBoxV2 {
|
||||
let loader = loader.read().unwrap();
|
||||
loader.load_all_plugins()
|
||||
}
|
||||
|
||||
/// Gracefully shutdown plugins (finalize singletons)
|
||||
pub fn shutdown_plugins_v2() -> BidResult<()> {
|
||||
let loader = get_global_loader_v2();
|
||||
let loader = loader.read().unwrap();
|
||||
loader.shutdown_singletons();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(not(feature = "plugins"), target_arch = "wasm32"))]
|
||||
@ -696,6 +819,7 @@ mod stub {
|
||||
|
||||
pub fn get_global_loader_v2() -> Arc<RwLock<PluginLoaderV2>> { GLOBAL_LOADER_V2.clone() }
|
||||
pub fn init_global_loader_v2(_config_path: &str) -> BidResult<()> { Ok(()) }
|
||||
pub fn shutdown_plugins_v2() -> BidResult<()> { Ok(()) }
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
||||
|
||||
@ -39,10 +39,9 @@ impl ScopeTracker {
|
||||
let _ = instance.fini();
|
||||
continue;
|
||||
}
|
||||
// PluginBox: call plugin fini
|
||||
// PluginBoxV2: do not auto-finalize (shared handle may be referenced elsewhere)
|
||||
#[cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
||||
if let Some(plugin) = arc_box.as_any().downcast_ref::<PluginBoxV2>() {
|
||||
plugin.call_fini();
|
||||
if arc_box.as_any().downcast_ref::<PluginBoxV2>().is_some() {
|
||||
continue;
|
||||
}
|
||||
// Builtin and others: no-op for now
|
||||
|
||||
Reference in New Issue
Block a user