-
Notifications
You must be signed in to change notification settings - Fork 181
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Define a trait for Plugins and allow them to be dynamically loaded or statically linked #114
Changes from 12 commits
5024c12
706c9bb
e7fac54
0797c75
800a15f
b37a751
3a3af7f
64b2c50
52f6b4f
35bcbd4
dabf107
a5c4814
864214f
eff8b0e
87f01a8
9ce6f83
99ab689
e3743de
4855978
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# | ||
# Copyright (c) 2017, 2020 ADLINK Technology Inc. | ||
# | ||
# This program and the accompanying materials are made available under the | ||
# terms of the Eclipse Public License 2.0 which is available at | ||
# http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 | ||
# which is available at https://www.apache.org/licenses/LICENSE-2.0. | ||
# | ||
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 | ||
# | ||
# Contributors: | ||
# ADLINK zenoh team, <zenoh@adlink-labs.tech> | ||
# | ||
[package] | ||
name = "zenoh-plugin-trait" | ||
version = "0.1.0-dev" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Version should be the same as zenoh (0.5.0-dev) since we will release all together. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, although I've removed |
||
authors = [ | ||
"kydos <angelo@icorsaro.net>", | ||
"Julien Enoch <julien@enoch.fr>", | ||
"Olivier Hécart <olivier.hecart@adlinktech.com>", | ||
"Luca Cominardi <luca.cominardi@adlinktech.com>", | ||
"Pierre Avital <pierre.avital@adlinktech.com>", | ||
] | ||
edition = "2018" | ||
|
||
[lib] | ||
name = "zenoh_plugin_trait" | ||
|
||
[features] | ||
no_mangle = [] | ||
default = ["no_mangle"] | ||
|
||
[dependencies] | ||
zenoh = { path="../../zenoh" } | ||
clap = "2" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
// | ||
// Copyright (c) 2017, 2020 ADLINK Technology Inc. | ||
// | ||
// This program and the accompanying materials are made available under the | ||
// terms of the Eclipse Public License 2.0 which is available at | ||
// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 | ||
// which is available at https://www.apache.org/licenses/LICENSE-2.0. | ||
// | ||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 | ||
// | ||
// Contributors: | ||
// ADLINK zenoh team, <zenoh@adlink-labs.tech> | ||
// | ||
|
||
//! # The plugin infrastructure for Zenoh. | ||
//! | ||
//! To build a plugin, up to 2 types may be constructed : | ||
//! * A [Plugin] type. | ||
//! * [PluginLaunch::start] should be non-blocking, and return a boxed instance of your stoppage type, which should implement [PluginStopper]. | ||
|
||
use clap::{Arg, ArgMatches}; | ||
use std::error::Error; | ||
use zenoh::net::runtime::Runtime; | ||
|
||
pub mod prelude { | ||
pub use crate::{dynamic_loading::*, Plugin, PluginLaunch, PluginStopper}; | ||
} | ||
|
||
/// Your plugin's compatibility. | ||
/// Currently, this should simply be the plugin crate's name. | ||
/// This structure may evolve to include more detailed information. | ||
#[derive(Clone, Debug, PartialEq)] | ||
pub struct Compatibility { | ||
pub uid: &'static str, | ||
} | ||
|
||
#[derive(Clone)] | ||
pub struct Incompatibility { | ||
pub own_compatibility: Compatibility, | ||
pub conflicting_with: Compatibility, | ||
pub details: Option<String>, | ||
} | ||
|
||
/// Zenoh plugins must implement [Plugin] and [PluginLaunch] | ||
pub trait Plugin: PluginLaunch + Sized + 'static { | ||
/// Returns this plugin's [Compatibility]. | ||
fn compatibility() -> Compatibility; | ||
|
||
/// As Zenoh instanciates plugins, it will append their [Compatibility] to an array. | ||
/// This array's current state will be shown to the next plugin. | ||
/// | ||
/// To signal that your plugin is incompatible with a previously instanciated plugin, return `Err`, | ||
/// Otherwise, return `Ok(Self::compatibility())`. | ||
/// | ||
/// By default, a plugin is non-reentrant to avoir reinstanciation if its dlib is accessible despite it already being statically linked. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "avoir" => "avoid" |
||
fn is_compatible_with(others: &[Compatibility]) -> Result<Compatibility, Incompatibility> { | ||
let own_compatibility = Self::compatibility(); | ||
if others.iter().any(|c| c == &own_compatibility) { | ||
let conflicting_with = own_compatibility.clone(); | ||
Err(Incompatibility { | ||
own_compatibility, | ||
conflicting_with, | ||
details: None, | ||
}) | ||
} else { | ||
Ok(own_compatibility) | ||
} | ||
} | ||
|
||
/// Returns the arguments that are required for the plugin's construction | ||
fn get_expected_args() -> Vec<Arg<'static, 'static>>; | ||
|
||
/// Constructs an instance of the plugin, which will be launched with [PluginLaunch::start] | ||
fn init(args: &ArgMatches) -> Result<Self, Box<dyn Error>>; | ||
|
||
fn box_init(args: &ArgMatches) -> Result<Box<dyn PluginLaunch>, Box<dyn Error>> { | ||
match Self::init(args) { | ||
Ok(v) => Ok(Box::new(v)), | ||
Err(e) => Err(e), | ||
} | ||
} | ||
} | ||
|
||
/// Allows a [Plugin] instance to be started. | ||
pub trait PluginLaunch { | ||
fn start(self, runtime: Runtime) -> Box<dyn PluginStopper>; | ||
} | ||
|
||
/// Allows a [Plugin] instance to be stopped. | ||
/// Typically, you can achieve this using a one-shot channel or an [AtomicBool](std::sync::atomic::AtomicBool). | ||
/// If you don't want a stopping mechanism, you can use `()` as your [PluginStopper]. | ||
pub trait PluginStopper { | ||
fn stop(self); | ||
} | ||
|
||
impl PluginStopper for () { | ||
fn stop(self) {} | ||
} | ||
|
||
pub mod dynamic_loading { | ||
use super::*; | ||
pub use no_mangle::*; | ||
|
||
type InitFn = fn(&ArgMatches) -> Result<Box<dyn PluginLaunch>, Box<dyn Error>>; | ||
pub type PluginVTableVersion = u16; | ||
|
||
/// This number should change any time the internal structure of [PluginVTable] changes | ||
pub const PLUGIN_VTABLE_VERSION: PluginVTableVersion = 0; | ||
|
||
#[repr(C)] | ||
struct PluginVTableInner { | ||
init: InitFn, | ||
is_compatible_with: fn(&[Compatibility]) -> Result<Compatibility, Incompatibility>, | ||
get_expected_args: fn() -> Vec<Arg<'static, 'static>>, | ||
} | ||
|
||
/// Automagical padding such that [PluginVTable::init]'s result is the size of a cache line | ||
#[repr(C)] | ||
struct PluginVTablePadding { | ||
__padding: [u8; PADDING_LENGTH], | ||
} | ||
const PADDING_LENGTH: usize = | ||
64 - std::mem::size_of::<Result<PluginVTableInner, PluginVTableVersion>>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It feels a bit less "magic" to set the padding length as "a cache line size minus the size of the type that actually interests us".
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a crate that does that: https://docs.rs/cache-padded/1.1.1/cache_padded/. I suggest to use this crate that is already widely used. |
||
impl PluginVTablePadding { | ||
fn new() -> Self { | ||
PluginVTablePadding { | ||
__padding: [0; PADDING_LENGTH], | ||
} | ||
} | ||
} | ||
|
||
/// For use with dynamically loaded plugins. Its size will not change accross versions, but its internal structure might. | ||
/// | ||
/// To ensure compatibility, its size and alignment must allow `size_of::<Result<PluginVTable, PluginVTableVersion>>() == 64` (one cache line). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. size_of::<Result<PluginVTable, PluginVTableVersion>> or size_of::<Result<PluginVTableInner, PluginVTableVersion>> ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. PluginVTable, the goal is for the size of the outer type to be fixed even if we change the size of the inner one (by adding more functions to the vtable for example). The constant should let us know that the pointers may not point to the same things, but I'm afraid of what would happen if the load_plugin function returns something bigger (or smaller) than what is expected by the caller. |
||
#[repr(C)] | ||
pub struct PluginVTable { | ||
inner: PluginVTableInner, | ||
padding: PluginVTablePadding, | ||
} | ||
|
||
impl PluginVTable { | ||
pub fn new<ConcretePlugin: Plugin + 'static>() -> Self { | ||
PluginVTable { | ||
inner: PluginVTableInner { | ||
is_compatible_with: ConcretePlugin::is_compatible_with, | ||
get_expected_args: ConcretePlugin::get_expected_args, | ||
init: ConcretePlugin::box_init, | ||
}, | ||
padding: PluginVTablePadding::new(), | ||
} | ||
} | ||
|
||
/// Ensures [PluginVTable]'s size stays the same between versions | ||
fn __size_check() { | ||
unsafe { | ||
std::mem::transmute::<_, [u8; 64]>(std::mem::MaybeUninit::< | ||
Result<Self, PluginVTableVersion>, | ||
>::uninit()) | ||
}; | ||
} | ||
|
||
pub fn is_compatible_with( | ||
&self, | ||
others: &[Compatibility], | ||
) -> Result<Compatibility, Incompatibility> { | ||
(self.inner.is_compatible_with)(others) | ||
} | ||
|
||
pub fn get_expected_args(&self) -> Vec<Arg<'static, 'static>> { | ||
(self.inner.get_expected_args)() | ||
} | ||
|
||
pub fn init(&self, args: &ArgMatches) -> Result<Box<dyn PluginLaunch>, Box<dyn Error>> { | ||
(self.inner.init)(args) | ||
} | ||
} | ||
|
||
pub use no_mangle::*; | ||
#[cfg(feature = "no_mangle")] | ||
pub mod no_mangle { | ||
/// This macro will add a non-mangled `load_plugin` function to the library if feature `no_mangle` is enabled (which it is by default). | ||
#[macro_export] | ||
macro_rules! declare_plugin { | ||
($ty: path) => { | ||
#[no_mangle] | ||
fn load_plugin( | ||
version: PluginVTableVersion, | ||
) -> Result<PluginVTable, PluginVTableVersion> { | ||
if version == PLUGIN_VTABLE_VERSION { | ||
Ok(PluginVTable::new::<$ty>()) | ||
} else { | ||
Err(PLUGIN_VTABLE_VERSION) | ||
} | ||
} | ||
}; | ||
} | ||
} | ||
#[cfg(not(feature = "no_mangle"))] | ||
pub mod no_mangle { | ||
#[macro_export] | ||
macro_rules! declare_plugin { | ||
($ty: path) => {}; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feel free to add yourself! 😉