Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an async interface using futures-rs #141

Merged
merged 8 commits into from Jun 26, 2018
Merged

Conversation

Marwes
Copy link
Collaborator

@Marwes Marwes commented Jan 22, 2018

This adds support for communicating with redis instances asynchronously using futures and tokio. Putting this up now to show the changes and additions I have done so far.

Since the current response parser is tied to reading synchronously from a Read instance I rewrote it using https://github.com/Marwes/combine. I choose combine since I know it (obviously) and I wanted to try the state machine support I have been working on in tandem with this PR (this PR is what made me come up with the idea). Though I am a bit biased I do think it should be a good fit after I have worked out the bugs so I hope the change is acceptable. (I did see the Nom parser in #129 but didn't use it as I'd still need to code the state machine manually whereas combine creates it automatically).

query(_async) is currently the only added API and there is also a bunch of cleanup to be done.

Closes #103

@Marwes Marwes force-pushed the async branch 5 times, most recently from d7fe429 to c9fba9e Compare January 30, 2018 11:32
@Marwes Marwes changed the title [WIP] Add an async interface using futures-rs Add an async interface using futures-rs Jan 31, 2018
@Marwes
Copy link
Collaborator Author

Marwes commented Jan 31, 2018

This should be "done" now. Only the basic query_async functions were added but everything else could be done independently of that.

@Marwes Marwes closed this Jan 31, 2018
@Marwes Marwes reopened this Jan 31, 2018
@softprops
Copy link

Awesome. So happy to see this moving forward

@mitsuhiko
Copy link
Contributor

Thanks! I will be playing with this.

@mitsuhiko
Copy link
Contributor

Really nice. This makes me very happy :) I would merge it as such and then fix up some smaller things such as missing documentation on master. I would not waste too much time on that yet in case it turns out we need to tweak the API more.

Would just need a release of combine before I can merge this.

Copy link
Contributor

@mitsuhiko mitsuhiko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only thing that needs changing before merging from my part is that combine points to a release.

@badboy
Copy link
Collaborator

badboy commented Feb 2, 2018

👍 so good to see work here happening. I’ll look over the code today as well.

pub trait ConnectionLike: Sized {
/// Sends an already encoded (packed) command into the TCP socket and
/// reads the single response from it.
fn req_packed_command(self, cmd: Vec<u8>) -> RedisFuture<(Self, Value)>;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may have stuck a bit to close to the synchronous versions by using this pattern actually. I will be making a separate PR (building on this one) hopefully today where the Connection allows multiple commands to be in flight concurrently without needing an explicit pipeline. The trade off is that it won't be able to handle explicitly built pipelines (at least not initially) and there is some additional overhead due to needing Arc/Rc.

match pieces.next() {
Some(detail) => fail!((kind, desc, detail.to_string())),
None => fail!((kind, desc)),
match parser.poll()? {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should mention that instead of calling the asynchronous parsing here, we could parse directly from the Read instance. The only drawback is that the parser needs to be instantiated twice (once for I == PartialStream<easy::Stream<&[u8]>> and once for I == ReadStream<R>.

keys: vec![],
}
.invoke(con)
script: self,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the churn caused by accidentally having rustfmt on. I could revert some of the changes If you want but I fear the files I modified may have to stick. (Would be nice to run through rustfmt on the whole repo though in my opinion).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Marwes i'm fine enforcing rustfmt. I tried to do it in the past but it completely broke with some of the macros at the point. Need to reinvestigate this.

@Marwes
Copy link
Collaborator Author

Marwes commented Feb 2, 2018

Would just need a release of combine before I can merge this.

I should release 3.0.0-beta.1 (beta == only minor breaking changes planned) later today unless I get caught up in something and don't have time.

@@ -622,3 +621,243 @@ pub fn transaction<K: ToRedisArgs,
}
}
}

