Skip to content

Commit ef16e23

Browse files
authored
Merge pull request #82 from neon-bindings/kv/tokio-example
Tokio example
2 parents 7a3698c + 2dbbef5 commit ef16e23

File tree

10 files changed

+2891
-16
lines changed

10 files changed

+2891
-16
lines changed

Cargo.lock

Lines changed: 825 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ members = [
44
"examples/cpu-count",
55
"examples/gzip-stream",
66
"examples/hello-world",
7+
"examples/tokio-fetch",
78
]
89

910
[profile.release]

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ All examples are for [`napi-backend`][napi-migration]. For examples using `legac
1818
| [`cpu-count`][cpu-count] | Return the number of CPUs |
1919
| [`gzip-stream`][gzip-stream] | Asynchronously compress a stream of data |
2020
| [`hello-world`][hello-world] | Return a JS String with a greeting message |
21+
| [`tokio-fetch`][tokio-fetch] | Asynchronously fetch a node release date |
2122

2223
[async-sqlite]: examples/async-sqlite
2324
[cpu-count]: examples/cpu-count
2425
[gzip-stream]: examples/gzip-stream
2526
[hello-world]: examples/hello-world
27+
[tokio-fetch]: examples/tokio-fetch
2628

2729
## Contributing
2830

examples/gzip-stream/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ crate-type = ["cdylib"]
1313
flate2 = "1"
1414

1515
[dependencies.neon]
16-
version = "0.10.0-alpha.2"
16+
version = "0.10.0-alpha.3"
1717
default-features = false
1818
features = ["napi-6", "promise-api", "task-api"]

examples/gzip-stream/src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,11 @@ impl CompressStream {
6868
// in both `compress_chunk` and `compress_finish`. It grabs any written bytes
6969
// with `CompressStream::output` and puts the data into an `ArrayBuffer`, throwing
7070
// a JavaScript exception if any Rust error occurred.
71-
fn and_buffer<'a>(
72-
cx: &mut TaskContext<'a>,
71+
fn and_buffer(
72+
mut cx: TaskContext,
7373
// Return value from `cx.task(..)` closure
7474
result: Result<Self, CompressError>,
75-
) -> JsResult<'a, JsBuffer> {
75+
) -> JsResult<JsBuffer> {
7676
let output = result
7777
// An error may have occurred while compressing; conditionally grab the
7878
// written data
@@ -81,7 +81,7 @@ impl CompressStream {
8181
.or_else(|err| cx.throw_error(err.to_string()))?;
8282

8383
// Create a `Buffer` backed by a `Vec<u8>` containing the written data
84-
Ok(JsBuffer::external(cx, output))
84+
Ok(JsBuffer::external(&mut cx, output))
8585
}
8686
}
8787

examples/tokio-fetch/Cargo.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[package]
2+
name = "tokio-fetch"
3+
version = "0.1.0"
4+
description = "Neon Tokio and Rust Async Example"
5+
license = "MIT"
6+
edition = "2018"
7+
exclude = ["index.node"]
8+
9+
[lib]
10+
crate-type = ["cdylib"]
11+
12+
[dependencies]
13+
once_cell = "1"
14+
reqwest = { version = "0.11", features = ["json"] }
15+
tokio = { version = "1", features = ["rt-multi-thread"] }
16+
serde = { version = "1", features = ["derive"] }
17+
18+
[dependencies.neon]
19+
version = "0.10.0-alpha.3"
20+
default-features = false
21+
features = ["channel-api", "napi-6", "promise-api"]

examples/tokio-fetch/README.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# `tokio-fetch`
2+
3+
Example of spawning a Rust async task on the [tokio][tokio] thread pool and resolving a JavaScript [Promise][promise] after it completes.
4+
5+
_**Note:** This example uses a pre-release version of Neon._
6+
7+
[tokio]: https://tokio.rs
8+
[promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
9+
10+
## Methods
11+
12+
#### `function nodeReleaseDate(): Promise<string>`
13+
14+
Asynchronously fetch the release date for the currently running Node process from nodejs.org.
15+
16+
## Design
17+
18+
### Executor
19+
20+
For optimum task scheduling, it is best to have a single Rust task executor (e.g., tokio runtime). To make the runtime singleton available to Neon functions, it is stored in a global using `OnceCell`.
21+
22+
```rust
23+
use once_cell::sync::OnceCell;
24+
use tokio::runtime::Runtime;
25+
26+
static RUNTIME: OnceCell<Runtime> = OnceCell::new();
27+
```
28+
29+
A small helper is provided to lazily initialize the runtime and throw an exception on failure.
30+
31+
```rust
32+
fn runtime<'a, C: Context<'a>>(cx: &mut C) -> NeonResult<&'static Runtime> {
33+
RUNTIME.get_or_try_init(|| Runtime::new().or_else(|err| cx.throw_error(err.to_string())))
34+
}
35+
```
36+
37+
### Spawning Tasks
38+
39+
Tasks may be spawned on the tokio runtime by using the `RUNTIME` handle. Spawning a task does *not* block the current thread. Inside a task the `await` keyword may be used and typical async Rust patterns may be used.
40+
41+
```rust
42+
let rt = runtime(&mut cx)?;
43+
44+
rt.spawn(async move {
45+
// Asynchronous Rust may used in here
46+
});
47+
```
48+
49+
### Promises
50+
51+
When a task is spawned on the tokio runtime, it will be executed at a later time. JavaScript needs to be notified when the task completes.
52+
53+
* Neon [`Channel`][channel] may be created for moving an operation from the tokio thread pool back to the JavaScript main thread.
54+
* [`cx.promise()`][cx-promise] creates a [`JsPromise`][js-promise] and [`Deferred`][deferred] for signaling JavaScript.
55+
* [`JsPromise`][js-promise] is synchronously returned and may be used with `await` in JavaScript
56+
* [`Deferred`][deferred] is used to settle the [`JsPromise`][js-promise] from the [`Channel`][channel] callback.
57+
58+
```rust
59+
let channel = cx.channel();
60+
let (deferred, promise) = cx.promise();
61+
62+
rt.spawn(async move {
63+
// Code here executes non-blocking on the tokio thread pool
64+
65+
deferred.settle_with(&channel, move |mut cx| {
66+
// Code here executes blocking on the JavaScript main thread
67+
68+
Ok(cx.undefined())
69+
});
70+
});
71+
72+
Ok(promise)
73+
```
74+
75+
[channel]: https://docs.rs/neon/0.10.0-alpha.3/neon/event/struct.Channel.html
76+
[cx-promise]: https://docs.rs/neon/0.10.0-alpha.3/neon/context/trait.Context.html#method.promise
77+
[js-promise]: https://docs.rs/neon/0.10.0-alpha.3/neon/types/struct.JsPromise.html
78+
[deferred]: https://docs.rs/neon/0.10.0-alpha.3/neon/types/struct.Deferred.html

0 commit comments

Comments
 (0)