Skip to content
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

feat: align plugin api with Extension #10427

Merged
merged 21 commits into from
May 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
eliassjogreen marked this conversation as resolved.
Show resolved Hide resolved

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