fix(napi): Buffer value lifetime should align the Rust lifetime
This commit is contained in:
parent
871cf5554d
commit
1104742983
6 changed files with 53 additions and 2 deletions
|
@ -3,15 +3,33 @@ use std::{mem, ptr};
|
||||||
|
|
||||||
use crate::{bindgen_prelude::*, check_status, sys, Result, ValueType};
|
use crate::{bindgen_prelude::*, check_status, sys, Result, ValueType};
|
||||||
|
|
||||||
/// zero copy u8 vector shared between rust and napi
|
/// Zero copy u8 vector shared between rust and napi.
|
||||||
|
/// Auto reference the raw JavaScript value, and release it when dropped.
|
||||||
|
/// So it is safe to use it in `async fn`, the `&[u8]` under the hood will not be dropped until the `drop` called.
|
||||||
pub struct Buffer {
|
pub struct Buffer {
|
||||||
inner: mem::ManuallyDrop<Vec<u8>>,
|
inner: mem::ManuallyDrop<Vec<u8>>,
|
||||||
|
raw: Option<(sys::napi_ref, sys::napi_env)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for Buffer {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some((ref_, env)) = self.raw {
|
||||||
|
check_status_or_throw!(
|
||||||
|
env,
|
||||||
|
unsafe { sys::napi_delete_reference(env, ref_) },
|
||||||
|
"Failed to delete Buffer reference in drop"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for Buffer {}
|
||||||
|
|
||||||
impl From<Vec<u8>> for Buffer {
|
impl From<Vec<u8>> for Buffer {
|
||||||
fn from(data: Vec<u8>) -> Self {
|
fn from(data: Vec<u8>) -> Self {
|
||||||
Buffer {
|
Buffer {
|
||||||
inner: mem::ManuallyDrop::new(data),
|
inner: mem::ManuallyDrop::new(data),
|
||||||
|
raw: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,7 +86,11 @@ impl FromNapiValue for Buffer {
|
||||||
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
|
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
|
||||||
let mut buf = ptr::null_mut();
|
let mut buf = ptr::null_mut();
|
||||||
let mut len = 0;
|
let mut len = 0;
|
||||||
|
let mut ref_ = ptr::null_mut();
|
||||||
|
check_status!(
|
||||||
|
unsafe { sys::napi_create_reference(env, napi_val, 1, &mut ref_) },
|
||||||
|
"Failed to create reference from Buffer"
|
||||||
|
)?;
|
||||||
check_status!(
|
check_status!(
|
||||||
unsafe { sys::napi_get_buffer_info(env, napi_val, &mut buf, &mut len as *mut usize) },
|
unsafe { sys::napi_get_buffer_info(env, napi_val, &mut buf, &mut len as *mut usize) },
|
||||||
"Failed to convert napi buffer into rust Vec<u8>"
|
"Failed to convert napi buffer into rust Vec<u8>"
|
||||||
|
@ -76,12 +98,27 @@ impl FromNapiValue for Buffer {
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
inner: mem::ManuallyDrop::new(unsafe { Vec::from_raw_parts(buf as *mut _, len, len) }),
|
inner: mem::ManuallyDrop::new(unsafe { Vec::from_raw_parts(buf as *mut _, len, len) }),
|
||||||
|
raw: Some((ref_, env)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToNapiValue for Buffer {
|
impl ToNapiValue for Buffer {
|
||||||
unsafe fn to_napi_value(env: sys::napi_env, mut val: Self) -> Result<sys::napi_value> {
|
unsafe fn to_napi_value(env: sys::napi_env, mut val: Self) -> Result<sys::napi_value> {
|
||||||
|
// From Node.js value, not from `Vec<u8>`
|
||||||
|
if let Some((ref_, _)) = val.raw {
|
||||||
|
let mut buf = ptr::null_mut();
|
||||||
|
check_status!(
|
||||||
|
unsafe { sys::napi_get_reference_value(env, ref_, &mut buf) },
|
||||||
|
"Failed to get Buffer value from reference"
|
||||||
|
)?;
|
||||||
|
check_status!(
|
||||||
|
unsafe { sys::napi_delete_reference(env, ref_) },
|
||||||
|
"Failed to delete Buffer reference"
|
||||||
|
)?;
|
||||||
|
val.raw = None; // Prevent double free
|
||||||
|
return Ok(buf);
|
||||||
|
}
|
||||||
let len = val.inner.len();
|
let len = val.inner.len();
|
||||||
let mut ret = ptr::null_mut();
|
let mut ret = ptr::null_mut();
|
||||||
check_status!(
|
check_status!(
|
||||||
|
|
|
@ -152,6 +152,7 @@ Generated by [AVA](https://avajs.dev).
|
||||||
export function createExternalTypedArray(): Uint32Array␊
|
export function createExternalTypedArray(): Uint32Array␊
|
||||||
export function mutateTypedArray(input: Float32Array): void␊
|
export function mutateTypedArray(input: Float32Array): void␊
|
||||||
export function derefUint8Array(a: Uint8Array, b: Uint8ClampedArray): number␊
|
export function derefUint8Array(a: Uint8Array, b: Uint8ClampedArray): number␊
|
||||||
|
export function bufferPassThrough(buf: Buffer): Promise<Buffer>␊
|
||||||
/**␊
|
/**␊
|
||||||
* \`constructor\` option for \`struct\` requires all fields to be public,␊
|
* \`constructor\` option for \`struct\` requires all fields to be public,␊
|
||||||
* otherwise tag impl fn as constructor␊
|
* otherwise tag impl fn as constructor␊
|
||||||
|
|
Binary file not shown.
|
@ -86,6 +86,7 @@ import {
|
||||||
chronoDateToMillis,
|
chronoDateToMillis,
|
||||||
derefUint8Array,
|
derefUint8Array,
|
||||||
chronoDateAdd1Minute,
|
chronoDateAdd1Minute,
|
||||||
|
bufferPassThrough,
|
||||||
} from '../'
|
} from '../'
|
||||||
|
|
||||||
test('export const', (t) => {
|
test('export const', (t) => {
|
||||||
|
@ -382,6 +383,12 @@ test('async move', async (t) => {
|
||||||
t.is(await asyncMultiTwo(2), 4)
|
t.is(await asyncMultiTwo(2), 4)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('buffer passthrough', async (t) => {
|
||||||
|
const fixture = Buffer.from('hello world')
|
||||||
|
const ret = await bufferPassThrough(fixture)
|
||||||
|
t.deepEqual(ret, fixture)
|
||||||
|
})
|
||||||
|
|
||||||
test('either', (t) => {
|
test('either', (t) => {
|
||||||
t.is(eitherStringOrNumber(2), 2)
|
t.is(eitherStringOrNumber(2), 2)
|
||||||
t.is(eitherStringOrNumber('hello'), 'hello'.length)
|
t.is(eitherStringOrNumber('hello'), 'hello'.length)
|
||||||
|
|
1
examples/napi/index.d.ts
vendored
1
examples/napi/index.d.ts
vendored
|
@ -142,6 +142,7 @@ export function convertU32Array(input: Uint32Array): Array<number>
|
||||||
export function createExternalTypedArray(): Uint32Array
|
export function createExternalTypedArray(): Uint32Array
|
||||||
export function mutateTypedArray(input: Float32Array): void
|
export function mutateTypedArray(input: Float32Array): void
|
||||||
export function derefUint8Array(a: Uint8Array, b: Uint8ClampedArray): number
|
export function derefUint8Array(a: Uint8Array, b: Uint8ClampedArray): number
|
||||||
|
export function bufferPassThrough(buf: Buffer): Promise<Buffer>
|
||||||
/**
|
/**
|
||||||
* `constructor` option for `struct` requires all fields to be public,
|
* `constructor` option for `struct` requires all fields to be public,
|
||||||
* otherwise tag impl fn as constructor
|
* otherwise tag impl fn as constructor
|
||||||
|
|
|
@ -33,3 +33,8 @@ fn mutate_typed_array(mut input: Float32Array) {
|
||||||
fn deref_uint8_array(a: Uint8Array, b: Uint8ClampedArray) -> u32 {
|
fn deref_uint8_array(a: Uint8Array, b: Uint8ClampedArray) -> u32 {
|
||||||
(a.len() + b.len()) as u32
|
(a.len() + b.len()) as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
async fn buffer_pass_through(buf: Buffer) -> Result<Buffer> {
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue