Skip to content

Commit 812d49c

Browse files
authored
Merge pull request #48 from enmand/feat/asyncdb-async-std
feat: add async-std as an optional runtime for AsyncDB
2 parents 0c7da61 + 7fb3e23 commit 812d49c

9 files changed

Lines changed: 263 additions & 94 deletions

File tree

Cargo.toml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,16 @@ rand = "0.8.5"
2222
snap = "1.0"
2323

2424
errno = { optional = true, version = "0.2" }
25-
fs2 = {optional = true, version = "0.4.3"}
25+
fs2 = { optional = true, version = "0.4.3" }
2626

27-
tokio = { optional = true, features = ["rt", "sync"], version = ">= 1.21" }
27+
tokio = { optional = true, features = ["rt", "sync"], version = "1.39.3" }
28+
async-std = { optional = true, version = "1.12.0" }
2829

2930
[features]
3031
default = ["fs"]
31-
async = ["tokio"]
32+
async = ["asyncdb-tokio"]
33+
asyncdb-tokio = ["tokio"]
34+
asyncdb-async-std = ["async-std"]
3235
fs = ["errno", "fs2"]
3336

3437
[dev-dependencies]
@@ -46,7 +49,8 @@ members = [
4649
"examples/leveldb-tool",
4750
"examples/word-analyze",
4851
"examples/stresstest",
49-
"examples/asyncdb",
52+
"examples/asyncdb-tokio",
53+
"examples/asyncdb-async-std",
5054
"examples/mcpe",
5155
"examples/kvserver",
5256
]
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
name = "asyncdb-async-std"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
async-std = { version = "1.12.0", features = ["attributes"] }
10+
rusty-leveldb = { path = "../../", features = ["asyncdb-async-std"] }
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use rusty_leveldb::{AsyncDB, Options, Status, StatusCode};
2+
3+
#[async_std::main]
4+
async fn main() {
5+
let adb = AsyncDB::new("testdb", Options::default()).unwrap();
6+
7+
adb.put("Hello".as_bytes().to_owned(), "World".as_bytes().to_owned())
8+
.await
9+
.expect("put()");
10+
11+
let r = adb.get("Hello".as_bytes().to_owned()).await;
12+
assert_eq!(r, Ok(Some("World".as_bytes().to_owned())));
13+
14+
let snapshot = adb.get_snapshot().await.expect("get_snapshot()");
15+
16+
adb.delete("Hello".as_bytes().to_owned())
17+
.await
18+
.expect("delete()");
19+
20+
// A snapshot allows us to travel back in time before the deletion.
21+
let r2 = adb.get_at(snapshot, "Hello".as_bytes().to_owned()).await;
22+
assert_eq!(r2, Ok(Some("World".as_bytes().to_owned())));
23+
24+
// Once dropped, a snapshot cannot be used anymore.
25+
adb.drop_snapshot(snapshot).await.expect("drop_snapshot()");
26+
27+
let r3 = adb.get_at(snapshot, "Hello".as_bytes().to_owned()).await;
28+
assert_eq!(
29+
r3,
30+
Err(Status {
31+
code: StatusCode::AsyncError,
32+
err: "Unknown snapshot reference: this is a bug".to_string()
33+
})
34+
);
35+
36+
adb.flush().await.expect("flush()");
37+
adb.close().await.expect("close()");
38+
}
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
[package]
2-
name = "asyncdb"
2+
name = "asyncdb-tokio"
33
version = "0.1.0"
44
edition = "2021"
55

66
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
77

88
[dependencies]
9-
tokio = { version = "1.21", features = ["rt", "macros" ] }
10-
rusty-leveldb = { path = "../../", features = ["async"] }
9+
tokio = { version = "1.21", features = ["rt", "macros"] }
10+
rusty-leveldb = { path = "../../", features = ["asyncdb-tokio"] }

src/asyncdb.rs

Lines changed: 27 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
11
use std::collections::hash_map::HashMap;
2-
use std::path::Path;
3-
use std::sync::Arc;
42

5-
use crate::{Options, Result, Status, StatusCode, WriteBatch, DB};
3+
use crate::{
4+
send_response, send_response_result, AsyncDB, Message, Result, Status, StatusCode, WriteBatch,
5+
DB,
6+
};
67

7-
use tokio::sync::mpsc;
8-
use tokio::sync::oneshot;
9-
use tokio::task::{spawn_blocking, JoinHandle};
10-
11-
const CHANNEL_BUFFER_SIZE: usize = 32;
8+
pub(crate) const CHANNEL_BUFFER_SIZE: usize = 32;
129

1310
#[derive(Clone, Copy)]
1411
pub struct SnapshotRef(usize);
1512

