Skip to content

Commit

Permalink
fix: align plugin api with Extension (#10427)
Browse files Browse the repository at this point in the history
  • Loading branch information
eliassjogreen authored May 7, 2021
1 parent c709f5d commit 4ed1428
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 209 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions core/extensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ impl Extension {

/// returns JS source code to be loaded into the isolate (either at snapshotting,
/// or at startup). as a vector of a tuple of the file name, and the source code.
pub(crate) fn init_js(&self) -> Vec<SourcePair> {
pub fn init_js(&self) -> Vec<SourcePair> {
match &self.js_files {
Some(files) => files.clone(),
None => vec![],
}
}

/// Called at JsRuntime startup to initialize ops in the isolate.
pub(crate) fn init_ops(&mut self) -> Option<Vec<OpPair>> {
pub fn init_ops(&mut self) -> Option<Vec<OpPair>> {
// TODO(@AaronO): maybe make op registration idempotent
if self.initialized {
panic!("init_ops called twice: not idempotent or correct");
Expand All @@ -43,15 +43,15 @@ impl Extension {
}

/// Allows setting up the initial op-state of an isolate at startup.
pub(crate) fn init_state(&self, state: &mut OpState) -> Result<(), AnyError> {
pub fn init_state(&self, state: &mut OpState) -> Result<(), AnyError> {
match &self.opstate_fn {
Some(ofn) => ofn(state),
None => Ok(()),
}
}

/// init_middleware lets us middleware op registrations, it's called before init_ops
pub(crate) fn init_middleware(&mut self) -> Option<Box<OpMiddlewareFn>> {
pub fn init_middleware(&mut self) -> Option<Box<OpMiddlewareFn>> {
self.middleware_fn.take()
}
}
Expand Down
1 change: 0 additions & 1 deletion core/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ mod normalize_path;
mod ops;
mod ops_builtin;
mod ops_json;
pub mod plugin_api;
mod resources;
mod runtime;

Expand Down
22 changes: 0 additions & 22 deletions core/plugin_api.rs

This file was deleted.

4 changes: 3 additions & 1 deletion runtime/js/40_plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
const core = window.Deno.core;

function openPlugin(filename) {
return core.opSync("op_open_plugin", filename);
const rid = core.opSync("op_open_plugin", filename);
core.syncOpsCache();
return rid;
}

window.__bootstrap.plugins = {
Expand Down
133 changes: 33 additions & 100 deletions runtime/ops/plugin.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::metrics::metrics_op;
use crate::permissions::Permissions;
use deno_core::error::AnyError;
use deno_core::futures::prelude::*;
use deno_core::op_sync;
use deno_core::plugin_api;
use deno_core::Extension;
use deno_core::Op;
use deno_core::OpAsyncFuture;
use deno_core::OpFn;
use deno_core::OpId;
use deno_core::OpState;
use deno_core::Resource;
use deno_core::ResourceId;
use deno_core::ZeroCopyBuf;
use dlopen::symbor::Library;
use log::debug;
use std::borrow::Cow;
use std::mem;
use std::path::PathBuf;
use std::pin::Pin;
use std::rc::Rc;
use std::task::Context;
use std::task::Poll;

/// A default `init` function for plugins which mimics the way the internal
/// extensions are initalized. Plugins currently do not support all extension
/// features and are most likely not going to in the future. Currently only
/// `init_state` and `init_ops` are supported while `init_middleware` and `init_js`
/// are not. Currently the `PluginResource` does not support being closed due to
/// certain risks in unloading the dynamic library without unloading dependent
/// functions and resources.
pub type InitFn = fn() -> Extension;

pub fn init() -> Extension {
Extension::builder()
Expand All @@ -44,111 +44,44 @@ pub fn op_open_plugin(
let plugin_lib = Library::open(filename).map(Rc::new)?;
let plugin_resource = PluginResource::new(&plugin_lib);

let rid;
let deno_plugin_init;
{
rid = state.resource_table.add(plugin_resource);
deno_plugin_init = *unsafe {
state
.resource_table
.get::<PluginResource>(rid)
.unwrap()
.lib
.symbol::<plugin_api::InitFn>("deno_plugin_init")
.unwrap()
};
}

let mut interface = PluginInterface::new(state, &plugin_lib);
deno_plugin_init(&mut interface);
// Forgets the plugin_lib value to prevent segfaults when the process exits
mem::forget(plugin_lib);

Ok(rid)
}

struct PluginResource {
lib: Rc<Library>,
}
let init = *unsafe { plugin_resource.0.symbol::<InitFn>("init") }?;
let rid = state.resource_table.add(plugin_resource);
let mut extension = init();

impl Resource for PluginResource {
fn name(&self) -> Cow<str> {
"plugin".into()
if !extension.init_js().is_empty() {
panic!("Plugins do not support loading js");
}
}

impl PluginResource {
fn new(lib: &Rc<Library>) -> Self {
Self { lib: lib.clone() }
if extension.init_middleware().is_some() {
panic!("Plugins do not support middleware");
}
}

struct PluginInterface<'a> {
state: &'a mut OpState,
plugin_lib: &'a Rc<Library>,
}

impl<'a> PluginInterface<'a> {
fn new(state: &'a mut OpState, plugin_lib: &'a Rc<Library>) -> Self {
Self { state, plugin_lib }
extension.init_state(state)?;
let ops = extension.init_ops().unwrap_or_default();
for (name, opfn) in ops {
state.op_table.register_op(name, opfn);
}
}

impl<'a> plugin_api::Interface for PluginInterface<'a> {
/// Does the same as `core::Isolate::register_op()`, but additionally makes
/// the registered op dispatcher, as well as the op futures created by it,
/// keep reference to the plugin `Library` object, so that the plugin doesn't
/// get unloaded before all its op registrations and the futures created by
/// them are dropped.
fn register_op(
&mut self,
name: &str,
dispatch_op_fn: plugin_api::DispatchOpFn,
) -> OpId {
let plugin_lib = self.plugin_lib.clone();
let plugin_op_fn: Box<OpFn> = Box::new(move |state_rc, payload| {
let mut state = state_rc.borrow_mut();
let mut interface = PluginInterface::new(&mut state, &plugin_lib);
let (_, buf): ((), Option<ZeroCopyBuf>) = payload.deserialize().unwrap();
let op = dispatch_op_fn(&mut interface, buf);
match op {
sync_op @ Op::Sync(..) => sync_op,
Op::Async(fut) => Op::Async(PluginOpAsyncFuture::new(&plugin_lib, fut)),
Op::AsyncUnref(fut) => {
Op::AsyncUnref(PluginOpAsyncFuture::new(&plugin_lib, fut))
}
_ => unreachable!(),
}
});
self.state.op_table.register_op(
name,
metrics_op(Box::leak(Box::new(name.to_string())), plugin_op_fn),
)
}
Ok(rid)
}

struct PluginOpAsyncFuture {
fut: Option<OpAsyncFuture>,
_plugin_lib: Rc<Library>,
}
struct PluginResource(Rc<Library>);

impl PluginOpAsyncFuture {
fn new(plugin_lib: &Rc<Library>, fut: OpAsyncFuture) -> Pin<Box<Self>> {
let wrapped_fut = Self {
fut: Some(fut),
_plugin_lib: plugin_lib.clone(),
};
Box::pin(wrapped_fut)
impl Resource for PluginResource {
fn name(&self) -> Cow<str> {
"plugin".into()
}
}

impl Future for PluginOpAsyncFuture {
type Output = <OpAsyncFuture as Future>::Output;
fn poll(mut self: Pin<&mut Self>, ctx: &mut Context) -> Poll<Self::Output> {
self.fut.as_mut().unwrap().poll_unpin(ctx)
fn close(self: Rc<Self>) {
unimplemented!();
}
}

impl Drop for PluginOpAsyncFuture {
fn drop(&mut self) {
self.fut.take();
impl PluginResource {
fn new(lib: &Rc<Library>) -> Self {
Self(lib.clone())
}
}
Loading

0 comments on commit 4ed1428

Please sign in to comment.