Skip to content

Commit

Permalink
feat: plugin resources
Browse files Browse the repository at this point in the history
  • Loading branch information
afinch7 committed Feb 21, 2020
1 parent 978d723 commit e6f82c8
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 28 deletions.
18 changes: 15 additions & 3 deletions cli/ops/plugins.rs
Expand Up @@ -32,6 +32,16 @@ struct PluginResource {

struct InitContext {
ops: HashMap<String, Box<OpDispatcher>>,
state: PluginState,
}

impl InitContext {
fn new(state: State) -> Self {
Self {
ops: HashMap::new(),
state: PluginState::new(state.resource_table()),
}
}
}

impl PluginInitContext for InitContext {
Expand All @@ -42,6 +52,10 @@ impl PluginInitContext for InitContext {
format!("Op already registered: {}", name)
);
}

fn state(&self) -> PluginState {
self.state.clone()
}
}

#[derive(Deserialize)]
Expand Down Expand Up @@ -76,9 +90,7 @@ pub fn op_open_plugin(
.lib
.symbol::<PluginInitFn>("deno_plugin_init")
}?;
let mut init_context = InitContext {
ops: HashMap::new(),
};
let mut init_context = InitContext::new(state.clone());
init_fn(&mut init_context);
for op in init_context.ops {
// Register each plugin op in the `OpRegistry` with the name
Expand Down
43 changes: 43 additions & 0 deletions core/plugins.rs
@@ -1,14 +1,57 @@
use crate::isolate::ZeroCopyBuf;
use crate::ops::CoreOp;
use crate::resources::ResourceTable;
use std::cell::RefCell;
use std::rc::Rc;

pub struct PluginState {
resource_table: Rc<RefCell<ResourceTable>>,
}

impl Clone for PluginState {
fn clone(&self) -> Self {
PluginState {
resource_table: self.resource_table.clone(),
}
}
}

impl PluginState {
pub fn new(resource_table: Rc<RefCell<ResourceTable>>) -> Self {
Self { resource_table }
}

pub fn resource_table(&self) -> Rc<RefCell<ResourceTable>> {
self.resource_table.clone()
}
}

pub type PluginInitFn = fn(context: &mut dyn PluginInitContext);

type StatefulOpFn =
dyn Fn(&PluginState, &[u8], Option<ZeroCopyBuf>) -> CoreOp + 'static;

pub trait PluginInitContext {
fn register_op(
&mut self,
name: &str,
op: Box<dyn Fn(&[u8], Option<ZeroCopyBuf>) -> CoreOp + 'static>,
);

fn state(&self) -> PluginState;

fn stateful_op(
&self,
op: Box<StatefulOpFn>,
) -> Box<dyn Fn(&[u8], Option<ZeroCopyBuf>) -> CoreOp + 'static> {
let state = self.state();

Box::new(
move |control: &[u8], zero_copy: Option<ZeroCopyBuf>| -> CoreOp {
op(&state, control, zero_copy)
},
)
}
}

#[macro_export]
Expand Down
35 changes: 29 additions & 6 deletions test_plugin/src/lib.rs
@@ -1,17 +1,17 @@
#[macro_use]
extern crate deno_core;
extern crate futures;

use deno_core::CoreOp;
use deno_core::Op;
use deno_core::PluginInitContext;
use deno_core::{Buf, ZeroCopyBuf};
use deno_core::*;
use futures::future::FutureExt;

fn init(context: &mut dyn PluginInitContext) {
context.register_op("testSync", Box::new(op_test_sync));
context.register_op("testAsync", Box::new(op_test_async));
context.register_op(
"createResource",
context.stateful_op(Box::new(op_create_resource)),
);
}

init_fn!(init);

