🐛 Fix rendezvous problem.

Now clients can queue up for a server, which fixes a few things:

- A server can receive multiple requests at once, reducing roundtrip
count in theory
- Clients can wait up to 30 seconds on the relay before the server
is ready for them
- If the server has just left to service a request, the client will
queue instead of seeing the server as absent and giving up
main
_ 2020-11-01 20:07:46 -06:00
parent 067e240ff4
commit e7edf84282
5 changed files with 200 additions and 160 deletions

View File

@ -12,6 +12,7 @@ license = "AGPL-3.0"
base64 = "0.12.3" base64 = "0.12.3"
blake3 = "0.3.7" blake3 = "0.3.7"
dashmap = "3.11.10"
futures = "0.3.7" futures = "0.3.7"
handlebars = "3.5.1" handlebars = "3.5.1"
http = "0.2.1" http = "0.2.1"

View File

@ -15,6 +15,10 @@ pub mod server;
#[cfg (test)] #[cfg (test)]
mod tests { mod tests {
use std::{
sync::Arc,
};
use tokio::{ use tokio::{
runtime::Runtime, runtime::Runtime,
spawn, spawn,
@ -35,12 +39,16 @@ mod tests {
rt.block_on (async { rt.block_on (async {
let relay_url = "http://127.0.0.1:4000"; let relay_url = "http://127.0.0.1:4000";
spawn (async { let relay_state = Arc::new (relay::RelayState::default ());
relay::main ().await.unwrap ();
let relay_state_2 = relay_state.clone ();
spawn (async move {
relay::run_relay (relay_state_2).await.unwrap ();
}); });
let relay_url_2 = relay_url.into (); assert! (relay_state.list_servers ().await.is_empty ());
let relay_url_2 = relay_url.into ();
let server_name = "alien_wildlands"; let server_name = "alien_wildlands";
let server_name_2 = server_name.into (); let server_name_2 = server_name.into ();
@ -54,9 +62,15 @@ mod tests {
server::main (opt).await.unwrap (); server::main (opt).await.unwrap ();
}); });
tokio::time::delay_for (std::time::Duration::from_secs (1)).await; tokio::time::delay_for (std::time::Duration::from_millis (500)).await;
let client = Client::new (); assert_eq! (relay_state.list_servers ().await, vec! [
server_name.to_string (),
]);
let client = Client::builder ()
.timeout (std::time::Duration::from_secs (2))
.build ().unwrap ();
let resp = client.get (&format! ("{}/relay_up_check", relay_url)) let resp = client.get (&format! ("{}/relay_up_check", relay_url))
.send ().await.unwrap ().bytes ().await.unwrap (); .send ().await.unwrap ().bytes ().await.unwrap ();

View File

@ -2,14 +2,15 @@ pub mod watcher;
use std::{ use std::{
error::Error, error::Error,
collections::*,
convert::Infallible, convert::Infallible,
net::SocketAddr, net::SocketAddr,
sync::{ sync::{
Arc Arc
}, },
time::{Duration},
}; };
use dashmap::DashMap;
use futures::channel::oneshot; use futures::channel::oneshot;
use handlebars::Handlebars; use handlebars::Handlebars;
use hyper::{ use hyper::{
@ -24,27 +25,74 @@ use hyper::service::{make_service_fn, service_fn};
use serde::Serialize; use serde::Serialize;
use tokio::{ use tokio::{
sync::Mutex, sync::Mutex,
time::delay_for,
}; };
use crate::{ use crate::{
http_serde, http_serde,
}; };
use watcher::*;
#[derive (Default)] /*
struct ServerState {
Here's what we need to handle:
When a request comes in:
- Park the client in response_rendezvous
- Look up the server ID in request_rendezvous
- If a server is parked, unpark it and send the request
- Otherwise, queue the request
When a server comes to listen:
- Look up the server ID in request_rendezvous
- Either return all pending requests, or park the server
When a server comes to respond:
- Look up the parked client in response_rendezvous
- Unpark the client and begin streaming
So we need these lookups to be fast:
- Server IDs, where (1 server) or (0 or many clients)
can be parked
- Request IDs, where 1 client is parked
*/
enum RequestRendezvous {
ParkedClients (Vec <http_serde::WrappedRequest>),
ParkedServer (oneshot::Sender <http_serde::WrappedRequest>),
}
type ResponseRendezvous = oneshot::Sender <(http_serde::ResponseParts, Body)>;
pub struct RelayState {
handlebars: Arc <Handlebars <'static>>, handlebars: Arc <Handlebars <'static>>,
// Holds clients that are waiting for a response to come // Key: Server ID
// back from a server. request_rendezvous: Mutex <HashMap <String, RequestRendezvous>>,
client_watchers: Arc <Mutex <Watchers <(http_serde::ResponseParts, Body)>>>, // Key: Request ID
response_rendezvous: DashMap <String, ResponseRendezvous>,
}
// Holds servers that are waiting for a request to come in impl Default for RelayState {
// from a client. fn default () -> Self {
Self {
handlebars: Arc::new (load_templates ().unwrap ()),
request_rendezvous: Default::default (),
response_rendezvous: Default::default (),
}
}
}
server_watchers: Arc <Mutex <Watchers <http_serde::WrappedRequest>>>, impl RelayState {
pub async fn list_servers (&self) -> Vec <String> {
self.request_rendezvous.lock ().await.iter ()
.map (|(k, _)| (*k).clone ())
.collect ()
}
} }
fn status_reply <B: Into <Body>> (status: StatusCode, b: B) fn status_reply <B: Into <Body>> (status: StatusCode, b: B)
@ -53,113 +101,122 @@ fn status_reply <B: Into <Body>> (status: StatusCode, b: B)
Response::builder ().status (status).body (b.into ()).unwrap () Response::builder ().status (status).body (b.into ()).unwrap ()
} }
async fn handle_http_listen (state: Arc <ServerState>, watcher_code: String) async fn handle_http_listen (state: Arc <RelayState>, watcher_code: String)
-> Response <Body> -> Response <Body>
{ {
//println! ("Step 1"); use RequestRendezvous::*;
match Watchers::long_poll (state.server_watchers.clone (), watcher_code).await {
Some (parts) => { let (tx, rx) = oneshot::channel ();
//println! ("Step 3");
status_reply (StatusCode::OK, rmp_serde::to_vec (&parts).unwrap ()) {
}, let mut request_rendezvous = state.request_rendezvous.lock ().await;
_ => status_reply (StatusCode::GATEWAY_TIMEOUT, "no\n"),
if let Some (ParkedClients (v)) = request_rendezvous.remove (&watcher_code)
{
return status_reply (StatusCode::OK, rmp_serde::to_vec (&v).unwrap ());
} }
request_rendezvous.insert (watcher_code, ParkedServer (tx));
}
let one_req = vec! [
rx.await.unwrap (),
];
return status_reply (StatusCode::OK, rmp_serde::to_vec (&one_req).unwrap ());
} }
async fn handle_http_response ( async fn handle_http_response (
req: Request <Body>, req: Request <Body>,
state: Arc <ServerState>, state: Arc <RelayState>,
req_id: String, req_id: String,
) )
-> Response <Body> -> Response <Body>
{ {
//println! ("Step 6");
let (parts, body) = req.into_parts (); let (parts, body) = req.into_parts ();
let resp_parts: http_serde::ResponseParts = rmp_serde::from_read_ref (&base64::decode (parts.headers.get (crate::PTTH_MAGIC_HEADER).unwrap ()).unwrap ()).unwrap (); let resp_parts: http_serde::ResponseParts = rmp_serde::from_read_ref (&base64::decode (parts.headers.get (crate::PTTH_MAGIC_HEADER).unwrap ()).unwrap ()).unwrap ();
{ match state.response_rendezvous.remove (&req_id) {
let mut watchers = state.client_watchers.lock ().await; Some ((_, tx)) => {
match tx.send ((resp_parts, body)) {
Ok (()) => status_reply (StatusCode::OK, "Connected to remote client...\n"),
_ => status_reply (StatusCode::BAD_GATEWAY, "Failed to connect to client"),
}
//println! ("Step 7"); },
if ! watchers.wake_one ((resp_parts, body), &req_id) None => status_reply (StatusCode::BAD_REQUEST, "Request ID not found in response_rendezvous"),
{
println! ("Step 8 (bad thing)");
status_reply (StatusCode::BAD_REQUEST, "A bad thing happened.\n")
}
else {
//println! ("Step 8");
status_reply (StatusCode::OK, "ok\n")
}
} }
} }
async fn handle_http_request ( async fn handle_http_request (
req: http::request::Parts, req: http::request::Parts,
uri: String, uri: String,
state: Arc <ServerState>, state: Arc <RelayState>,
watcher_code: String watcher_code: String
) )
-> Response <Body> -> Response <Body>
{ {
let parts = {
let id = ulid::Ulid::new ().to_string (); let id = ulid::Ulid::new ().to_string ();
let req = match http_serde::RequestParts::from_hyper (req.method, uri, req.headers) { let req = match http_serde::RequestParts::from_hyper (req.method, uri, req.headers) {
Ok (x) => x, Ok (x) => x,
_ => return status_reply (StatusCode::BAD_REQUEST, "Bad request"), _ => return status_reply (StatusCode::BAD_REQUEST, "Bad request"),
}; };
http_serde::WrappedRequest { let (tx, rx) = oneshot::channel ();
state.response_rendezvous.insert (id.clone (), tx);
{
let mut request_rendezvous = state.request_rendezvous.lock ().await;
let wrapped = http_serde::WrappedRequest {
id, id,
req, req,
}
}; };
//println! ("Step 2 {}", parts.id); use RequestRendezvous::*;
let (s, r) = oneshot::channel (); let new_rendezvous = match request_rendezvous.remove (&watcher_code) {
let timeout = Duration::from_secs (5); Some (ParkedClients (mut v)) => {
v.push (wrapped);
ParkedClients (v)
},
Some (ParkedServer (s)) => {
// If sending to the server fails, queue it
let id_2 = parts.id.clone (); match s.send (wrapped) {
{ Ok (()) => ParkedClients (vec! []),
let mut that = state.client_watchers.lock ().await; Err (wrapped) => ParkedClients (vec! [wrapped]),
that.add_watcher_with_id (s, id_2) }
},
None => ParkedClients (vec! [wrapped]),
};
request_rendezvous.insert (watcher_code, new_rendezvous);
} }
let req_id = parts.id.clone (); let timeout = tokio::time::delay_for (std::time::Duration::from_secs (30));
tokio::spawn (async move { let received = tokio::select! {
{ val = rx => val,
let mut watchers = state.server_watchers.lock ().await; () = timeout => {
return status_reply (StatusCode::GATEWAY_TIMEOUT, "Remote server never responded")
//println! ("Step 3"); },
if ! watchers.wake_one (parts, &watcher_code) { };
watchers.remove_watcher (&req_id);
}
}
delay_for (timeout).await;
{
let mut that = state.client_watchers.lock ().await;
that.remove_watcher (&req_id);
}
});
match r.await {
Ok ((resp_parts, body)) => {
//println! ("Step 7");
match received {
Ok ((parts, body)) => {
let mut resp = Response::builder () let mut resp = Response::builder ()
.status (hyper::StatusCode::from (resp_parts.status_code)); .status (hyper::StatusCode::from (parts.status_code));
for (k, v) in resp_parts.headers.into_iter () { for (k, v) in parts.headers.into_iter () {
resp = resp.header (&k, v); resp = resp.header (&k, v);
} }
resp resp.body (body)
.body (body)
.unwrap () .unwrap ()
}, },
_ => status_reply (StatusCode::GATEWAY_TIMEOUT, "server didn't reply in time or somethin'"), _ => status_reply (StatusCode::GATEWAY_TIMEOUT, "Remote server timed out"),
} }
} }
@ -173,7 +230,7 @@ fn prefix_match <'a> (hay: &'a str, needle: &str) -> Option <&'a str>
} }
} }
async fn handle_all (req: Request <Body>, state: Arc <ServerState>) async fn handle_all (req: Request <Body>, state: Arc <RelayState>)
-> Result <Response <Body>, Infallible> -> Result <Response <Body>, Infallible>
{ {
let path = req.uri ().path (); let path = req.uri ().path ();
@ -208,11 +265,7 @@ async fn handle_all (req: Request <Body>, state: Arc <ServerState>)
servers: Vec <ServerEntry <'a>>, servers: Vec <ServerEntry <'a>>,
} }
let names: Vec <_> = { let names = state.list_servers ().await;
state.server_watchers.lock ().await.senders.iter ()
.map (|(k, _)| (*k).clone ())
.collect ()
};
//println! ("Found {} servers", names.len ()); //println! ("Found {} servers", names.len ());
@ -262,17 +315,10 @@ pub fn load_templates ()
Ok (handlebars) Ok (handlebars)
} }
pub async fn main () -> Result <(), Box <dyn Error>> { pub async fn run_relay (state: Arc <RelayState>) -> Result <(), Box <dyn Error>>
{
let addr = SocketAddr::from(([0, 0, 0, 0], 4000)); let addr = SocketAddr::from(([0, 0, 0, 0], 4000));
let state = ServerState {
handlebars: Arc::new (load_templates ()?),
server_watchers: Default::default (),
client_watchers: Default::default (),
};
let state = Arc::new (state);
let make_svc = make_service_fn (|_conn| { let make_svc = make_service_fn (|_conn| {
let state = state.clone (); let state = state.clone ();
@ -292,47 +338,19 @@ pub async fn main () -> Result <(), Box <dyn Error>> {
Ok (()) Ok (())
} }
pub async fn main () -> Result <(), Box <dyn Error>> {
let state = RelayState {
handlebars: Arc::new (load_templates ()?),
request_rendezvous: Default::default (),
response_rendezvous: Default::default (),
};
let state = Arc::new (state);
run_relay (state).await
}
#[cfg (test)] #[cfg (test)]
mod tests { mod tests {
// Toy model of a relay for a single server
// with one consumer thread.
// To scale this up, we can just put a bunch into a
// concurrent hash map inside of mutexes or something
struct RelayStateMachine {
} }
enum RequestStateMachine {
WaitForServerAccept, // Client has connected
WaitForServerResponse, // Server has accepted request
}
/*
Here's what we need to handle:
When a request comes in:
- Look up the server
- If the server is parked, unpark it
- Park the client
When a server comes to listen:
- Look up the server
- Either return all pending requests, or park the server
When a server comes to respond:
- Look up the parked client
- Begin a stream, unparking the client
So we need these lookups to be fast:
- Server IDs, where 0 or 1 servers and 0 or many clients
can be parked
- Request IDs, where 1 client is parked
*/
}

View File

@ -21,7 +21,7 @@ pub mod file_server;
async fn handle_req_resp <'a> ( async fn handle_req_resp <'a> (
opt: &'a Opt, opt: &'a Opt,
handlebars: Arc <Handlebars <'static>>, handlebars: Arc <Handlebars <'static>>,
client: &'a Client, client: Arc <Client>,
req_resp: reqwest::Response req_resp: reqwest::Response
) { ) {
//println! ("Step 1"); //println! ("Step 1");
@ -32,12 +32,18 @@ async fn handle_req_resp <'a> (
} }
let body = req_resp.bytes ().await.unwrap (); let body = req_resp.bytes ().await.unwrap ();
let wrapped_req: http_serde::WrappedRequest = match rmp_serde::from_read_ref (&body) let wrapped_reqs: Vec <http_serde::WrappedRequest> = match rmp_serde::from_read_ref (&body)
{ {
Ok (x) => x, Ok (x) => x,
_ => return, _ => return,
}; };
for wrapped_req in wrapped_reqs.into_iter () {
let handlebars = handlebars.clone ();
let opt = opt.clone ();
let client = client.clone ();
tokio::spawn (async move {
let (req_id, parts) = (wrapped_req.id, wrapped_req.req); let (req_id, parts) = (wrapped_req.id, wrapped_req.req);
let response = file_server::serve_all (handlebars, &opt.file_server_root, parts).await; let response = file_server::serve_all (handlebars, &opt.file_server_root, parts).await;
@ -54,8 +60,11 @@ async fn handle_req_resp <'a> (
if let Err (e) = resp_req.send ().await { if let Err (e) = resp_req.send ().await {
println! ("Err: {:?}", e); println! ("Err: {:?}", e);
} }
});
}
} }
#[derive (Clone)]
pub struct Opt { pub struct Opt {
pub relay_url: String, pub relay_url: String,
pub server_name: String, pub server_name: String,
@ -65,7 +74,6 @@ pub struct Opt {
pub async fn main (opt: Opt) -> Result <(), Box <dyn Error>> { pub async fn main (opt: Opt) -> Result <(), Box <dyn Error>> {
let client = Arc::new (Client::new ()); let client = Arc::new (Client::new ());
let opt = Arc::new (opt); let opt = Arc::new (opt);
let handlebars = Arc::new (file_server::load_templates ()?); let handlebars = Arc::new (file_server::load_templates ()?);
let mut backoff_delay = 0; let mut backoff_delay = 0;
@ -97,7 +105,7 @@ pub async fn main (opt: Opt) -> Result <(), Box <dyn Error>> {
let handlebars = handlebars.clone (); let handlebars = handlebars.clone ();
tokio::spawn (async move { tokio::spawn (async move {
handle_req_resp (&opt, handlebars, &client, req_resp).await; handle_req_resp (&opt, handlebars, client, req_resp).await;
}); });
} }
} }

View File

@ -1,4 +1,3 @@
- Fix possible timing gap when refreshing http_listen
- Set up tokens or privkeys or tripcodes or something so - Set up tokens or privkeys or tripcodes or something so
clients can't trivially impersonate servers clients can't trivially impersonate servers