neon-bindings / rfcs Goto Github PK
View Code? Open in Web Editor NEWRFCs for changes to Neon
License: Apache License 2.0
RFCs for changes to Neon
License: Apache License 2.0
Is it possible to define Rust types that the JS GC can trace through? This was brought up in the main repo:
I'd like to see a proposal for a command-line flag for vargo new
that lets you specify a local path to a local development tree of neon
, so that you can easily build neon projects with a custom build or fork of neon. This would be useful in particular for testing.
We need to spell out our approach to error safety. This has come up in a few issues on the main repo:
The purpose of this pre-RFC is to start gathering feedback from maintainers and users about the perception of mutability inside the VM via a Context<'_>
in order to guide a future RFC to update Neon's usage of Context<'_>
.
Neon provides the Context<'_>
trait for abstracting interactions with the JavaScript engine. Most methods in Neon require a reference to a Context
. Some of these references are shared (&Context<'_>
) and others are exclusive (&mut Context<'_>
). However, there are some inconsistencies in when one or the other is required. For example, most methods that create JavaScript objects require &mut
while methods that tend to hold a reference to a Context<'_>
(or an underlying Isolate
or napi_env
) tend to use a &
.
Neon should provide a consistent, opinionated API that guides users towards correct implementations with the help of the borrow checker. While I can't speak for all contributors, I know that I have lazily chose a safer exclusive reference (&mut
) without thinking critically if it was necessary.
&
and &mut
in idiomatic RustTerminology: For the purpose of this document,
&
will be referred to as a shared reference and&mut
an exclusive reference.
In idiomatic Rust, a shared reference &
signals that it is safe for multiple pointers to the same data to exist concurrently. In contrast, only a single exclusive reference &mut
may exist. Typically, this aligns 1-to-1 with mutability. There can either be one mutable reference or many immutable reference.
However, Rust also provides a concept of interior mutability. This allows mutating a value from a shared &
reference. Examples include, Cell
, RefCell
, and atomics. An AtomicU32
can be incremented with a shared &
reference because it provides internal consistency guarantees.
In application code, it is generally best to only use internal mutability to abstract implementation details in implementations that appear immutable from the user. For example, memoization of a pure function.
Unlike Rust, JavaScript makes use of exceptions. When working with the JavaScript Engine, it can be in several states:
Calls into JavaScript from a native extension may trigger an exception. For example, calling a function that executes throw
or attempting to read a property from undefined
. Neon modules must be sensitive to the state of the VM.
With the Nan backend, Neon does not prevent calls into the JavaScript engine when in a throwing exception. Making calls when in a throwing state is undefined behavior. The user must be careful to check for the Throw
error type in Result<_, _>
and avoid using the Context<'_>
.
All N-API methods verify state before executing code that accesses the engine. If the engine is not in a valid state for the call, a status enum other than napi_status::napi_ok
will be returned.
Neon functions that use N-API validate the status with assert_eq!(status, napi_status::napi_ok)
. The call to the engine will not occur, the Rust code will panic, the panic will be caught and returned to JavaScript and no undefined behavior will occur.
Context<'_>
?Under normal circumstances, creating a type (e.g., cx.string("hello")
) appears pure. A new type is created and the underlying Context<'_>
does not change. However, it's possible for the call to transition the engine to the throwing state.
In this case, an unrelated call may start failing (e.g., another cx.string("hello")
). This may violate expectations of the user that one "pure" function should not cause another "pure" function to fail.
"Pure" is quoted because these methods are not technically pure functions since they may cause side-effects.
In my opinion, this behavior is acceptable and preferred because it mirrors patterns found in the standard library. For example, Mutex
can be used with &
shared references, but if the lock becomes poisoned, future unrelated locks will fail. With this justification, I propose that most Neon methods should accept &Context<'_>
and not &mut Context<'_>
.
Using shared references in more places improves Neon's ergonomics because user defined structs and functions may alias Context<'_>
.
Context<'_>
is a trait and some implementations include additional methods and data. For example, ModuleContext<'a>
wraps the module as a JsObject
. Methods that mutate the underlying JsObject
should take an exclusive &mut
reference. E.g., export_function
.
As @kjvalencik points out in neon-bindings/neon#551 and neon-bindings/neon#552, the memory management model for EventHandler
is still not safe: it's susceptible to leaks and races.
The problem is that the we aren't coordinating the overall lifetime of the queue of pending invocations of the event handler (which all run on the main thread); we're only tracking the places in random other threads where invocations are requested. So when the last request is made, we drop the underlying queue even though there may be pending invocations.
Instead, I propose we expose the ownership model into the public API of EventHandler
. That is, the user should understand EventHandler
as an atomically refcounted handle to a JS event handler callback. The relevant changes to the API would be:
self
..clone()
it.The types would look something like this:
struct InvocationQueue(...); // private implementation
impl Drop for InvocationQueue { ... } // private implementation
#[derive(Clone)]
pub struct EventHandler(Arc<InvocationQueue>);
impl EventHandler {
pub fn new<'a, C: Context<'a>, T: Value>(cx: &C, this: Handle<T>, callback: Handle<JsFunction>) -> Self;
// CHANGE: consumes self (implementation clones the Arc<InvocationQueue> and sends to main thread)
pub fn schedule<T, F>(self, arg_cb: F)
where T: Value,
F: for<'a> FnOnce(&mut EventContext<'a>) -> JsResult<'a, T>,
F: Send + 'static;
// CHANGE: consumes self (implementation clones the Arc<InvocationQueue> and sends to main thread)
pub fn schedule_with<F>(self, arg_cb: F)
where F: FnOnce(&mut EventContext, Handle<JsValue>, Handle<JsFunction>) -> NeonResult<()>,
F: Send + 'static;
}
Notice that the implementation of the schedule*
methods would need to clone the Arc<InvocationQueue>
and send it to the main thread in order to ensure that it's kept alive until all the invocations have terminated. This prevents the race where the invocation queue is shut down before all the invocations have executed.
This should also make EventHandler
virtually leak-proof: the only way to keep it alive indefinitely is to either keep a local computation running infinitely or to keep cloning it and passing it on to other computations infinitely. Otherwise, by default it will be shut down once all cloned instances have either dropped or scheduled and executed their invocations.
This is an example of what would look different in user code. The example from the RFC would just need one more line to explicitly clone the EventHandler
on each iteration:
let mut this = cx.this();
let cb = cx.argument::<JsFunction>(0)?;
let handler = EventHandler::new(cb);
// or: = EventHandler::bind(this, cb);
thread::spawn(move || {
for i in 0..100 {
// do some work ....
thread::sleep(Duration::from_millis(40));
// schedule a call into javascript
let handler = handler.clone(); // CHANGE: clone the handler before scheduling an invocation
handler.schedule(move |cx| {
// successful result to be passed to the event handler
Ok(cx.number(i))
}
}
});
I spent a while this morning trying to figure out how to get at a JsArrayBuffer
as a [u8]
.
There is this RFC: 0005-array-buffer-views but that doesn't seem to be implemented (at least not as the RFC describes it)
There is also this legacy example: sharing-binary-data but the code there is commented out and wouldn't compile otherwise.
Here's what I arrived at:
if let Ok(buffer) = handle.downcast::<JsArrayBuffer>() {
let lock = cx.lock();
let binary = buffer.borrow(&lock);
let slice: &[u8] = binary.as_slice();
return Ok(slice.to_owned());
}
Updating this page: Arrays which currently has a broken link to where the legacy example used to be with some explanation of all the nice things you can do with as_slice
would be great.
As mentioned in #1 the design of a benchmarking tool should be considered.
Replaces #6
Proc macros might allow creating classes as wrappers around the JsBox
concept. For example:
struct MyClass {}
impl neon::JsClass for MyClass {
fn init(cx: FunctionContext) -> Self { todo!() }
}
#[neon::class]
impl MyClass {
#[neon::class_method(name = "myMethod")]
my_method(cx: FunctionContext) -> Self {}
}
A use case to keep in my is re-using implementations. For example, a user might have both MemorySession
and a RedisSession
classes that share implementations. These should be friendly to generics and re-using the base class.
As discussed in this issue, I propose making the change to the new N-API.
Supporting N-API has several benefits, the most important being that different node versions can use the same addon without recompiling. It will require some changes to the existing API, but my guess from experience with both projects is it will not be that bad.
As mentioned in #1 the design of neon test
should be considered.
IntoIterator
for iterating over a JsArray
FromIterator
for collecting an iterator into a JsArray
We need an RFC for cross-compilation flags. The design should try to mirror the UX of Cargo as much as possible.
The current borrowing API was designed to support borrowing both binary JavaScript data and the internal Rust
data of classes. It is intended to prevent aliased borrows.
However, it has multiple soundness holes (JavaScript handles might alias the same object, multiple Lock
can be created). It also has an API that includes types and helpers from before NLL that are awkward and unnecessary today.
Lastly, classes were removed in the N-API backend, leaving a generic API that only applies to borrowing binary data.
neon::borrow
neon::binary
JsArrayBuffer
) to neon::binary
neon::binary
Buffer
is a legacy type from before JavaScript included ArrayBuffer
. It is now a sub-class of Uint8Array
. Neon does not need any type level distinction for JsBuffer
because it can be treated as an array view.
JsBuffer
buffer
is a constructor on ArrayBuffer
for creating a Buffer
. The return type is indistinguishable from a Uint8Array
in Neon.Neon does not currently understand views. We will need to add this. We most likely do not need to know the view type and can have a simple JsArrayView
. If we want to include the type, it could be done with a generic phantom data element. JsArrayView<u8>
.
In an initial implementation, we will only support borrowing ArrayBuffer
. This way we can avoid complicated set math for determining if any of our borrows overlap. We can simply keep a HashSet
of starting pointers.
Lock
The Lock
type will be removed from the public API and moved to Context
.
Borrowing views will mostly be a convenience API. We will still borrow the entire ArrayBuffer
. This will behave very similar to how borrowing a &[u8]
from a Vec<u8>
borrows the entire Vec
.
This is necessary to remove complexity from users. If we don't provide this API, users cannot borrow buffers without first either copying data to an ArrayBuffer
or passing the underlying buffer along with a Range
.
Hi, not sure if anyone noticed:
I opened a RFC #25 with a preliminary implementation at neon-bindings/neon#375
Need to flesh out Rust APIs for working with the basic stdlib types, including:
Date
RegExp
What else?
When returning a typed array, like JsInt16Array
for example, the following JavaScript expression will evaluate to false
:
x instanceof Int16Array
Using Jest, this assertion
expect(arr).toBeInstanceOf(Int16Array)
fails and reports the following:
Expected constructor: Int16Array
Received constructor: Int16Array
This makes me suspect that Neon is using a constructor function with an identical name, but different equality identity.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.