pub fn op_test_sync(data: &[u8], zero_copy: Option<ZeroCopyBuf>) -> CoreOp {
Expand Down Expand Up @@ -51,3 +51,26 @@ pub fn op_test_async(data: &[u8], zero_copy: Option<ZeroCopyBuf>) -> CoreOp {

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

struct TestResource {
pub name: String,
}

impl Drop for TestResource {
fn drop(&mut self) {
println!("Dropped resource: {}", self.name)
}
}

pub fn op_create_resource(
state: &PluginState,
data: &[u8],
_zero_copy: Option<ZeroCopyBuf>,
) -> CoreOp {
let name = std::str::from_utf8(&data[..]).unwrap().to_string();
let _table = state.resource_table();
let mut table = _table.borrow_mut();
let resource = TestResource { name };
let rid = table.add("testResource", Box::new(resource));
Op::Sync(Box::new(rid.to_be_bytes()))
}
36 changes: 35 additions & 1 deletion test_plugin/tests/integration_tests.rs
Expand Up @@ -26,7 +26,7 @@ fn basic() {
let _build_plugin_output = build_plugin.output().unwrap();
let output = deno_cmd()
.arg("--allow-plugin")
.arg("tests/test.js")
.arg("tests/test_basic.js")
.arg(BUILD_VARIANT)
.output()
.unwrap();
Expand All @@ -45,3 +45,37 @@ fn basic() {
assert_eq!(stdout, expected);
assert_eq!(stderr, "");
}

// TODO(ry) Re-enable this test on windows. It is flaky for an unknown reason.
// Actually not sure if this one will be flaky but disabling it out of caution.
#[cfg(not(windows))]
#[test]
fn resources() {
let mut build_plugin_base = Command::new("cargo");
let mut build_plugin =
build_plugin_base.arg("build").arg("-p").arg("test_plugin");
if BUILD_VARIANT == "release" {
build_plugin = build_plugin.arg("--release");
}
let _build_plugin_output = build_plugin.output().unwrap();
let output = deno_cmd()
.arg("--allow-plugin")
.arg("tests/test_resources.js")
.arg(BUILD_VARIANT)
.output()
.unwrap();
let stdout = std::str::from_utf8(&output.stdout).unwrap();
let stderr = std::str::from_utf8(&output.stderr).unwrap();
if !output.status.success() {
println!("stdout {}", stdout);
println!("stderr {}", stderr);
}
assert!(output.status.success());
let expected = if cfg!(target_os = "windows") {
"testResource\r\ntestResource\r\nDropped resource: two\nDropped resource: one\n"
} else {
"testResource\ntestResource\nDropped resource: two\nDropped resource: one\n"
};
assert_eq!(stdout, expected);
assert_eq!(stderr, "");
}
20 changes: 20 additions & 0 deletions test_plugin/tests/ops.js
@@ -0,0 +1,20 @@
const filenameBase = "test_plugin";

let filenameSuffix = ".so";
let filenamePrefix = "lib";

if (Deno.build.os === "win") {
filenameSuffix = ".dll";
filenamePrefix = "";
}
if (Deno.build.os === "mac") {
filenameSuffix = ".dylib";
}

const filename = `../target/${Deno.args[0]}/${filenamePrefix}${filenameBase}${filenameSuffix}`;

const plugin = Deno.openPlugin(filename);

const { testSync, testAsync, createResource } = plugin.ops;

export { testSync, testAsync, createResource };
19 changes: 1 addition & 18 deletions test_plugin/tests/test.js → test_plugin/tests/test_basic.js
@@ -1,21 +1,4 @@
const filenameBase = "test_plugin";

let filenameSuffix = ".so";
let filenamePrefix = "lib";

if (Deno.build.os === "win") {
filenameSuffix = ".dll";
filenamePrefix = "";
}
if (Deno.build.os === "mac") {
filenameSuffix = ".dylib";
}

const filename = `../target/${Deno.args[0]}/${filenamePrefix}${filenameBase}${filenameSuffix}`;

const plugin = Deno.openPlugin(filename);

const { testSync, testAsync } = plugin.ops;
import { testSync, testAsync } from "./ops.js";

const textDecoder = new TextDecoder();

Expand Down
22 changes: 22 additions & 0 deletions test_plugin/tests/test_resources.js
@@ -0,0 +1,22 @@
import { createResource as createResourceOp } from "./ops.js";

const textEncoder = new TextEncoder();

function createResource(name) {
const response = createResourceOp.dispatch(textEncoder.encode(name));
const rid = new DataView(response.buffer, 0, 4).getUint32(0);
return {
rid
};
}

function dropResource(resource) {
Deno.close(resource.rid);
}

const one = createResource("one");
const two = createResource("two");
console.log(Deno.resources()[one.rid]);
console.log(Deno.resources()[two.rid]);
dropResource(two);
dropResource(one);

0 comments on commit e6f82c8

Please sign in to comment.