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

feat(docs): Add routeguide tutorial #21

Merged
merged 35 commits into from
Oct 18, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
28e66ef
started routeguide tutorial
alce Sep 30, 2019
2459ff6
remove original go code
alce Sep 30, 2019
4d7f143
Apply suggestions from code review
alce Oct 1, 2019
87d5ef3
update urls, make capitalization consistent
alce Oct 1, 2019
7007797
Merge remote-tracking branch 'upstream/master' into tutorial
alce Oct 1, 2019
d11d7b6
pin all alpha dependencies
alce Oct 1, 2019
cb84a13
Merge branch 'master' of github.com:LucioFranco/tonic
alce Oct 1, 2019
824043e
Merge branch 'master' into tutorial
alce Oct 1, 2019
2883d80
some method implementations
alce Oct 1, 2019
ea649a6
spurious commit, testing CI
alce Oct 1, 2019
e3ea43c
spurious 2
alce Oct 1, 2019
481c3a9
Merge branch 'master' of github.com:hyperium/tonic
alce Oct 2, 2019
0d676f4
Merge branch 'master' into tutorial
alce Oct 2, 2019
d6d2efd
server state
alce Oct 2, 2019
14a4e13
delete repeted lines, wording
alce Oct 2, 2019
bbb8561
improve route_chat, record_route and list_features
alce Oct 2, 2019
a000abd
a little love for the client
alce Oct 2, 2019
e617bef
cleanup stubbed content
alce Oct 2, 2019
1e47dfc
simplify loading features from json file
alce Oct 2, 2019
c3d598c
server section should be ready for review
alce Oct 2, 2019
12bf715
minor tweaks, wording
alce Oct 3, 2019
5e6d717
client setup
alce Oct 3, 2019
b018273
example: implement client list_features
alce Oct 3, 2019
1ad36b1
tweaks to server, a bit more client
alce Oct 5, 2019
6979497
typo
alce Oct 5, 2019
ea666ff
tutorial ready for review
alce Oct 5, 2019
6a452ca
typo
alce Oct 5, 2019
920426f
server section tweaks
alce Oct 6, 2019
460e82a
client tweaks
alce Oct 6, 2019
e0d558b
include server side `use`
alce Oct 6, 2019
f8a976b
upgrade tonic to alpha.2
alce Oct 8, 2019
193c35a
move notes map out of shared state
alce Oct 13, 2019
ddf33da
address blgBV's comments
alce Oct 14, 2019
e03408a
Merge remote-tracking branch 'origin/master' into tutorial
LucioFranco Oct 18, 2019
bf7428c
Update route guide tutorial to track latest master
LucioFranco Oct 18, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
move notes map out of shared state
  • Loading branch information
alce committed Oct 13, 2019
commit 193c35af66ad1b6946a400f47b4246b27a354b1e
55 changes: 15 additions & 40 deletions tonic-examples/routeguide-tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,10 @@ serde_json = "1.0"
prost = "0.5"
rand = "0.7.2"
tokio = "0.2.0-alpha.6"
tonic = "0.1.0-alpha.2"
tonic = "0.1.0-alpha.3"

[build-dependencies]
tonic-build = "0.1.0-alpha.2"
tonic-build = "0.1.0-alpha.3"
```

Create a `build.rs` file at the root of your crate:
Expand Down Expand Up @@ -310,37 +310,17 @@ uses [async-trait] internally. You can learn more about `async fn` in traits in
[async book]: https://rust-lang.github.io/async-book/07_workarounds/06_async_in_traits.html

### Server state
There are two pieces of state our service needs to access: an immutable list of features and a
mutable map from points to route notes.

When designing our state shape, we must consider that our server will run in a multi-threaded Tokio
executor and that the `server::RouteGuide` trait has `Send + Sync + 'static` bounds.

This in one way we can represent our state:

```rust
use tokio::sync::Mutex;
use std::sync::Arc;
use std::collections::HashMap;
```
Our service needs access to an immutable list of features. When the server starts, we are going to
deserialize them from a json file and keep them around as our only piece of shared state:

```rust
#[derive(Debug)]
pub struct RouteGuide {
state: State,
}

#[derive(Debug, Clone)]
struct State {
features: Arc<Vec<Feature>>,
notes: Arc<Mutex<HashMap<Point, Vec<RouteNote>>>>,
}
```

**Note:** we are using `tokio::sync::Mutex` here, not `std::sync::Mutex`.

When our server boots, we are going to deserialize our features vector from a json file.
Create the data file and a helper module to read and deserialize our features.
Create the json data file and a helper module to read and deserialize our features.