pub mod async {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file is already quite long and here we have another module.
IMO this should be extracted into its own file here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wanted to say. Also makes reading the diff hard because the types have the same name :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could change to AsyncConnection etc but I feel it is more idiomatic to write async::Connection to avoid ambiguities in that case.

src/lib.rs Outdated
pub use types::{/* utility functions */ from_redis_value, /* error kinds */ ErrorKind,
/* conversion traits */ FromRedisValue, /* utility types */ InfoDict,
NumericBehavior, /* error and result types */ RedisError, RedisResult,
ToRedisArgs, /* low level values */ Value, RedisFuture};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This auto-formatted change makes it both harder to read and to change.
IMO we should keep the old formatting and mark it so rustfmt does not touch it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@@ -0,0 +1,236 @@
use std::fmt::Arguments;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to have connection::sync and connection::async modules?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As long as we still export connection::sync::Connection (and other types from that module) on the root module, we could split it up and not break existing usage.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@badboy we never exposed modules anyways. Only the toplevel thing.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mitsuhiko Exactly, that's why I just wanted to mention that we re-export it to keep it working

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left it under src/async.rs since I would need to re-export it as async under the root anyway.

@gsquire
Copy link

gsquire commented Feb 2, 2018

I guess I can close my nom attempt which is a good thing!

@badboy
Copy link
Collaborator

badboy commented Feb 3, 2018

@gsquire Thanks for your effort anyway!

@Marwes
Copy link
Collaborator Author

Marwes commented Feb 4, 2018

Released a new version of combine and updated this PR to point to it https://crates.io/crates/combine

@Marwes
Copy link
Collaborator Author

Marwes commented Feb 12, 2018

Is there anything blocking this from being merged?

@badboy
Copy link
Collaborator

badboy commented Feb 12, 2018

IMO 2 things:

  • Documentation. We have usage examples in the root for the sync interface. Now that we have an async module, we should put some examples on how to use it there
  • Stable release of combine v3. I don't think it would be good to rely on the beta release.

@Marwes
Copy link
Collaborator Author

Marwes commented May 24, 2018

Did a rebase and squash since most of the commits won't compile due to depending on dead git branches. Still lacking unix stream support due to tokio-uds not having a new release but that can be added in without a breaking release at a later point.

@Marwes
Copy link
Collaborator Author

Marwes commented May 28, 2018

I believe all remaining issues have been resolved(?) (pointing combine to a real 3.0 release was the last one). cc @mitsuhiko @badboy

@jwilm
Copy link
Collaborator

jwilm commented May 29, 2018

Did a rebase and squash since most of the commits won't compile due to depending on dead git branches. Still lacking unix stream support due to tokio-uds not having a new release but that can be added in without a breaking release at a later point.

I haven't been following closely, but does that mean that Unix stream support is broken for the sync interface as well? We rely on this in our production environment and would need to pin to an earlier commit.

@Marwes
Copy link
Collaborator Author

Marwes commented May 29, 2018

I haven't been following closely, but does that mean that Unix stream support is broken for the sync interface as well? We rely on this in our production environment and would need to pin to an earlier commit.

No, that only affects the async interface (they are largely separate implementations in that respect)

@jwilm
Copy link
Collaborator

jwilm commented May 29, 2018

No, that only affects the async interface (they are largely separate implementations in that respect)

👍 Thanks for clearing that up. I recall some discussion around having the implementations be unified, and it's good to hear there won't be any breaking changes due to this feature on the sync side. At least, until they unify :).

@Marwes
Copy link
Collaborator Author

Marwes commented Jun 5, 2018

Bump (@mitsuhiko @badboy )

@Marwes
Copy link
Collaborator Author

Marwes commented Jun 18, 2018

Ping @mitsuhiko and @badboy . Anything I can do to help get this and #143 merged?

@badboy
Copy link
Collaborator

badboy commented Jun 18, 2018

I'm just back from a work week. I'll put this on my schedule for the week.

Copy link
Collaborator

@badboy badboy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job!
The rustfmt made it a bit harder to review, but I'm fine with it being done now and not having to deal with it later.
@mitsuhiko's "Request changes" should be resolved.

Some small things remaining and one last question:

Is it likely that we can switch away from a boxed future in the future (pun intended) by using -> impl X? (No need to do it here now)

Furthermore, we should at least run cargo bench --no-run on CI with nightly to ensure the code actually compiles. That can be done in another bug.

Cargo.toml Outdated
@@ -19,18 +19,29 @@ build = "build.rs"
[features]
with-rustc-json = ["rustc-serialize"]
with-unix-sockets = ["unix_socket"]
# with-async-unix-sockets = ["tokio-uds"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this comment.

Cargo.toml Outdated
tokio-reactor = "0.1"
tokio-tcp = "0.1"
tokio-io = "0.1"
# tokio-uds = { git = "https://github.com/Marwes/tokio-uds", branch = "only_tokio_upgrade", optional = true }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also this comment.

extern crate tokio_core;

use futures::Future;
use tokio_core::reactor::Core;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tokio_core is not in the dependencies anymore. This whole benchmark needs a rewrite.

