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 19 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
125 changes: 25 additions & 100 deletions runtime/ops/plugin.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,20 @@
// 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;

pub type InitFn = fn() -> Extension;

pub fn init() -> Extension {
Extension::builder()
Expand All @@ -43,111 +36,43 @@ pub fn op_open_plugin(
debug!("Loading Plugin: {:#?}", filename);
let plugin_lib = Library::open(filename).map(Rc::new)?;
let plugin_resource = PluginResource::new(&plugin_lib);
mem::forget(plugin_lib);
eliassjogreen marked this conversation as resolved.
Show resolved Hide resolved

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);

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, buf| {
let mut state = state_rc.borrow_mut();
let mut interface = PluginInterface::new(&mut state, &plugin_lib);
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())
}
}
128 changes: 89 additions & 39 deletions test_plugin/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,55 +1,105 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.

use deno_core::plugin_api::Interface;
use deno_core::plugin_api::Op;
use deno_core::plugin_api::OpResult;
use deno_core::plugin_api::ZeroCopyBuf;
use futures::future::FutureExt;
use std::borrow::Cow;
use std::cell::RefCell;
use std::rc::Rc;

use deno_core::error::bad_resource_id;
use deno_core::error::AnyError;
use deno_core::op_async;
use deno_core::op_sync;
use deno_core::Extension;
use deno_core::OpState;
use deno_core::Resource;
use deno_core::ResourceId;
use deno_core::ZeroCopyBuf;

#[no_mangle]
pub fn deno_plugin_init(interface: &mut dyn Interface) {
interface.register_op("testSync", op_test_sync);
interface.register_op("testAsync", op_test_async);
pub fn init() -> Extension {
Extension::builder()
.ops(vec![
("op_test_sync", op_sync(op_test_sync)),
("op_test_async", op_async(op_test_async)),
(
"op_test_resource_table_add",
op_sync(op_test_resource_table_add),
),
(
"op_test_resource_table_get",
op_sync(op_test_resource_table_get),
),
])
.build()
}

fn op_test_sync(
_interface: &mut dyn Interface,
_state: &mut OpState,
_args: (),
zero_copy: Option<ZeroCopyBuf>,
) -> Op {
if zero_copy.is_some() {
println!("Hello from plugin.");
}
) -> Result<String, AnyError> {
println!("Hello from sync plugin op.");

if let Some(buf) = zero_copy {
let buf_str = std::str::from_utf8(&buf[..]).unwrap();
let buf_str = std::str::from_utf8(&buf[..])?;
println!("zero_copy: {}", buf_str);
}
let result = b"test";
let result_box: Box<[u8]> = Box::new(*result);
Op::Sync(OpResult::Ok(result_box.into()))

Ok("test".to_string())
}

fn op_test_async(
_interface: &mut dyn Interface,
async fn op_test_async(
_state: Rc<RefCell<OpState>>,
_args: (),
zero_copy: Option<ZeroCopyBuf>,
) -> Op {
if zero_copy.is_some() {
println!("Hello from plugin.");
) -> Result<String, AnyError> {
println!("Hello from async plugin op.");

if let Some(buf) = zero_copy {
let buf_str = std::str::from_utf8(&buf[..])?;
println!("zero_copy: {}", buf_str);
}

let (tx, rx) = futures::channel::oneshot::channel::<Result<(), ()>>();
std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_secs(1));
tx.send(Ok(())).unwrap();
});
assert!(rx.await.is_ok());

Ok("test".to_string())
}

struct TestResource(String);
impl Resource for TestResource {
fn name(&self) -> Cow<str> {
"TestResource".into()
}
let fut = async move {
if let Some(buf) = zero_copy {
let buf_str = std::str::from_utf8(&buf[..]).unwrap();
println!("zero_copy: {}", buf_str);
}
let (tx, rx) = futures::channel::oneshot::channel::<Result<(), ()>>();
std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_secs(1));
tx.send(Ok(())).unwrap();
});
assert!(rx.await.is_ok());
let result = b"test";
let result_box: Box<[u8]> = Box::new(*result);
(0, OpResult::Ok(result_box.into()))
};

Op::Async(fut.boxed())
}

#[allow(clippy::unnecessary_wraps)]
fn op_test_resource_table_add(
state: &mut OpState,
text: String,
_zero_copy: Option<ZeroCopyBuf>,
) -> Result<u32, AnyError> {
println!("Hello from resource_table.add plugin op.");

Ok(state.resource_table.add(TestResource(text)))
}

fn op_test_resource_table_get(
state: &mut OpState,
rid: ResourceId,
_zero_copy: Option<ZeroCopyBuf>,
) -> Result<String, AnyError> {
println!("Hello from resource_table.get plugin op.");

Ok(
state
.resource_table
.get::<TestResource>(rid)
.ok_or_else(bad_resource_id)?
.0
.clone(),
)
}
Loading