1613
/// A request sent to the database thread.
17-
enum Request {
14+
pub(crate) enum Request {
1815
Close,
1916
Put { key: Vec<u8>, val: Vec<u8> },
2017
Delete { key: Vec<u8> },
@@ -28,42 +25,14 @@ enum Request {
2825
}
2926

3027
/// A response received from the database thread.
31-
enum Response {
28+
pub(crate) enum Response {
3229
OK,
3330
Error(Status),
3431
Value(Option<Vec<u8>>),
3532
Snapshot(SnapshotRef),
3633
}
3734

38-
/// Contains both a request and a back-channel for the reply.
39-
struct Message {
40-
req: Request,
41-
resp_channel: oneshot::Sender<Response>,
42-
}
43-
44-
/// `AsyncDB` makes it easy to use LevelDB in a tokio runtime.
45-
/// The methods follow very closely the main API (see `DB` type). Iteration is not yet implemented.
46-
///
47-
/// TODO: Make it work in other runtimes as well. This is a matter of adapting the blocking thread
48-
/// mechanism as well as the channel types.
49-
#[derive(Clone)]
50-
pub struct AsyncDB {
51-
jh: Arc<JoinHandle<()>>,
52-
send: mpsc::Sender<Message>,
53-
}
54-
5535
impl AsyncDB {
56-
/// Create a new or open an existing database.
57-
pub fn new<P: AsRef<Path>>(name: P, opts: Options) -> Result<AsyncDB> {
58-
let db = DB::open(name, opts)?;
59-
let (send, recv) = mpsc::channel(CHANNEL_BUFFER_SIZE);
60-
let jh = spawn_blocking(move || AsyncDB::run_server(db, recv));
61-
Ok(AsyncDB {
62-
jh: Arc::new(jh),
63-
send,
64-
})
65-
}
66-
6736
pub async fn close(&self) -> Result<()> {
6837
let r = self.process_request(Request::Close).await?;
6938
match r {
@@ -182,104 +151,79 @@ impl AsyncDB {
182151
}
183152
}
184153

185-
async fn process_request(&self, req: Request) -> Result<Response> {
186-
let (tx, rx) = oneshot::channel();
187-
let m = Message {
188-
req,
189-
resp_channel: tx,
190-
};
191-
if let Err(e) = self.send.send(m).await {
192-
return Err(Status {
193-
code: StatusCode::AsyncError,
194-
err: e.to_string(),
195-
});
196-
}
197-
let resp = rx.await;
198-
match resp {
199-
Err(e) => Err(Status {
200-
code: StatusCode::AsyncError,
201-
err: e.to_string(),
202-
}),
203-
Ok(r) => Ok(r),
204-
}
205-
}
206-
207-
fn run_server(mut db: DB, mut recv: mpsc::Receiver<Message>) {
154+
pub(crate) fn run_server(mut db: DB, mut recv: impl ReceiverExt<Message>) {
208155
let mut snapshots = HashMap::new();
209156
let mut snapshot_counter: usize = 0;
210157

211158
while let Some(message) = recv.blocking_recv() {
212159
match message.req {
213160
Request::Close => {
214-
message.resp_channel.send(Response::OK).ok();
161+
send_response(message.resp_channel, Response::OK);
215162
recv.close();
216163
return;
217164
}
218165
Request::Put { key, val } => {
219166
let ok = db.put(&key, &val);
220-
send_response(message.resp_channel, ok);
167+
send_response_result(message.resp_channel, ok);
221168
}
222169
Request::Delete { key } => {
223170
let ok = db.delete(&key);
224-
send_response(message.resp_channel, ok);
171+
send_response_result(message.resp_channel, ok);
225172
}
226173
Request::Write { batch, sync } => {
227174
let ok = db.write(batch, sync);
228-
send_response(message.resp_channel, ok);
175+
send_response_result(message.resp_channel, ok);
229176
}
230177
Request::Flush => {
231178
let ok = db.flush();
232-
send_response(message.resp_channel, ok);
179+
send_response_result(message.resp_channel, ok);
233180
}
234181
Request::GetAt { snapshot, key } => {
235182
let snapshot_id = snapshot.0;
236183
if let Some(snapshot) = snapshots.get(&snapshot_id) {
237184
let ok = db.get_at(snapshot, &key);
238185
match ok {
239186
Err(e) => {
240-
message.resp_channel.send(Response::Error(e)).ok();
187+
send_response(message.resp_channel, Response::Error(e));
241188
}
242189
Ok(v) => {
243-
message.resp_channel.send(Response::Value(v)).ok();
190+
send_response(message.resp_channel, Response::Value(v));
244191
}
245192
};
246193
} else {
247-
message
248-
.resp_channel
249-
.send(Response::Error(Status {
194+
send_response(
195+
message.resp_channel,
196+
Response::Error(Status {
250197
code: StatusCode::AsyncError,
251198
err: "Unknown snapshot reference: this is a bug".to_string(),
252-
}))
253-
.ok();
199+
}),
200+
);
254201
}
255202
}
256203
Request::Get { key } => {
257204
let r = db.get(&key);
258-
message.resp_channel.send(Response::Value(r)).ok();
205+
send_response(message.resp_channel, Response::Value(r));
259206
}
260207
Request::GetSnapshot => {
261208
snapshots.insert(snapshot_counter, db.get_snapshot());
262209
let sref = SnapshotRef(snapshot_counter);
263210
snapshot_counter += 1;
264-
message.resp_channel.send(Response::Snapshot(sref)).ok();
211+
send_response(message.resp_channel, Response::Snapshot(sref));
265212
}
266213
Request::DropSnapshot { snapshot } => {
267214
snapshots.remove(&snapshot.0);
268-
send_response(message.resp_channel, Ok(()));
215+
send_response_result(message.resp_channel, Ok(()));
269216
}
270217
Request::CompactRange { from, to } => {
271218
let ok = db.compact_range(&from, &to);
272-
send_response(message.resp_channel, ok);
219+
send_response_result(message.resp_channel, ok);
273220
}
274221
}
275222
}
276223
}
277224
}
278225

279-
fn send_response(ch: oneshot::Sender<Response>, result: Result<()>) {
280-
if let Err(e) = result {
281-
ch.send(Response::Error(e)).ok();
282-
} else {
283-
ch.send(Response::OK).ok();
284-
}
226+
pub(crate) trait ReceiverExt<T> {
227+
fn blocking_recv(&mut self) -> Option<T>;
228+
fn close(&mut self);
285229
}

src/asyncdb_async_std.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
use std::path::Path;
2+
use std::sync::Arc;
3+
4+
use async_std::channel;
5+
use async_std::task::{spawn_blocking, JoinHandle};
6+
7+
use crate::asyncdb::{ReceiverExt, Request, Response, CHANNEL_BUFFER_SIZE};
8+
use crate::{Options, Result, Status, StatusCode, DB};
9+
10+
pub(crate) struct Message {
11+
pub(crate) req: Request,
12+
pub(crate) resp_channel: channel::Sender<Response>,
13+
}
14+
/// `AsyncDB` makes it easy to use LevelDB in a async-std runtime.
15+
/// The methods follow very closely the main API (see `DB` type). Iteration is not yet implemented.
16+
#[derive(Clone)]
17+
pub struct AsyncDB {
18+
jh: Arc<JoinHandle<()>>,
19+
send: channel::Sender<Message>,
20+
}
21+
22+
impl AsyncDB {
23+
/// Create a new or open an existing database.
24+
pub fn new<P: AsRef<Path>>(name: P, opts: Options) -> Result<AsyncDB> {
25+
let db = DB::open(name, opts)?;
26+
27+
let (send, recv) = channel::bounded(CHANNEL_BUFFER_SIZE);
28+
let jh = spawn_blocking(move || AsyncDB::run_server(db, recv));
29+
Ok(AsyncDB {
30+
jh: Arc::new(jh),
31+
send,
32+
})
33+
}
34+
35+
pub(crate) async fn process_request(&self, req: Request) -> Result<Response> {
36+
let (tx, rx) = channel::bounded(1);
37+
38+
let m = Message {
39+
req,
40+
resp_channel: tx,
41+
};
42+
if let Err(e) = self.send.send(m).await {
43+
return Err(Status {
44+
code: StatusCode::AsyncError,
45+
err: e.to_string(),
46+
});
47+
}
48+
let resp = rx.recv().await;
49+
match resp {
50+
Err(e) => Err(Status {
51+
code: StatusCode::AsyncError,
52+
err: e.to_string(),
53+
}),
54+
Ok(r) => Ok(r),
55+
}
56+
}
57+
}
58+
59+
pub(crate) fn send_response_result(ch: channel::Sender<Response>, result: Result<()>) {
60+
if let Err(e) = result {
61+
ch.try_send(Response::Error(e)).ok();
62+
} else {
63+
ch.try_send(Response::OK).ok();
64+
}
65+
}
66+
67+
pub(crate) fn send_response(ch: channel::Sender<Response>, res: Response) {
68+
ch.send_blocking(res).ok();
69+
}
70+
71+
impl<T> ReceiverExt<T> for channel::Receiver<T> {
72+
fn blocking_recv(&mut self) -> Option<T> {
73+
self.recv_blocking().ok()
74+
}
75+
76+
fn close(&mut self) {
77+
channel::Receiver::close(self);
78+
}
79+
}

0 commit comments

Comments
 (0)