```shell
$ mkdir data && touch data/route_guide_db.json
Expand Down Expand Up @@ -398,7 +378,7 @@ an empty one.

```rust
async fn get_feature(&self, request: Request<Point>) -> Result<Response<Feature>, Status> {
for feature in &self.state.features[..] {
for feature in &self.features[..] {
if feature.location.as_ref() == Some(request.get_ref()) {
return Ok(Response::new(feature.clone()));
}
Expand Down Expand Up @@ -426,11 +406,10 @@ async fn list_features(
request: Request<Rectangle>,
) -> Result<Response<Self::ListFeaturesStream>, Status> {
let (mut tx, rx) = mpsc::channel(4);

let state = self.state.clone();
let features = self.features.clone();

tokio::spawn(async move {
Copy link
Contributor

Choose a reason for hiding this comment

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

This would be a great way to use the async-stream crate right? Maybe mention it here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

async-stream is used on the bidirectional streaming example and we talk abut it briefly. Do you think we should use it here too or explain in a bit more detail what it does?

for feature in &state.features[..] {
for feature in &features[..] {
if in_range(feature.location.as_ref().unwrap(), request.get_ref()) {
tx.send(Ok(feature.clone())).await.unwrap();
}
Expand Down Expand Up @@ -476,7 +455,7 @@ async fn record_route(
let point = point?;
summary.point_count += 1;

for feature in &self.state.features[..] {
for feature in &self.features[..] {
if feature.location.as_ref() == Some(&point) {
summary.feature_count += 1;
}
Expand Down Expand Up @@ -511,9 +490,9 @@ async fn route_chat(
&self,
request: Request<tonic::Streaming<RouteNote>>,
) -> Result<Response<Self::RouteChatStream>, Status> {
let mut notes = HashMap::new();
let stream = request.into_inner();
let mut state = self.state.clone();


let output = async_stream::try_stream! {
futures::pin_mut!(stream);

Expand All @@ -522,11 +501,10 @@ async fn route_chat(

let location = note.location.clone().unwrap();

let mut notes = state.notes.lock().await;
let notes = notes.entry(location).or_insert(vec![]);
notes.push(note);
let location_notes = notes.entry(location).or_insert(vec![]);
location_notes.push(note);

for note in notes {
for note in location_notes {
yield note.clone();
}
}
Expand Down Expand Up @@ -566,10 +544,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "[::1]:10000".parse().unwrap();

let route_guide = RouteGuide {
state: State {
features: Arc::new(data::load()),
notes: Arc::new(Mutex::new(HashMap::new())),
},
features: Arc::new(data::load()),
};

let svc = server::RouteGuideServer::new(route_guide);
Expand Down
32 changes: 10 additions & 22 deletions tonic-examples/src/routeguide/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::hash::{Hash, Hasher};
use std::pin::Pin;
use std::sync::Arc;
use std::time::Instant;
use tokio::sync::{mpsc, Mutex};
use tokio::sync::mpsc;
use tonic::transport::Server;
use tonic::{Request, Response, Status};

Expand All @@ -18,21 +18,15 @@ use routeguide::{server, Feature, Point, Rectangle, RouteNote, RouteSummary};

#[derive(Debug)]
pub struct RouteGuide {
state: State,
}

#[derive(Debug, Clone)]
struct State {
features: Arc<Vec<Feature>>,
notes: Arc<Mutex<HashMap<Point, Vec<RouteNote>>>>,
}

#[tonic::async_trait]
impl server::RouteGuide for RouteGuide {
async fn get_feature(&self, request: Request<Point>) -> Result<Response<Feature>, Status> {
println!("GetFeature = {:?}", request);

for feature in &self.state.features[..] {
for feature in &self.features[..] {
if feature.location.as_ref() == Some(request.get_ref()) {
return Ok(Response::new(feature.clone()));
}
Expand All @@ -55,11 +49,10 @@ impl server::RouteGuide for RouteGuide {
println!("ListFeatures = {:?}", request);

let (mut tx, rx) = mpsc::channel(4);

let state = self.state.clone();
let features = self.features.clone();

tokio::spawn(async move {
for feature in &state.features[..] {
for feature in &features[..] {
if in_range(feature.location.as_ref().unwrap(), request.get_ref()) {
println!(" => send {:?}", feature);
tx.send(Ok(feature.clone())).await.unwrap();
Expand Down Expand Up @@ -96,7 +89,7 @@ impl server::RouteGuide for RouteGuide {
summary.point_count += 1;

// Find features
for feature in &self.state.features[..] {
for feature in &self.features[..] {
if feature.location.as_ref() == Some(&point) {
summary.feature_count += 1;
}
Expand All @@ -123,8 +116,8 @@ impl server::RouteGuide for RouteGuide {
) -> Result<Response<Self::RouteChatStream>, Status> {
println!("RouteChat");

let mut notes = HashMap::new();
let stream = request.into_inner();
let state = self.state.clone();

let output = async_stream::try_stream! {
futures::pin_mut!(stream);
Expand All @@ -134,11 +127,10 @@ impl server::RouteGuide for RouteGuide {

let location = note.location.clone().unwrap();

let mut notes = state.notes.lock().await;
let notes = notes.entry(location).or_insert(vec![]);
notes.push(note);
let location_notes = notes.entry(location).or_insert(vec![]);
location_notes.push(note);

for note in notes {
for note in location_notes {
yield note.clone();
}
}
Expand All @@ -158,11 +150,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Listening on: {}", addr);

let route_guide = RouteGuide {
state: State {
// Load data file
features: Arc::new(data::load()),
notes: Arc::new(Mutex::new(HashMap::new())),
},
features: Arc::new(data::load()),
};

let svc = server::RouteGuideServer::new(route_guide);
Expand Down