-
Notifications
You must be signed in to change notification settings - Fork 5.4k
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
Feature: Native plugins #2385
Feature: Native plugins #2385
Conversation
I might need some help with adding the |
It's about 90% there at this point just need to fix some bugs, and figure out what to do with the new dependencies I added. Major TODOs still left:
|
1619c3e
to
e8ad99b
Compare
Mostly finished at this point. Ended up dropping
This poses some problems for cross platform mainly that the built artifact name varies from platform to platform. I didn't want to introduce any magic I.E. I also might need some help figuring out the current issue on appveyor or finding a simpler way to build my test binding plugin. I'm currently waiting on my changes to |
f6fe31e
to
d30c877
Compare
This is an awesome patch. Amazing work. Adding a native bindings API needs to be considered very carefully. It has impacts on security (all bets are off when you execute some random native code), performance, and usability (the GYP situation in Node is one of my largest blunders). I am not ready to commit to importing .so files via the module system. For one thing, this breaks browser compatibility; not such a big deal itself. It introduces need for There's also the question of how these I propose having a Line 53 in 856c442
I think making these changes will reduce the surface area of this proposal and allow us to land something basic before committing to defining how everything works upfront. I suggest renaming --allow-native-bindings to --allow-dlopen. |
Great feedback. I needed to get out of my one track mindset a little here. Since I already decided that direct cargo import is a little too complicated for this one, I don't really see any reason it has to be a dylib that loads typescript instead of the other way around. export interface DynamicLibFn {
dispatchSync(
data: Uint8Array,
zeroCopy: undefined | ArrayBufferView,
): Uint8Array;
dispatchAsync(
data: Uint8Array,
zeroCopy: undefined | ArrayBufferView,
): Promise<Uint8Array>;
}
// A loaded dynamic lib function.
// Loaded functions will need to loaded and addressed by unique identifiers
// for performance, since loading a function from a library for every call
// would likely be the limiting factor for many use cases.
/** @internal */
class DynamicLibFnImpl implements DynamicLibFn {
private readonly dlFnId: number;
constructor(dlId: number, name: string) {
this.dlFnId = loadDlFn(dlId, name);
}
dispatchSync(
data: Uint8Array,
zeroCopy: undefined | ArrayBufferView,
): Uint8Array {
// Like the prior Deno.nativeBindings.sendSync
return callDlFnSync(this.dlFnId, data, zeroCopy);
}
async dispatchAsync(
data: Uint8Array,
zeroCopy: undefined | ArrayBufferView,
): Promise<Uint8Array> {
// Like the prior Deno.nativeBindings.sendSync but async
return callDlFnAsync(this.dlFnId, data, zeroCopy);
}
}
export interface DynamicLib {
loadFn(name: string): DynamicLibFn;
}
// A loaded dynamic lib.
// Dynamic libraries need to remain loaded into memory on the rust side
// ,and then be addressed by their unique identifier to avoid loading
// the same library multiple times.
export class DynamicLibImpl implements DynamicLib {
// unique resource identifier for the loaded dynamic lib rust side
private readonly dlId: number;
private readonly fnMap: Map<string, DynamicLibFn> = new Map();
constructor(private readonly libraryPath: string) {
// call some op to load the library and get a resource identifier
this.dlId = loadDl(libraryPath);
}
loadFn(name: string): DynamicLibFn {
// call some op to load the fn from this library and get resource identifier
const cachedFn = this.fnMap.get(name);
if (cachedFn) {
return cachedFn;
} else {
const dlFn = new DynamicLibFnImpl(this.dlId, name);
this.fnMap.set(name, dlFn);
return dlFn;
}
}
} The implementation for the test plugin/binding being something like this const { platformFilename, DynamicLibary } = Deno.dlopen;
const dLib = new DynamicLibary("target/release/" + platformFilename("test_binding_lib"));
const testOpFn = dLib.loadFn("test_op");
export interface TestOptions {
name: string;
}
export interface TestResponse {
data: Uint8Array;
}
const textEncoder = new TextEncoder();
function encodeTestOp(args: TestOptions): Uint8Array {
return textEncoder.encode(JSON.stringify(args));
}
const textDecoder = new TextDecoder();
function decodeTestOp(data: Uint8Array): any {
return textDecoder.decode(data);
}
export const testOp = (args: TestOptions): any => {
return decodeTestOp(
testOpFn.dispatchSync(
encodeTestOp(args),
),
);
} |
@ry I made some major simplifications, and removed the import functionality PTAL. |
16ea973
to
1003661
Compare
.travis.yml
Outdated
@@ -73,7 +72,8 @@ script: | |||
- ./tools/lint.py | |||
- ./tools/test_format.py | |||
- ./tools/build.py -C target/release | |||
- DENO_BUILD_MODE=release ./tools/test.py | |||
- cargo build -p test_plugin --release --locked |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like that this breaks my usual ./tools/build.py && ./tools/test.py
Let me see if I can whip up a BUILD.gn to do this...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added the BUILD.gn file... it's a bit hacky but ./tools/build.py && ./tools/test.py -p 034
works.
tests/034_plugin.ts
Outdated
@@ -1,7 +1,7 @@ | |||
const { openPlugin, pluginFilename, env } = Deno; | |||
|
|||
const plugin = openPlugin( | |||
env().DENO_BUILD_PATH + "/" + pluginFilename("test_plugin") | |||
env().DENO_BUILD_PATH + "/rust_crates/" + pluginFilename("test_plugin") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is unfortunate...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is, but it works. Any idea what is up with the appveyor build?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tools/build.py --release
works fine on windows, but tools/build.py
doesn't for some reason.
This patch resolves the issue with windows, but it wouldn't really be a debug build anymore
It seems like we need to add a arg to the linker via deno/build_extra/rust/rust.gni Lines 247 to 252 in 1b48d67
|
1961a12
to
4d3862f
Compare
@afinch7 You simply skip the plugin build & tests when is_win && is_debug ? I forgot that we don't even test that in the 'cargo build' we always force release Lines 20 to 23 in 32cde32
|
In further testing I discovered that asyncs don't work correctly. |
@ry I don't think so. Also I forgot to mention that patch is for |
That works |
Update: I very much want to land this patch, but I think we need a bit more reorganization in core ops first. In particular I'd like to add |
Native plugins are still something we're actively working towards, but this PR is very out of date - so I'm closing it. We'll refer back to it for dlopen bindings. |
See denoland/std#475 for WIP example usage.