Skip to content

Commit e919aa6

Browse files
author
Jakub Konka
authored
Update to futures 0.3 and async/await syntax (#20)
* Update to futures 0.3 and async/await syntax * Bump version and fix docs * Handle Ctrl-C interrupt * Use cargo+git for testing * Abort task when Ctrl-C interrupted * Use published golem-rpc-* crates
1 parent a69f22c commit e919aa6

File tree

5 files changed

+132
-170
lines changed

5 files changed

+132
-170
lines changed

Cargo.toml

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "gwasm-api"
3-
version = "0.1.1"
3+
version = "0.2.0"
44
authors = ["Golem RnD Team <contact@golem.network>"]
55
edition = "2018"
66
license = "GPL-3.0"
@@ -11,23 +11,27 @@ documentation = "https://docs.rs/gwasm-rust-api"
1111
description = "gWasm API for Rust apps"
1212

1313
[dependencies]
14-
actix = "0.8"
15-
thiserror = "1.0"
16-
futures = "0.1"
17-
actix-wamp = "0.1"
18-
golem-rpc-api = "0.1"
19-
golem-rpc-macros = "0.1"
14+
actix = "0.9"
15+
thiserror = "1"
16+
futures = "0.3"
17+
actix-wamp = "0.2"
18+
golem-rpc-api = "0.2"
19+
golem-rpc-macros = "0.2"
2020
serde_json = "1"
2121
serde = { version = "1", features = ["derive"] }
2222
chrono = "0.4"
23-
tempfile = "3.0"
24-
tokio-ctrlc-error = "0.1"
25-
tokio = "0.1"
23+
tempfile = "3"
24+
25+
[dependencies.tokio]
26+
version = "0.2"
27+
features = [
28+
"time",
29+
"signal"
30+
]
2631

2732
[dev-dependencies]
2833
indicatif = "0.11"
29-
tempfile = "3"
30-
anyhow = "1.0"
34+
anyhow = "1"
3135

3236
[badges]
3337
maintenance = { status = "actively-developed" }

src/error.rs

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
//! Errors that can be returned by the library
22
use actix::MailboxError;
33
use std::io;
4-
use tokio::timer;
4+
5+
/// Convience wrapper for `Result` returned by fallible functions in the library
6+
pub type Result<T> = std::result::Result<T, Error>;
57

68
/// Enum wrapping all possible errors that can be generated by the library
79
#[derive(Debug, thiserror::Error)]
@@ -10,10 +12,6 @@ pub enum Error {
1012
#[error("internal gWasm API error: {0}")]
1113
MailboxError(#[from] MailboxError),
1214

13-
/// Wraps tokio's `timer::Error` error
14-
#[error("internal gWasm API error: {0}")]
15-
TimerError(#[from] timer::Error),
16-
1715
/// Wraps libstd's `std::io::Error` error
1816
#[error("internal gWasm API error: {0}")]
1917
IOError(#[from] io::Error),
@@ -26,19 +24,14 @@ pub enum Error {
2624
#[error("internal Golem error: {0}")]
2725
GolemRPCError(golem_rpc_api::Error),
2826

29-
/// Wraps `tokio_ctrlc_error::KeyboardInterrupt` which is used to handle
30-
/// Ctrl-C interrupt event for the lib's client
31-
#[error("received Ctrl-C interrupt")]
32-
KeyboardInterrupt(tokio_ctrlc_error::KeyboardInterrupt),
33-
34-
/// Wraps other `tokio_ctrlc_error::IoError` type errors
35-
#[error("internal gWasm API error: {0}")]
36-
CtrlcError(tokio_ctrlc_error::IoError),
37-
3827
/// Wraps `chrono::ParseError` error
3928
#[error("error parsing Timeout value: {0}")]
4029
ChronoError(#[from] chrono::ParseError),
4130

31+
/// Received Ctrl-C interrupt
32+
#[error("received Ctrl-C interrupt")]
33+
KeyboardInterrupt,
34+
4235
/// Error generated when trying to create a zero [`Timeout`](../timeout/struct.Timeout.html)
4336
/// value for a Golem Task
4437
#[error("zero timeout \"00:00:00\" is forbidden")]
@@ -73,15 +66,3 @@ impl From<golem_rpc_api::Error> for Error {
7366
Error::GolemRPCError(err)
7467
}
7568
}
76-
77-
impl From<tokio_ctrlc_error::IoError> for Error {
78-
fn from(err: tokio_ctrlc_error::IoError) -> Self {
79-
Error::CtrlcError(err)
80-
}
81-
}
82-
83-
impl From<tokio_ctrlc_error::KeyboardInterrupt> for Error {
84-
fn from(err: tokio_ctrlc_error::KeyboardInterrupt) -> Self {
85-
Error::KeyboardInterrupt(err)
86-
}
87-
}

src/golem.rs

Lines changed: 86 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,22 @@
11
//! Convenience async functions for creating gWasm tasks, connecting to a
22
//! Golem instance, and listening for task's progress as it's computed
33
//! on Golem.
4-
use super::{
5-
error::Error,
6-
task::{ComputedTask, Task},
7-
Net, ProgressUpdate,
8-
};
4+
use super::error::{Error, Result};
5+
use super::task::{ComputedTask, Task};
6+
use super::{Net, ProgressUpdate};
97
use actix::{Actor, ActorContext, Context, Handler, Message};
108
use actix_wamp::RpcEndpoint;
11-
use futures::{
12-
future,
13-
stream::{self, Stream},
14-
Future,
15-
};
16-
use golem_rpc_api::{
17-
comp::{AsGolemComp, TaskStatus as GolemTaskStatus},
18-
connect_to_app,
19-
};
9+
use futures::future::FutureExt;
10+
use futures::stream::{self, Stream, StreamExt, TryStreamExt};
11+
use futures::{pin_mut, select};
12+
use golem_rpc_api::comp::{AsGolemComp, TaskStatus as GolemTaskStatus};
13+
use golem_rpc_api::connect_to_app;
2014
use serde_json::json;
21-
use std::{convert::TryInto, path::Path, time::Duration};
22-
use tokio::timer::Interval;
23-
use tokio_ctrlc_error::AsyncCtrlc;
15+
use std::convert::TryInto;
16+
use std::path::{Path, PathBuf};
17+
use std::pin::Pin;
18+
use std::time::Duration;
19+
use tokio::{signal, time};
2420

2521
/// A convenience function for running a gWasm [`Task`] on Golem
2622
///
@@ -34,46 +30,51 @@ use tokio_ctrlc_error::AsyncCtrlc;
3430
/// [`Task`]: ../task/struct.Task.html
3531
/// [`ComputedTask`]: ../task/struct.ComputedTask.html
3632
/// [`gwasm_api::compute`]: ../fn.compute.html
37-
pub fn compute<P, S>(
33+
pub async fn compute<P, S>(
3834
datadir: P,
3935
address: S,
4036
port: u16,
4137
task: Task,
4238
net: Net,
4339
progress_handler: impl ProgressUpdate + 'static,
4440
polling_interval: Option<Duration>,
45-
) -> impl Future<Item = ComputedTask, Error = Error> + 'static
41+
) -> Result<ComputedTask>
4642
where
47-
P: AsRef<Path>,
48-
S: AsRef<str>,
43+
P: Into<PathBuf>,
44+
S: Into<String>,
4945
{
50-
create_task(datadir.as_ref(), address.as_ref(), port, net, task.clone())
51-
.and_then(move |(endpoint, task_id)| {
52-
poll_task_progress(endpoint.clone(), task_id.clone(), polling_interval)
53-
.fold(
54-
ProgressActor::new(progress_handler).start(),
55-
|addr, task_status| {
56-
addr.send(Update {
57-
progress: task_status.progress,
58-
})
59-
.and_then(|_| Ok(addr))
60-
},
61-
)
62-
.and_then(|addr| addr.send(Finish).map_err(Error::from))
63-
.ctrlc_as_error()
64-
.or_else(move |e: Error| match e {
65-
Error::KeyboardInterrupt(e) => {
66-
future::Either::A(endpoint.as_golem_comp().abort_task(task_id).then(
67-
|res| match res {
68-
Ok(()) => future::err(Error::KeyboardInterrupt(e)),
69-
Err(e) => future::err(e.into()),
70-
},
71-
))
72-
}
73-
e => future::Either::B(future::err(e)),
46+
let (endpoint, task_id) =
47+
create_task(&datadir.into(), &address.into(), port, net, task.clone()).await?;
48+
let poll_stream = poll_task_progress(endpoint.clone(), task_id.clone(), polling_interval);
49+
let progress = poll_stream
50+
.try_fold(
51+
ProgressActor::new(progress_handler).start(),
52+
|addr, task_status| async move {
53+
addr.send(Update {
54+
progress: task_status.progress,
7455
})
75-
})
76-
.and_then(|()| task.try_into())
56+
.await?;
57+
Ok(addr)
58+
},
59+
)
60+
.fuse();
61+
let ctrlc = signal::ctrl_c().fuse();
62+
63+
pin_mut!(ctrlc, progress);
64+
65+
select! {
66+
maybe_ctrlc = ctrlc => {
67+
maybe_ctrlc?;
68+
endpoint.as_golem_comp().abort_task(task_id).await?;
69+
Err(Error::KeyboardInterrupt)
70+
}
71+
maybe_addr = progress => {
72+
let addr = maybe_addr?;
73+
addr.send(Finish).await?;
74+
let task: ComputedTask = task.try_into()?;
75+
Ok(task)
76+
}
77+
}
7778
}
7879

7980
/// A convenience function for creating a gWasm [`Task`] on Golem
@@ -84,21 +85,16 @@ where
8485
/// [`Task`]: ../task/struct.Task.html
8586
/// [`RpcEndpoint`]:
8687
/// https://golemfactory.github.io/golem-client/latest/actix_wamp/trait.RpcEndpoint.html
87-
pub fn create_task(
88+
pub async fn create_task(
8889
datadir: &Path,
8990
address: &str,
9091
port: u16,
9192
net: Net,
9293
task: Task,
93-
) -> impl Future<Item = (impl Clone + Send + RpcEndpoint, String), Error = Error> + 'static {
94-
connect_to_app(datadir, Some(net), Some((address, port)))
95-
.and_then(move |endpoint| {
96-
endpoint
97-
.as_golem_comp()
98-
.create_task(json!(task))
99-
.map(|task_id| (endpoint, task_id))
100-
})
101-
.from_err()
94+
) -> Result<(impl Clone + Send + RpcEndpoint, String)> {
95+
let endpoint = connect_to_app(datadir, Some(net), Some((address, port))).await?;
96+
let task_id = endpoint.as_golem_comp().create_task(json!(task)).await?;
97+
Ok((endpoint, task_id))
10298
}
10399

104100
/// A convenience function for polling gWasm [`Task`]'s computation progress on Golem
@@ -114,72 +110,60 @@ pub fn poll_task_progress(
114110
endpoint: impl Clone + Send + RpcEndpoint + 'static,
115111
task_id: String,
116112
polling_interval: Option<Duration>,
117-
) -> impl Stream<Item = TaskStatus, Error = Error> + 'static {
118-
stream::unfold(TaskState::new(endpoint, task_id), |state| {
113+
) -> impl Stream<Item = Result<TaskStatus>> {
114+
stream::try_unfold(TaskState::new(endpoint, task_id), |state| async move {
119115
if let Some(status) = state.task_status.status {
120116
match status {
121-
GolemTaskStatus::Finished => return None,
122-
GolemTaskStatus::Aborted => {
123-
return Some(future::Either::A(future::err(Error::TaskAborted)))
124-
}
125-
GolemTaskStatus::Timeout => {
126-
return Some(future::Either::A(future::err(Error::TaskTimedOut)))
127-
}
117+
GolemTaskStatus::Finished => return Ok(None),
118+
GolemTaskStatus::Aborted => return Err(Error::TaskAborted),
119+
GolemTaskStatus::Timeout => return Err(Error::TaskTimedOut),
128120
_ => {}
129121
}
130122
}
131123

132124
let mut next_state = TaskState::new(state.endpoint.clone(), state.task_id.clone());
133-
Some(future::Either::B(
134-
state
135-
.endpoint
136-
.as_golem_comp()
137-
.get_task(state.task_id.clone())
138-
.map_err(Error::from)
139-
.and_then(move |task_info| {
140-
let task_info = task_info.ok_or(Error::EmptyTaskInfo)?;
141-
next_state.task_status.status = Some(task_info.status);
142-
next_state.task_status.progress =
143-
task_info.progress.ok_or(Error::EmptyProgress)?;
144-
Ok((next_state.task_status.clone(), next_state))
145-
}),
146-
))
125+
let task_info = state
126+
.endpoint
127+
.as_golem_comp()
128+
.get_task(state.task_id.clone())
129+
.await?;
130+
let task_info = task_info.ok_or(Error::EmptyTaskInfo)?;
131+
next_state.task_status.status = Some(task_info.status);
132+
next_state.task_status.progress = task_info.progress.ok_or(Error::EmptyProgress)?;
133+
Ok(Some((next_state.task_status.clone(), next_state)))
147134
})
148-
.zip(
149-
Interval::new_interval(polling_interval.unwrap_or_else(|| Duration::from_secs(2)))
150-
.from_err(),
151-
)
135+
.zip(time::interval(
136+
polling_interval.unwrap_or_else(|| Duration::from_secs(2)),
137+
))
152138
.map(|(x, _)| x)
153139
}
154140

155-
#[derive(Message)]
156141
struct Update {
157142
progress: f64,
158143
}
159144

160-
#[derive(Message)]
145+
impl Message for Update {
146+
type Result = ();
147+
}
148+
161149
struct Finish;
162150

163-
struct ProgressActor<T>
164-
where
165-
T: ProgressUpdate + 'static,
166-
{
167-
handler: T,
151+
impl Message for Finish {
152+
type Result = ();
168153
}
169154

170-
impl<T> ProgressActor<T>
171-
where
172-
T: ProgressUpdate + 'static,
173-
{
174-
fn new(handler: T) -> Self {
155+
struct ProgressActor {
156+
handler: Pin<Box<dyn ProgressUpdate>>,
157+
}
158+
159+
impl ProgressActor {
160+
fn new<T: ProgressUpdate + 'static>(handler: T) -> Self {
161+
let handler = Box::pin(handler);
175162
Self { handler }
176163
}
177164
}
178165

179-
impl<T> Actor for ProgressActor<T>
180-
where
181-
T: ProgressUpdate + 'static,
182-
{
166+
impl Actor for ProgressActor {
183167
type Context = Context<Self>;
184168

185169
fn started(&mut self, _ctx: &mut Self::Context) {
@@ -191,21 +175,15 @@ where
191175
}
192176
}
193177

194-
impl<T> Handler<Update> for ProgressActor<T>
195-
where
196-
T: ProgressUpdate + 'static,
197-
{
178+
impl Handler<Update> for ProgressActor {
198179
type Result = ();
199180

200181
fn handle(&mut self, msg: Update, _ctx: &mut Self::Context) -> Self::Result {
201182
self.handler.update(msg.progress);
202183
}
203184
}
204185

205-
impl<T> Handler<Finish> for ProgressActor<T>
206-
where
207-
T: ProgressUpdate + 'static,
208-
{
186+
impl Handler<Finish> for ProgressActor {
209187
type Result = ();
210188

211189
fn handle(&mut self, _msg: Finish, ctx: &mut Self::Context) -> Self::Result {

0 commit comments

Comments
 (0)