Skip to main content

linkrs_query/executor/expression/functions/udf/
dynamic.rs

1//! Owned handle for a plugin instance living in a dynamic library.
2//!
3//! The instance is created through `udf_create` and must be destroyed through
4//! `udf_destroy` before the library handle is released. `DynamicPlugin` keeps
5//! an `Arc<Library>` alive for exactly that long.
6
7use super::error::UdfError;
8use super::plugin::{check_arity, UdfDestroyFn, UdfPlugin};
9use crate::executor::expression::{ExpressionError, ExpressionErrorType};
10use libloading::Library;
11use linkrs_core::Value;
12use std::panic::{catch_unwind, AssertUnwindSafe};
13use std::sync::Arc;
14
15/// Owned plugin instance backed by a dynamic library.
16pub struct DynamicPlugin {
17    raw: *mut dyn UdfPlugin,
18    destroy: UdfDestroyFn,
19    /// Kept alive so the vtable behind `raw` stays valid.
20    _lib: Arc<Library>,
21    /// Library path used in error messages.
22    path: String,
23}
24
25// The plugin trait requires Send + Sync; the wrapper upholds the same bound
26// because execution only crosses the boundary through `&self`.
27unsafe impl Send for DynamicPlugin {}
28unsafe impl Sync for DynamicPlugin {}
29
30impl DynamicPlugin {
31    /// Take ownership of a raw plugin pointer created by `udf_create`.
32    ///
33    /// # Safety
34    /// `raw` must be a non-null pointer returned by the matching `udf_create`
35    /// of the same library version, and `destroy` must be the matching
36    /// `udf_destroy` symbol from that library.
37    pub unsafe fn from_raw(
38        raw: *mut dyn UdfPlugin,
39        destroy: UdfDestroyFn,
40        lib: Arc<Library>,
41        path: String,
42    ) -> Result<Self, UdfError> {
43        if raw.is_null() {
44            return Err(UdfError::NullPlugin(path));
45        }
46        Ok(Self {
47            raw,
48            destroy,
49            _lib: lib,
50            path,
51        })
52    }
53
54    fn inner(&self) -> &dyn UdfPlugin {
55        unsafe { &*self.raw }
56    }
57}
58
59impl Drop for DynamicPlugin {
60    fn drop(&mut self) {
61        unsafe {
62            (self.destroy)(self.raw);
63        }
64    }
65}
66
67impl std::fmt::Debug for DynamicPlugin {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        f.debug_struct("DynamicPlugin")
70            .field("name", &self.inner().name())
71            .field("path", &self.path)
72            .finish()
73    }
74}
75
76impl UdfPlugin for DynamicPlugin {
77    fn name(&self) -> &str {
78        self.inner().name()
79    }
80
81    fn description(&self) -> &str {
82        self.inner().description()
83    }
84
85    fn min_arity(&self) -> usize {
86        self.inner().min_arity()
87    }
88
89    fn max_arity(&self) -> usize {
90        self.inner().max_arity()
91    }
92
93    fn is_pure(&self) -> bool {
94        self.inner().is_pure()
95    }
96
97    fn execute(&self, args: &[Value]) -> Result<Value, ExpressionError> {
98        check_arity(self.inner(), args)?;
99        let name = self.inner().name().to_string();
100        match catch_unwind(AssertUnwindSafe(|| self.inner().execute(args))) {
101            Ok(result) => result,
102            Err(payload) => {
103                let detail = payload
104                    .downcast_ref::<String>()
105                    .cloned()
106                    .or_else(|| payload.downcast_ref::<&str>().map(|s| s.to_string()))
107                    .unwrap_or_else(|| "unknown panic".to_string());
108                Err(ExpressionError::new(
109                    ExpressionErrorType::FunctionExecutionError,
110                    UdfError::ExecutionPanic(name, detail).to_string(),
111                ))
112            }
113        }
114    }
115}
116
117/// Execute an in-process plugin with the same isolation guarantees.
118///
119/// Used by `CustomFunctionImpl::Dynamic` so both Library-backed and
120/// in-process plugins share one execution path.
121pub fn execute_isolated(plugin: &dyn UdfPlugin, args: &[Value]) -> Result<Value, ExpressionError> {
122    check_arity(plugin, args)?;
123    let name = plugin.name().to_string();
124    match catch_unwind(AssertUnwindSafe(|| plugin.execute(args))) {
125        Ok(result) => result,
126        Err(payload) => {
127            let detail = payload
128                .downcast_ref::<String>()
129                .cloned()
130                .or_else(|| payload.downcast_ref::<&str>().map(|s| s.to_string()))
131                .unwrap_or_else(|| "unknown panic".to_string());
132            Err(ExpressionError::new(
133                ExpressionErrorType::FunctionExecutionError,
134                UdfError::ExecutionPanic(name, detail).to_string(),
135            ))
136        }
137    }
138}