fn bench_simple_getsetdel_async(b: &mut Bencher) {
let client = get_client();
let mut core = Core::new().unwrap();
let con = client.get_async_connection(&core.handle());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_async_connection doesn't take a handle anymore, so I guess this all needs to change.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed (and I enabled cargo check for travis)

bench_simple_getsetdel_pipeline,
bench_simple_getsetdel_pipeline_precreated,
bench_long_pipeline,
bench_encode_pipeline,
bench_encode_pipeline_nested
bench_encode_pipeline_nested,
bench_long_pipeline
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now duplicated.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

src/async.rs Outdated
.map(|con| ActualConnection::Tcp(BufReader::new(con))),
)
}
#[cfg(feature = "with-async-unix-sockets")]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's weird seeing this feature, when it's not available in Cargo.toml.
What do you think about extracting the async-unix-socket code from this PR, but immediately opening one adding it, pending the updated tokio-uds crate?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tokio-uds actually got a release so I removed the feature and enable tokio-uds support with the normal uds feature https://crates.io/crates/tokio-uds

src/parser.rs Outdated
Some(detail) => fail!((kind, desc, detail.to_string())),
None => fail!((kind, desc)),
match parser.poll()? {
Async::NotReady => panic!("Blocking read received WouldBlock error"),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing a panic makes me kinda worry:
Is it likely that code is ever hit? Is it an actual error that can't be handled at all?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed it to return WouldBlock again which is the only reason why it could happen.

extern crate futures;
extern crate partial_io;
#[macro_use]
extern crate quickcheck;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yey for seeing quickcheck used here.

@Marwes
Copy link
Collaborator Author

Marwes commented Jun 25, 2018

The rustfmt made it a bit harder to review, but I'm fine with it being done now and not having to deal with it later.

Sorry about that. I think I had it split up before but I ended up with a nasty rebase and had to remove it to make it easier. Tried to break it up into two commits again but looked pretty nasty still.

Is it likely that we can switch away from a boxed future in the future (pun intended) by using -> impl X? (No need to do it here now)

Not in the trait methods which return a future, but in all the other functions then certainly.

Furthermore, we should at least run cargo bench --no-run on CI with nightly to ensure the code actually compiles. That can be done in another bug.

Did it as cargo check --benches which I think should do basically the same but might be a bit faster.

@Marwes
Copy link
Collaborator Author

Marwes commented Jun 25, 2018

@mitsuhiko's "Request changes" should be resolved.

I think I resolved everything (can't find anything I missed at least)?

@badboy badboy dismissed mitsuhiko’s stale review June 26, 2018 09:13

Now uses combine v3.0.0 (stable release)

@badboy
Copy link
Collaborator

badboy commented Jun 26, 2018

Hmmm, the tests fail on Windows:

	thread 'test_args' panicked at 'called `Result::unwrap()` on an `Err` value: An invalid argument was supplied. (os error 10022)', libcore\result.rs:945:5

	thread 'test_pipeline_transaction' panicked at 'called `Result::unwrap()` on an `Err` value: An invalid argument was supplied. (os error 10022)', libcore\result.rs:945:5

@Marwes
Copy link
Collaborator Author

Marwes commented Jun 26, 2018

Kicking of a println test on appveyor to see if it is an easy fix, otherwise I will try and get this work locally on windows tonight.

@badboy
Copy link
Collaborator

badboy commented Jun 26, 2018

@Marwes Just ping me again if this is resolved. I'm happy to merge it then :)

These are rarely necessary and should hopefully fix the issue on windows
since a rather weird use of net2 could be removed
@Marwes
Copy link
Collaborator Author

Marwes commented Jun 26, 2018

@badboy I made some mistake when using the net2 crate to connect to tcp using an explicit tokio handle. Since an explicit handle should rarely (if ever) be necessary I just removed the use of an explicit handle which made it work.

If someone ends up needing a function that takes an explicit handle we can revisit this and try to implement it then instead.

Everything is green now!

@badboy
Copy link
Collaborator

badboy commented Jun 26, 2018

Great! Merging it now!

@badboy badboy merged commit 0d6c726 into redis-rs:master Jun 26, 2018
@Marwes Marwes deleted the async branch June 26, 2018 12:37
@Terkwood
Copy link
Contributor

Great work! Looking forward to trying this out!

@badboy badboy mentioned this pull request Jan 28, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants