refactor: purge CHandle from module internals (M14 R5)
Module-internal object references are Rust types now (values, Arc, Mutex); CHandle remains only at the oakengine C-ABI boundary: - oakundo: the global stack holds UndoStack/UndoCommand values directly (stack token is the static's address) - oaktimeline: marker/workarea boxes carry Arc<Mutex<T>>; commands share the same allocation through Arc clones (readers in oakengine stubs and the app's graphops updated to lock) - oaktask/oakstorage: sessions, write-through bindings and the database backend pass ProjectArc; the Session drops its manual release bookkeeping; nodeutil keeps the CHandle<->Arc boundary conversion (release_project restored for the app) - oakcodec: handle.rs deleted outright (no facade entry needed it); texture/block placeholders are unit structs - oakrender: copier's project handle is an identity u64; alive-count machinery removed; handle.rs is make_owned/get/get_mut only - oakplugin: the instance registry is gone (its unregister key never matched, leaking weak entries); handle.rs is the RefBox boundary type - oaknode/oakcommon: only dead guard/borrow helpers removed; external payload handles (texture/processor) documented as the boundary Flake hunts landed along the way: the audio recording test serializes on the shared manager lock with a normalized state; the autocacher cancel test uses a slow producer so cancellation is deterministic.
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
src/
|
||||
lib.rs crate 文档、模块图、FFI 纪律
|
||||
error.rs 错误码(与 include/plugin/error.h 一一对应)
|
||||
handle.rs 引用计数句柄脚手架({ctx,addref,release,abi_version})
|
||||
handle.rs RefBox 边界容器(Host::create_instance 返回值类型)
|
||||
property.rs PropertySet:OFX 属性集的存储与类型化读写
|
||||
suites/ 插件调进来的 C 函数表(unsafe trampoline 层)
|
||||
mod.rs fetchSuite 注册表 + 渲染/GL 上下文 TLS
|
||||
@@ -163,9 +163,11 @@ src/
|
||||
|
||||
## 实现纪律(实现方必读)
|
||||
|
||||
1. 所有 `extern "C"` 函数体必须包 `crate::handle::guard(..)`(
|
||||
catch_unwind + 错误码映射),禁止 panic 越过 FFI。
|
||||
2. 句柄全部经 `handle.rs` 的 `RefBox<T>`;`ctx` 永不裸指针外露含义。
|
||||
1. panic 不得越过插件 FFI:suite 分发表(`suites/mod.rs`)与宿主侧
|
||||
插件调用点各自 `catch_unwind` 兜底并映射错误码。
|
||||
2. 实例以 `Arc<RefBox<Instance>>` 传(`RefBox` 为 facade 边界类型,
|
||||
见 `handle.rs`);跨 FFI 的裸指针只指向堆上稳定对象(props/
|
||||
tag 打标),永不外露其地址含义。
|
||||
3. 共享状态(插件缓存、instance 注册表、线程表)一律 `Mutex`;
|
||||
插件可能在其自起线程回调任意 suite(MultiThread suite 存活期)。
|
||||
4. OFX 语义以 HostSupport 的行为为参照系;每个协商/时序实现点
|
||||
@@ -259,8 +261,8 @@ cargo tarpaulin --out stdout --features test-stubs # 覆盖率门槛
|
||||
|
||||
TDD:测试声明与实现声明同步冻结(tests/,函数体 `todo!()`):
|
||||
|
||||
- `handle_test.rs` / `property_test.rs` — 基础设施契约(引用计数、
|
||||
free 容错、Registry、属性集语义、并发)。
|
||||
- `handle_test.rs` / `property_test.rs` — 基础设施契约(RefBox
|
||||
容器、属性集语义、并发)。
|
||||
- `suites_test.rs` — 八张 suite 的 round-trip(经最小测试插件,
|
||||
"插件视角"的 HostSupport 兼容性背书)。
|
||||
- `ffi_host_test.rs` / `ffi_instance_test.rs` — C ABI 出口契约
|
||||
|
||||
+18
-201
@@ -14,213 +14,30 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 引用计数句柄脚手架。
|
||||
//! 句柄机制的历史遗留:`RefBox<T>` 容器。
|
||||
//!
|
||||
//! 对应 C 侧布局(`include/plugin/instance.h`,与 oak 全项目约定一致):
|
||||
//! 单库化(M14 R5)后 crate 内部不再传 CHandle——原 CHandle 装拆
|
||||
//! (`make_owned`/`make_borrowed`/`get`)、panic 兜底(`guard`/
|
||||
//! `guard_handle`/`guard_void`)与身份注册表(`Registry`)均已删除:
|
||||
//! 前者只被测试使用,后者在 src 无读取方(param 桥实际走
|
||||
//! [`crate::suites::param`] 的 props 地址映射与 [`crate::node`] 的
|
||||
//! 身份注册表)。
|
||||
//!
|
||||
//! ```c
|
||||
//! typedef struct OakPluginInstance {
|
||||
//! void *ctx;
|
||||
//! void (*addref)(void *ctx);
|
||||
//! void (*release)(void *ctx);
|
||||
//! uint32_t abi_version;
|
||||
//! } OakPluginInstance;
|
||||
//! ```
|
||||
//!
|
||||
//! 句柄按值传;`ctx` 指向本 crate 堆上的 [`RefBox<T>`]。`addref`/
|
||||
//! `release` 函数指针永远指向本 crate 的代码(所有权不出 DLL)。
|
||||
//! 仅 [`RefBox`] 保留:它是 [`crate::host::Host::create_instance`] 的
|
||||
//! 返回值容器(`Arc<RefBox<Instance>>`),该类型被 oakengine 的
|
||||
//! test_support 显式标注消费,是本 crate 在 facade 边界的公开类型。
|
||||
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::panic::{catch_unwind, AssertUnwindSafe};
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
use std::sync::atomic::AtomicU32;
|
||||
|
||||
use crate::error::OAKPLUGIN_E_FAILED;
|
||||
|
||||
/// 当前 ABI 版本,写进每个句柄的 `abi_version` 字段。
|
||||
pub const OAKPLUGIN_ABI_VERSION: u32 = 1;
|
||||
|
||||
/// 句柄背后的堆盒子。`owns == false` 的盒子(借用包装)在计数归零时
|
||||
/// 只释放盒子本身,不销毁内含对象。
|
||||
/// 边界盒:`Arc<RefBox<Instance>>` 的承载类型。
|
||||
///
|
||||
/// 原为 C 句柄(`{ctx, addref, release, abi_version}`)背后的堆盒子,
|
||||
/// addref/release thunk 经 `refs` 计数;装拆删除后 `refs` 不再被读写,
|
||||
/// 仅以固定值 1 构造("单个拥有者"语义),生命周期完全由外层
|
||||
/// `Arc` 管理。
|
||||
pub struct RefBox<T: ?Sized> {
|
||||
/// 引用计数(原子;release 可在任意线程发生)。
|
||||
/// 引用计数(句柄时代遗留,现无 thunk 读写)。
|
||||
pub refs: AtomicU32,
|
||||
/// 内含对象。
|
||||
pub value: T,
|
||||
}
|
||||
|
||||
/// C 句柄的 Rust 镜像。`#[repr(C)]`,与 C 头文件布局一致。
|
||||
///
|
||||
/// 生命周期:`*_init`/`*_create` 返回计数 1 的拥有型句柄;
|
||||
/// The shared ABI value-handle type (single-lib unification, see
|
||||
/// `docs/zh/plans/riir/single-lib.md`): one canonical
|
||||
/// `{ctx, addref, release, abi_version}` type in `oakcore-rs`, re-exported
|
||||
/// here so the crate's handle scaffolding stays source-compatible.
|
||||
/// `Send + Sync` come from the shared type.
|
||||
pub use oakcore_rs::handle::CHandle;
|
||||
|
||||
/// addref 的实现:原子 +1。拥有型与借用型共用——借用型只延长盒子
|
||||
/// 的寿命,不延长被借用对象。
|
||||
unsafe extern "C" fn refbox_addref<T: Any + Send>(ctx: *mut std::ffi::c_void) {
|
||||
unsafe {
|
||||
let rb = ctx as *const RefBox<T>;
|
||||
// 调用方保证句柄在借用期内有效(ctx 非空且未被释放)。
|
||||
(*rb).refs.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
/// release 的实现(拥有型):原子 -1,归零时回收盒子并销毁内含对象。
|
||||
unsafe extern "C" fn refbox_release_owned<T: Any + Send>(ctx: *mut std::ffi::c_void) {
|
||||
unsafe {
|
||||
let rb = ctx as *mut RefBox<T>;
|
||||
// AcqRel:归零这一侧要能看见最后一次引用前的全部写(含对象
|
||||
// 析构所需的内部状态)。
|
||||
if (*rb).refs.fetch_sub(1, Ordering::AcqRel) == 1 {
|
||||
drop(Box::from_raw(rb));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// release 的实现(借用型,[`make_borrowed`] 的产物):归零时只回收
|
||||
/// 盒子内存,把内含对象原样忘掉——其所有权仍在借用方手里。
|
||||
unsafe extern "C" fn refbox_release_borrowed<T: Any + Send>(ctx: *mut std::ffi::c_void) {
|
||||
unsafe {
|
||||
let rb = ctx as *mut RefBox<T>;
|
||||
if (*rb).refs.fetch_sub(1, Ordering::AcqRel) == 1 {
|
||||
// 部分 move:把 value 移出临时 Box,Box 析构只释放分配;
|
||||
// value 用 forget 放弃析构(double-free 防线)。
|
||||
std::mem::forget((Box::from_raw(rb)).value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 为 `T` 制作拥有型句柄(计数 1)。分配失败返回空句柄并销毁对象。
|
||||
///
|
||||
/// 注:Rust 默认分配失败(OOM)直接 abort,不会走到"返回空句柄"
|
||||
/// 路径;此处语义保留给未来接入自定义分配器的场景。
|
||||
pub fn make_owned<T: Any + Send>(value: T) -> CHandle {
|
||||
let rb = Box::into_raw(Box::new(RefBox {
|
||||
refs: AtomicU32::new(1),
|
||||
value,
|
||||
}));
|
||||
CHandle {
|
||||
ctx: rb as *mut std::ffi::c_void,
|
||||
addref: Some(refbox_addref::<T>),
|
||||
release: Some(refbox_release_owned::<T>),
|
||||
abi_version: OAKPLUGIN_ABI_VERSION,
|
||||
}
|
||||
}
|
||||
|
||||
/// 为已有对象制作借用句柄(计数归零只释放盒子)。`ptr` 必须在本
|
||||
/// 句柄被释放前保持有效。
|
||||
///
|
||||
/// 语义:按位拷贝("借用拷贝",如纹理句柄的快照);被借用对象
|
||||
/// 的析构完全由调用方负责,盒子从不碰它。拷贝即快照——借出后
|
||||
/// 修改 `*ptr` 不会反映到句柄内。
|
||||
///
|
||||
/// # Safety
|
||||
/// 调用方保证 `ptr` 的生命周期覆盖所有派生句柄,且其值在借用期内
|
||||
/// 不被 move/析构。
|
||||
pub unsafe fn make_borrowed<T: Any + Send>(ptr: *mut T) -> CHandle {
|
||||
if ptr.is_null() {
|
||||
return CHandle::null();
|
||||
}
|
||||
let rb = Box::into_raw(Box::new(RefBox {
|
||||
refs: AtomicU32::new(1),
|
||||
value: unsafe { std::ptr::read(ptr) },
|
||||
}));
|
||||
CHandle {
|
||||
ctx: rb as *mut std::ffi::c_void,
|
||||
addref: Some(refbox_addref::<T>),
|
||||
release: Some(refbox_release_borrowed::<T>),
|
||||
abi_version: OAKPLUGIN_ABI_VERSION,
|
||||
}
|
||||
}
|
||||
|
||||
/// 取回盒子内对象的不可变引用;空句柄返回 `None`。
|
||||
///
|
||||
/// # Safety
|
||||
/// 调用方必须保证 `T` 与创建句柄时的类型一致。
|
||||
pub unsafe fn get<T: Any>(h: &CHandle) -> Option<&T> {
|
||||
if h.is_null() {
|
||||
return None;
|
||||
}
|
||||
unsafe { Some(&(*(h.ctx as *const RefBox<T>)).value) }
|
||||
}
|
||||
|
||||
/// FFI 兜底:捕获 panic,把 `Result<i32>` 映射为对外错误码
|
||||
/// ([`crate::error`])。所有返回 i32 的导出函数必须经它。
|
||||
///
|
||||
/// panic 路径返回 `OAKPLUGIN_E_FAILED`;panic 详情暂不落日志
|
||||
/// (message 桥接入后补 TODO)。
|
||||
pub fn guard<F>(f: F) -> i32
|
||||
where
|
||||
F: FnOnce() -> crate::error::Result<()>,
|
||||
{
|
||||
match catch_unwind(AssertUnwindSafe(f)) {
|
||||
Ok(Ok(())) => crate::error::OAKPLUGIN_OK,
|
||||
Ok(Err(e)) => e.code(),
|
||||
Err(_) => OAKPLUGIN_E_FAILED,
|
||||
}
|
||||
}
|
||||
|
||||
/// 指针/句柄返回值版本的 [`guard`]:panic 或 Err 时返回空句柄
|
||||
/// (指针类返回 NULL)。
|
||||
pub fn guard_handle<F>(f: F) -> CHandle
|
||||
where
|
||||
F: FnOnce() -> crate::error::Result<CHandle>,
|
||||
{
|
||||
match catch_unwind(AssertUnwindSafe(f)) {
|
||||
Ok(Ok(h)) => h,
|
||||
Ok(Err(_)) | Err(_) => CHandle::null(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 无返回值版本:panic 被吞(日志回调待 message 出口接入后补)。
|
||||
pub fn guard_void<F>(f: F)
|
||||
where
|
||||
F: FnOnce(),
|
||||
{
|
||||
let _ = catch_unwind(AssertUnwindSafe(f));
|
||||
}
|
||||
|
||||
/// 句柄身份注册表:`usize` 身份 ↔ 弱引用。供 param↔node 等需要
|
||||
/// "按身份找回对象"的桥使用(替代 M9 C++ 版的
|
||||
/// `oaknode_node_identity()` 注册表)。
|
||||
pub struct Registry<T: Any + Send> {
|
||||
map: Mutex<HashMap<usize, Weak<RefBox<T>>>>,
|
||||
}
|
||||
|
||||
impl<T: Any + Send> Registry<T> {
|
||||
/// 空注册表。
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
map: Mutex::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// 登记对象,返回其身份(地址语义,进程内唯一)。
|
||||
pub fn register(&self, obj: &Arc<RefBox<T>>) -> usize {
|
||||
// Arc 分配地址即身份:同一 RefBox 恒稳定,进程内唯一。
|
||||
let id = Arc::as_ptr(obj) as *const () as usize;
|
||||
lock(&self.map).insert(id, Arc::downgrade(obj));
|
||||
id
|
||||
}
|
||||
|
||||
/// 按身份取对象;对象已销毁或身份未知返回 `None`。
|
||||
pub fn lookup(&self, id: usize) -> Option<Arc<RefBox<T>>> {
|
||||
lock(&self.map).get(&id).and_then(|w| w.upgrade())
|
||||
}
|
||||
|
||||
/// 摘除身份(对象销毁路径调用)。未知身份是 no-op。
|
||||
pub fn unregister(&self, id: usize) {
|
||||
lock(&self.map).remove(&id);
|
||||
}
|
||||
}
|
||||
|
||||
/// 取锁。毒锁(本 crate 代码持锁时 panic)时接管内部状态继续——
|
||||
/// 一次 panic 不级联成后续所有 FFI 调用失败。
|
||||
fn lock<T>(m: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
|
||||
m.lock().unwrap_or_else(|e| e.into_inner())
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex, OnceLock};
|
||||
|
||||
use crate::descriptor::EffectDescriptor;
|
||||
use crate::handle::{RefBox, Registry};
|
||||
use crate::handle::RefBox;
|
||||
use crate::instance::Instance;
|
||||
use crate::property::{PropertySet, Value};
|
||||
use crate::suites::status;
|
||||
@@ -790,14 +790,6 @@ pub struct Host {
|
||||
pub(crate) instances: Mutex<Vec<std::sync::Weak<RefBox<Instance>>>>,
|
||||
}
|
||||
|
||||
/// 实例身份注册表(param 桥按身份反查;见 [`crate::handle::Registry`])。
|
||||
static INSTANCE_REGISTRY: OnceLock<Registry<Instance>> = OnceLock::new();
|
||||
|
||||
/// 实例身份注册表入口。
|
||||
pub(crate) fn instance_registry() -> &'static Registry<Instance> {
|
||||
INSTANCE_REGISTRY.get_or_init(Registry::new)
|
||||
}
|
||||
|
||||
impl Host {
|
||||
/// 进程单例。首次调用构建宿主属性集(能力宣告在此写入)。
|
||||
pub fn global() -> &'static Host {
|
||||
@@ -919,7 +911,7 @@ impl Host {
|
||||
)));
|
||||
}
|
||||
|
||||
// 登记:实例表 + param→instance 回写表。
|
||||
// 登记活跃实例表(泄漏断言用)+ param→instance 回写表。
|
||||
self.instances
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner())
|
||||
@@ -929,7 +921,6 @@ impl Host {
|
||||
let p_addr = &p.props as *const PropertySet as usize;
|
||||
crate::suites::param::register_param_owner(p_addr, inst_props);
|
||||
}
|
||||
instance_registry().register(&arc);
|
||||
|
||||
Ok(arc)
|
||||
}
|
||||
|
||||
@@ -86,8 +86,9 @@ pub struct RenderScale {
|
||||
pub y: f64,
|
||||
}
|
||||
|
||||
/// 插件实例。`Arc<RefBox<Instance>>` 管理生命周期;身份注册见
|
||||
/// [`crate::handle::Registry`](param 桥按身份反查)。
|
||||
/// 插件实例。`Arc<RefBox<Instance>>` 管理生命周期(`RefBox` 为 facade
|
||||
/// 边界类型,见 [`crate::handle`]);param 桥按 props 地址映射反查
|
||||
/// ([`crate::suites::param`]),节点绑定经 [`crate::node`] 身份注册表。
|
||||
///
|
||||
/// `#[repr(C)]` + props 在偏移 0(句柄约定,见 [`crate::suites::tag`];
|
||||
/// 实例期 effect/param-set handle 即 `&props`)。
|
||||
@@ -123,8 +124,9 @@ pub struct Instance {
|
||||
pub render_lock: std::sync::Mutex<()>,
|
||||
}
|
||||
|
||||
/// 实例销毁路径:先通知 destroyInstance action,再摘除身份登记。
|
||||
/// (RefBox 归零时 Drop 触发——action 通知必须在对象析构前发出。)
|
||||
/// 实例销毁路径:先通知 destroyInstance action,再摘除 param 回写登记。
|
||||
/// (`Arc<RefBox<Instance>>` 归零时 Drop 触发——action 通知必须在
|
||||
/// 对象析构前发出。)
|
||||
impl Drop for Instance {
|
||||
fn drop(&mut self) {
|
||||
if !self
|
||||
@@ -134,8 +136,6 @@ impl Drop for Instance {
|
||||
self.notify_destroy();
|
||||
}
|
||||
crate::suites::param::unregister_params_of(&self.props as *const _ as usize);
|
||||
crate::host::instance_registry()
|
||||
.unregister(&self.props as *const crate::property::PropertySet as usize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -954,8 +954,8 @@ impl Instance {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 销毁(destroyInstance action)。析构由 RefBox 驱动;
|
||||
/// 此处只做 action 通知,幂等([`Instance::drop`] 的
|
||||
/// 销毁(destroyInstance action)。析构由 `Arc<RefBox<Instance>>`
|
||||
/// 归零驱动;此处只做 action 通知,幂等([`Instance::drop`] 的
|
||||
/// `destroyed` 门保证只发一次)。
|
||||
pub(crate) fn notify_destroy(&self) {
|
||||
use crate::host::ACTION_DESTROY_INSTANCE;
|
||||
|
||||
@@ -14,22 +14,23 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! handle.rs 的契约测试:引用计数语义、free 容错、借用盒、Registry。
|
||||
//! handle.rs 的契约测试:`RefBox` 容器。
|
||||
//!
|
||||
//! 对应实现:crate::handle。每个测试只验一条规则,命名即规约。
|
||||
//! 单库化(M14 R5)后 crate 内部不再传 CHandle——原 CHandle 装拆
|
||||
//! (`make_owned`/`make_borrowed`/`get`)、panic 兜底(`guard`/
|
||||
//! `guard_handle`/`guard_void`)与身份注册表(`Registry`)均已删除。
|
||||
//! [`oakplugin::handle::RefBox`] 仅作为 `Host::create_instance` 的
|
||||
//! 边界返回类型保留(`Arc<RefBox<Instance>>`,oakengine test_support
|
||||
//! 消费);此处验证其基本契约。每个测试只验一条规则,命名即规约。
|
||||
|
||||
mod common;
|
||||
|
||||
use std::ptr;
|
||||
use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use oakplugin::error::{Error, OAKPLUGIN_E_FAILED, OAKPLUGIN_E_NOT_FOUND, OAKPLUGIN_OK};
|
||||
use oakplugin::handle::{
|
||||
get, guard, guard_handle, make_borrowed, make_owned, CHandle, RefBox, Registry,
|
||||
};
|
||||
use oakplugin::handle::RefBox;
|
||||
|
||||
/// 析构标志:以"被析构次数"断言对象的销毁时机(引用计数语义的
|
||||
/// 析构标志:以"被析构次数"断言对象的销毁时机(Arc 生命周期语义的
|
||||
/// 行为探针)。
|
||||
struct DropFlag(Arc<AtomicUsize>);
|
||||
|
||||
@@ -39,207 +40,32 @@ impl Drop for DropFlag {
|
||||
}
|
||||
}
|
||||
|
||||
/// 模拟 C 侧 addref(头文件契约:复制句柄时先 addref)。
|
||||
fn addref(h: &CHandle) {
|
||||
unsafe { (h.addref.expect("addref fn 缺失"))(h.ctx) };
|
||||
}
|
||||
|
||||
/// 模拟 C 侧 free(`oakplugin_instance_free` 语义:ctx 非空才调
|
||||
/// release,随后清空 ctx;空句柄/已清空句柄是 no-op)。
|
||||
fn free(h: &mut CHandle) {
|
||||
if !h.is_null() {
|
||||
unsafe { (h.release.expect("release fn 缺失"))(h.ctx) };
|
||||
}
|
||||
h.ctx = ptr::null_mut();
|
||||
}
|
||||
|
||||
/// 拥有型句柄:创建计数为 1;addref 后 release 一次对象仍活;
|
||||
/// 再 release 对象销毁(用析构标志位断言)。
|
||||
/// 值可经 `.value` 字段取回(create_instance 返回后调用方的访问方式)。
|
||||
#[test]
|
||||
fn owned_handle_refcount_lifecycle() {
|
||||
let drops = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
// 按值传句柄的位级复制(C 侧 `OakPluginInstance` 结构体拷贝),
|
||||
// 复制方必须先 addref:refs 1 -> 2。
|
||||
let h1 = make_owned(DropFlag(drops.clone()));
|
||||
let mut h2 = unsafe { ptr::read(&h1) };
|
||||
addref(&h2);
|
||||
|
||||
// 释放一份:2 -> 1,对象仍活。
|
||||
let mut h1 = h1;
|
||||
free(&mut h1);
|
||||
assert_eq!(drops.load(Ordering::Relaxed), 0);
|
||||
|
||||
// 释放最后一份:1 -> 0,对象恰好销毁一次。
|
||||
free(&mut h2);
|
||||
assert_eq!(drops.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
/// free(NULL)/free(空句柄)/重复 free 同一个已清空句柄:全部 no-op,
|
||||
/// 不崩、不计数变化(alive 计数前后一致)。
|
||||
#[test]
|
||||
fn free_null_and_empty_is_noop() {
|
||||
let drops = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
// free(空句柄):no-op,不崩。
|
||||
let mut null = CHandle::null();
|
||||
free(&mut null);
|
||||
assert!(null.is_null());
|
||||
|
||||
// 正常销毁后句柄已清空;重复 free 是 no-op,计数不再变化。
|
||||
let mut h = make_owned(DropFlag(drops.clone()));
|
||||
free(&mut h);
|
||||
assert_eq!(drops.load(Ordering::Relaxed), 1);
|
||||
free(&mut h);
|
||||
free(&mut h);
|
||||
assert_eq!(drops.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
/// 借用句柄:release 只释放盒子,被借用的对象仍然存活
|
||||
/// (用外部栈对象的析构标志断言)。
|
||||
#[test]
|
||||
fn borrowed_handle_never_destroys_object() {
|
||||
let drops = Arc::new(AtomicUsize::new(0));
|
||||
let mut obj = DropFlag(drops.clone());
|
||||
|
||||
let mut h = unsafe { make_borrowed(&mut obj as *mut DropFlag) };
|
||||
assert!(!h.is_null());
|
||||
|
||||
free(&mut h);
|
||||
// 盒子释放了,但对象归调用方:析构不在 release 时发生。
|
||||
assert_eq!(drops.load(Ordering::Relaxed), 0);
|
||||
assert!(h.is_null());
|
||||
|
||||
// 对象最终由调用方析构,恰好一次。
|
||||
drop(obj);
|
||||
assert_eq!(drops.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
/// 空句柄的 get::<T>() 返回 None;类型不符的 get 是调用方责任
|
||||
/// (文档约定),此处只验空句柄路径。
|
||||
#[test]
|
||||
fn get_on_empty_handle_is_none() {
|
||||
assert!(unsafe { get::<u32>(&CHandle::null()) }.is_none());
|
||||
|
||||
// 对照:正常句柄能取回引用。
|
||||
let mut h = make_owned(42u32);
|
||||
assert_eq!(unsafe { get::<u32>(&h) }, Some(&42));
|
||||
free(&mut h);
|
||||
}
|
||||
|
||||
/// guard:闭包 panic 被捕获并映射为 OAKPLUGIN_E_FAILED,
|
||||
/// 不 unwind 出 FFI;Err 映射为对应负码;Ok 映射为 OAKPLUGIN_OK。
|
||||
#[test]
|
||||
fn guard_maps_panic_err_ok() {
|
||||
// Ok -> OAKPLUGIN_OK。
|
||||
assert_eq!(guard(|| Ok(())), OAKPLUGIN_OK);
|
||||
|
||||
// Err -> 对应负码(错误码与 include/plugin/error.h 一致,
|
||||
// 项目 -MMCCCC 方案:-90001..-90005)。
|
||||
assert_eq!(guard(|| Err(Error::NotFound)), OAKPLUGIN_E_NOT_FOUND);
|
||||
assert_eq!(
|
||||
guard(|| Err(Error::Invalid)),
|
||||
oakplugin::error::OAKPLUGIN_E_INVALID
|
||||
);
|
||||
assert_eq!(
|
||||
guard(|| Err(Error::State)),
|
||||
oakplugin::error::OAKPLUGIN_E_STATE
|
||||
);
|
||||
assert_eq!(
|
||||
guard(|| Err(Error::Failed("x".into()))),
|
||||
oakplugin::error::OAKPLUGIN_E_FAILED
|
||||
);
|
||||
assert_eq!(
|
||||
guard(|| Err(Error::NoMem)),
|
||||
oakplugin::error::OAKPLUGIN_E_NOMEM
|
||||
);
|
||||
|
||||
// panic -> OAKPLUGIN_E_FAILED,且 panic 不越过 guard 边界
|
||||
// (catch_unwind 语义:本测试线程存活即证明未 unwind)。
|
||||
assert_eq!(guard(|| panic!("boom")), OAKPLUGIN_E_FAILED);
|
||||
}
|
||||
|
||||
/// guard_handle:panic/Err 返回空句柄;Ok 透传非空句柄。
|
||||
#[test]
|
||||
fn guard_handle_maps_to_null_on_failure() {
|
||||
let mut ok = guard_handle(|| Ok(make_owned(7u32)));
|
||||
assert!(!ok.is_null());
|
||||
free(&mut ok);
|
||||
|
||||
let err = guard_handle(|| Err::<CHandle, _>(Error::State));
|
||||
assert!(err.is_null());
|
||||
|
||||
let panicked: CHandle = guard_handle(|| panic!("boom"));
|
||||
assert!(panicked.is_null());
|
||||
}
|
||||
|
||||
/// Registry:register 返回唯一身份;lookup 命中;对象销毁后
|
||||
/// lookup 返回 None(弱引用语义);unregister 未知身份 no-op。
|
||||
#[test]
|
||||
fn registry_register_lookup_unregister() {
|
||||
let reg: Registry<u32> = Registry::new();
|
||||
|
||||
let arc = Arc::new(RefBox {
|
||||
fn refbox_exposes_value_by_field() {
|
||||
let boxed = RefBox {
|
||||
refs: AtomicU32::new(1),
|
||||
value: 7u32,
|
||||
});
|
||||
let id = reg.register(&arc);
|
||||
assert_eq!(id, Arc::as_ptr(&arc) as *const () as usize);
|
||||
assert_eq!(reg.lookup(id).unwrap().value, 7);
|
||||
|
||||
// 同一对象重复登记:身份稳定(地址语义),值覆盖。
|
||||
let id2 = reg.register(&arc);
|
||||
assert_eq!(id2, id);
|
||||
|
||||
// 摘除后 lookup 命中失败;再摘除未知身份是 no-op。
|
||||
reg.unregister(id);
|
||||
assert!(reg.lookup(id).is_none());
|
||||
reg.unregister(id);
|
||||
|
||||
// 对象销毁后弱引用失效:lookup 返回 None。
|
||||
let dying = Arc::new(RefBox {
|
||||
refs: AtomicU32::new(1),
|
||||
value: 9u32,
|
||||
});
|
||||
let dying_id = reg.register(&dying);
|
||||
drop(dying);
|
||||
assert!(reg.lookup(dying_id).is_none());
|
||||
|
||||
drop(arc);
|
||||
};
|
||||
assert_eq!(boxed.value, 7);
|
||||
}
|
||||
|
||||
/// 并发:64 线程对同一句柄 addref/release 各一千次,最终计数正确、
|
||||
/// 对象恰好销毁一次(线程模型是 multithread suite 的直接投影)。
|
||||
/// 生命周期完全由外层 Arc 管理:`refs` 只以固定值 1 构造(无 thunk
|
||||
/// 增减),Arc 归零时内含对象恰好析构一次;弱引用在 Arc 存活期间
|
||||
/// 有效、归零后失效(host 的活跃实例表即 Weak 列表)。
|
||||
#[test]
|
||||
fn refcount_is_thread_safe() {
|
||||
fn refbox_value_drops_exactly_once_when_arc_reaches_zero() {
|
||||
let drops = Arc::new(AtomicUsize::new(0));
|
||||
let h = make_owned(DropFlag(drops.clone()));
|
||||
let boxed = Arc::new(RefBox {
|
||||
refs: AtomicU32::new(1),
|
||||
value: DropFlag(drops.clone()),
|
||||
});
|
||||
|
||||
// 每个线程持有 ctx + 函数指针的拷贝(对应 C 侧各线程各持一份
|
||||
// 句柄值),对同一对象做 1000 次 addref/release 配对。
|
||||
// 裸指针不可 Send,测试侧经 usize 搬运(C 侧本来也是整数传递)。
|
||||
let threads: Vec<_> = (0..64)
|
||||
.map(|_| {
|
||||
let ctx = h.ctx as usize;
|
||||
let addref = h.addref.expect("addref fn 缺失");
|
||||
let release = h.release.expect("release fn 缺失");
|
||||
std::thread::spawn(move || {
|
||||
for _ in 0..1000 {
|
||||
unsafe { addref(ctx as *mut std::ffi::c_void) };
|
||||
unsafe { release(ctx as *mut std::ffi::c_void) };
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
for t in threads {
|
||||
t.join().unwrap();
|
||||
}
|
||||
|
||||
// 全部配对完成:计数回到创建值,对象存活。
|
||||
let weak = Arc::downgrade(&boxed);
|
||||
assert!(weak.upgrade().is_some());
|
||||
assert_eq!(drops.load(Ordering::Relaxed), 0);
|
||||
|
||||
// 最终一次 release 恰好销毁。
|
||||
let mut h = h;
|
||||
free(&mut h);
|
||||
drop(boxed);
|
||||
assert_eq!(drops.load(Ordering::Relaxed), 1);
|
||||
assert!(weak.upgrade().is_none());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user