| ... | @@ -44,6 +44,16 @@ const SNAPSHOT_AGE: Duration = Duration::from_secs(120); | ... | @@ -44,6 +44,16 @@ const SNAPSHOT_AGE: Duration = Duration::from_secs(120); |
| 44 | const IMAGES: usize = 4; | 44 | const IMAGES: usize = 4; |
| 45 | /// What a host says leaving as it stops sharing. | 45 | /// What a host says leaving as it stops sharing. |
| 46 | const STOPPED: &str = "stopped"; | 46 | const STOPPED: &str = "stopped"; |
| | 47 | /// What a host says hanging up on a guest that asks too much too fast. |
| | 48 | const FLOODED: &str = "flooded"; |
| | 49 | /// A guest's requests a host holds at once, waiting and in hand, past which it hangs up. |
| | 50 | const QUEUED: usize = 32; |
| | 51 | /// The threads working through each guest's requests. |
| | 52 | const WORKERS: usize = 2; |
| | 53 | /// Requests a guest may start each second, and at once: every request but a read's later |
| | 54 | /// chunks and an upload's, which come from memory or go to it. |
| | 55 | const STARTS: f64 = 100.0; |
| | 56 | const BURST: f64 = 200.0; |
| 47 | | 57 | |
| 48 | const WORDS: &str = include_str!("words.txt"); | 58 | const WORDS: &str = include_str!("words.txt"); |
| 49 | | 59 | |
| ... | @@ -231,7 +241,7 @@ impl Host { | ... | @@ -231,7 +241,7 @@ impl Host { |
| 231 | images: Mutex::default(), | 241 | images: Mutex::default(), |
| 232 | snapshots: Mutex::default(), | 242 | snapshots: Mutex::default(), |
| 233 | puts: Mutex::default(), | 243 | puts: Mutex::default(), |
| 234 | lines: Mutex::default(), | 244 | guests: Mutex::default(), |
| 235 | }); | 245 | }); |
| 236 | let serving = Hello { | 246 | let serving = Hello { |
| 237 | serves: Some(sharing.share), | 247 | serves: Some(sharing.share), |
| ... | @@ -244,22 +254,12 @@ impl Host { | ... | @@ -244,22 +254,12 @@ impl Host { |
| 244 | reach, | 254 | reach, |
| 245 | relay, | 255 | relay, |
| 246 | move |event| match event { | 256 | move |event| match event { |
| 247 | Event::Met(hello, line) => { | 257 | Event::Met(hello, line) => heard.admit(hello.peer, line), |
| 248 | heard.lines.lock().unwrap().insert(hello.peer, line.clone()); | | |
| 249 | } | | |
| 250 | Event::Left(hello) => heard.forget(&hello.peer), | 258 | Event::Left(hello) => heard.forget(&hello.peer), |
| 251 | Event::Frame { | 259 | Event::Frame { |
| 252 | from, | 260 | from, kind, body, .. |
| 253 | kind, | | |
| 254 | body, | | |
| 255 | line, | | |
| 256 | } if wire::KNOWN.contains(&kind) && kind > 256 && kind != kind::REPLY => { | 261 | } if wire::KNOWN.contains(&kind) && kind > 256 && kind != kind::REPLY => { |
| 257 | let (served, line, peer) = (Arc::clone(&heard), line.clone(), from.peer); | 262 | heard.queue(&from.peer, kind, body); |
| 258 | let body = body.to_vec(); | | |
| 259 | thread::spawn(move || { | | |
| 260 | let reply = served.handle(&peer, kind, &body); | | |
| 261 | let _ = line.send(kind::REPLY, &reply); | | |
| 262 | }); | | |
| 263 | } | 263 | } |
| 264 | Event::Changed => told(), | 264 | Event::Changed => told(), |
| 265 | _ => {} | 265 | _ => {} |
| ... | @@ -379,7 +379,16 @@ struct Served { | ... | @@ -379,7 +379,16 @@ struct Served { |
| 379 | snapshots: Mutex<ByGuest<(Arc<Vec<u8>>, Instant)>>, | 379 | snapshots: Mutex<ByGuest<(Arc<Vec<u8>>, Instant)>>, |
| 380 | /// Bytes a later request carries, by guest and upload. | 380 | /// Bytes a later request carries, by guest and upload. |
| 381 | puts: Mutex<ByGuest<Vec<u8>>>, | 381 | puts: Mutex<ByGuest<Vec<u8>>>, |
| 382 | lines: Mutex<BTreeMap<[u8; 16], Line>>, | 382 | guests: Mutex<BTreeMap<[u8; 16], Admitted>>, |
| | 383 | } |
| | 384 | |
| | 385 | /// A guest as its host serves it: the line to it, its requests waiting for its workers, and |
| | 386 | /// how many more it may start now. |
| | 387 | struct Admitted { |
| | 388 | line: Line, |
| | 389 | queue: mpsc::SyncSender<(u16, Vec<u8>)>, |
| | 390 | starts: f64, |
| | 391 | counted: Instant, |
| 383 | } | 392 | } |
| 384 | | 393 | |
| 385 | /// A section's path, stamp and image. | 394 | /// A section's path, stamp and image. |
| ... | @@ -406,8 +415,57 @@ fn refused(kind: io::ErrorKind, message: &str) -> Error { | ... | @@ -406,8 +415,57 @@ fn refused(kind: io::ErrorKind, message: &str) -> Error { |
| 406 | } | 415 | } |
| 407 | | 416 | |
| 408 | impl Served { | 417 | impl Served { |
| | 418 | /// Serves `peer` on `line`, through workers of its own that end as it leaves. |
| | 419 | fn admit(self: &Arc<Self>, peer: [u8; 16], line: &Line) { |
| | 420 | let (queue, waiting) = mpsc::sync_channel::<(u16, Vec<u8>)>(QUEUED - WORKERS); |
| | 421 | let waiting = Arc::new(Mutex::new(waiting)); |
| | 422 | for _ in 0..WORKERS { |
| | 423 | let (served, waiting, line) = |
| | 424 | (Arc::downgrade(self), Arc::clone(&waiting), line.clone()); |
| | 425 | thread::spawn(move || { |
| | 426 | loop { |
| | 427 | let next = waiting.lock().unwrap().recv(); |
| | 428 | let (Ok((kind, body)), Some(served)) = (next, served.upgrade()) else { |
| | 429 | return; |
| | 430 | }; |
| | 431 | let _ = line.send(kind::REPLY, &served.handle(&peer, kind, &body)); |
| | 432 | } |
| | 433 | }); |
| | 434 | } |
| | 435 | let guest = Admitted { |
| | 436 | line: line.clone(), |
| | 437 | queue, |
| | 438 | starts: BURST, |
| | 439 | counted: Instant::now(), |
| | 440 | }; |
| | 441 | self.guests.lock().unwrap().insert(peer, guest); |
| | 442 | } |
| | 443 | |
| | 444 | /// Hands a request from `peer` to its workers, or hangs up on a guest that has too many |
| | 445 | /// waiting or starts them too fast. |
| | 446 | fn queue(&self, peer: &[u8; 16], kind: u16, body: &[u8]) { |
| | 447 | let mut guests = self.guests.lock().unwrap(); |
| | 448 | let Some(guest) = guests.get_mut(peer) else { |
| | 449 | return; |
| | 450 | }; |
| | 451 | let continued = kind == kind::PUT |
| | 452 | || matches!(kind, kind::READ | kind::READ_FILE) |
| | 453 | && minicbor::decode::<Request>(body).is_ok_and(|request| request.handle.is_some()); |
| | 454 | let now = Instant::now(); |
| | 455 | guest.starts = |
| | 456 | (guest.starts + now.duration_since(guest.counted).as_secs_f64() * STARTS).min(BURST); |
| | 457 | guest.counted = now; |
| | 458 | if !continued { |
| | 459 | guest.starts -= 1.0; |
| | 460 | } |
| | 461 | if guest.starts < 0.0 || guest.queue.try_send((kind, body.to_vec())).is_err() { |
| | 462 | guest.line.hang_up(FLOODED); |
| | 463 | guests.remove(peer); |
| | 464 | } |
| | 465 | } |
| | 466 | |
| 409 | fn forget(&self, peer: &[u8; 16]) { | 467 | fn forget(&self, peer: &[u8; 16]) { |
| 410 | self.lines.lock().unwrap().remove(peer); | 468 | self.guests.lock().unwrap().remove(peer); |
| 411 | self.snapshots | 469 | self.snapshots |
| 412 | .lock() | 470 | .lock() |
| 413 | .unwrap() | 471 | .unwrap() |
| ... | @@ -423,8 +481,8 @@ impl Served { | ... | @@ -423,8 +481,8 @@ impl Served { |
| 423 | let touched = Touched { | 481 | let touched = Touched { |
| 424 | paths: paths.to_vec(), | 482 | paths: paths.to_vec(), |
| 425 | }; | 483 | }; |
| 426 | for line in self.lines.lock().unwrap().values() { | 484 | for guest in self.guests.lock().unwrap().values() { |
| 427 | let _ = line.send(kind::TOUCHED, &touched); | 485 | let _ = guest.line.send(kind::TOUCHED, &touched); |
| 428 | } | 486 | } |
| 429 | } | 487 | } |
| 430 | | 488 | |