Open SilverMira opened 5 months ago
It seems to me like extensibility is there to support loading newer libraries than expected, essentially working in the wrong direction to what's needed for a plugin system.
Try compiling the plugin with interface 0.1.1, then loading it with interface 0.1.0. This works in my experience, but is really the opposite of what I wanted.
EDIT: If instead of loading the plugin like this:
let lib = PluginLibRef::load_from_file(&PathBuf::from("tests/data/libexample_provider.so"))
.expect("plugin must be loadable");
You check the version and type layout yourself, reversing the expected and actual layouts:
let header = lib_header_from_path(&PathBuf::from("tests/data/libexample_provider.so"))
.expect("plugin library header must be loadable");
// TODO check version strings manually
if let IsLayoutChecked::Yes(layout) = header.root_mod_consts().layout() {
// Note the full path here, the abi_checking module is hidden from documentation
// Also note the arguments have been reversed, passing the plugin's layout first
abi_stable::abi_stability::abi_checking::check_layout_compatibility(layout, PluginLibRef::LAYOUT)
.expect("interface must be compatible");
};
let lib = unsafe {
header
.unchecked_layout::<PluginLibRef>()
.expect("plugin broke while loading")
};
Then everything seems to work like you would want for a plugin system. I can add new methods/nonexhaustive variants to the interface and still load older libraries, but not the other way around. No clue whether this is still safe to use, but it seems to behave correctly for now.
@thorio, that's a good idea for validating the layout, in my testing I did managed to use the unsafe functions to load the library which ignored layout validation, and everything seems to work as expected. My version of ensuring compatibility was a bit different, I used the same check_layout_compatibility function without reversing the arguments but instead permissively passes the validation if the error detected is just AbiInstability::FieldCountMismatch where expected count is more than actual count
fn ensure_compatibility(
interface: &'static TypeLayout,
implementation: &'static TypeLayout,
) -> Result<(), AbiInstabilityErrors> {
let compatibility = abi_stable::abi_stability::abi_checking::check_layout_compatibility(
interface,
implementation,
);
if let Err(err) = compatibility {
let incompatibilities = err.errors.iter().filter(|e| !e.errs.is_empty());
let fatal_incompatibilities = incompatibilities.filter(|err| {
err.errs.iter().any(|err| {
!matches!(
err,
AbiInstability::FieldCountMismatch(assert) if assert.expected > assert.found
)
})
});
if fatal_incompatibilities.count() > 0 {
return Err(err);
}
}
Ok(())
}
Checking the generated code for the Trait_TO, this part of the docs does work as expected (ie: default implementation does get invoked if there's no corresponding VTable entry in the library layout).
Accidentally calling newer methods on trait objects from older versions of a library will cause a panic at runtime, unless it has a default implementation (within the trait definition that #[sabi_trait] can see).
It's just the layout checking logic that seems to be contradicting what the docs say.
A library will not load (through safe means) if methods are added anywhere but the end.
For people who have stumbled upon this same plugin use case, I have since changed my approach to implementing a plugin system.
Check out remoc's rtc feature which can create the same sort of RPC capability like sabi_trait as long as there is a binary pipe between 2 ends (even across networks). In fact, you can even combine sabi with remoc, with sabi providing the abi stable pipe to a dylib using the channels
feature. Since remoc
is based on serde
, types with Serialize
and Deserialize
can just work easily without plumbing StableAbi, though of course, serializing/deserializing will have more overhead over just doing pure FFI
Hello, I'm currently trying out this crate to implement a plugin system.
Although the docs seem to suggest that I can load a library that's compiled against a previous minor version of the shared interface, I'm getting a AbiInstability error when trying to load.
I'm not sure whether I'm doing anything wrong which made it not work.
With the above interface crate, I'm able to compile and run
hello()
without issues. However if I update thePlugin
trait as such and try to rebuild & run the main executable without recompiling the library, I'm getting a AbiInstability error "Too many fields" while loading the library.Logging the AbiInstability error
```sh AbiInstability(Compared