yo_resp/front.rs
1//! What one thread owns: the connections, their buffers and the framing.
2//!
3//! A server on several threads is two halves that have to be told apart before
4//! either of them can move. One half is per connection and belongs to whichever
5//! thread accepted it: the read buffer, the decoder holding a half read command,
6//! the session, the reply buffer and the queue of commands framed and not yet
7//! run. The other half is the keyspace, which every thread reaches and which is
8//! behind the stripes. This module is the first half, and the line is drawn by
9//! the compiler rather than by a comment: nothing in this file can name a
10//! [`Server`], because it does not import one.
11//!
12//! [`Wire`] is where the two meet. Everything that needs both, which is running
13//! a command, answering a blocked client and forgetting a client that has gone,
14//! is a method there and calls into here for the connection half. Everything
15//! that needs only the connections is a method here, which is why framing can be
16//! tested against a [`Front`] with no database anywhere in the test.
17//!
18//! [`Server`]: crate::dispatch::Server
19//! [`Wire`]: crate::engine::Wire
20
21use std::collections::VecDeque;
22
23use yo_reactor::BATCH_MAX;
24
25use crate::dispatch::table::lookup_index;
26use crate::dispatch::{Args, Session};
27use crate::engine::{ConnId, Sink};
28use crate::error::ProtocolError;
29use crate::proto::{Limits, Proto};
30use crate::reply::Out;
31use crate::request::{Argv, Step};
32
33/// The read buffer a connection starts with.
34///
35/// Redis's query buffer starts at sixteen kilobytes for the same reason: it is
36/// larger than every command a client actually sends, so the buffer grows once
37/// at accept time and then never again.
38const READ_BUF: usize = 16 * 1024;
39
40/// The reply buffer a connection starts with.
41const OUT_BUF: usize = 16 * 1024;
42
43/// How many arguments a decoder has room for before it grows.
44const ARGV_HINT: usize = 8;
45
46/// One framed command, waiting to run.
47///
48/// Names the bytes rather than holding them, so the reactor can queue a batch
49/// of these while the front keeps ownership of every buffer they point into.
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub struct Cmd {
52 pub(crate) conn: ConnId,
53 pub(crate) slot: u32,
54 pub(crate) base: usize,
55 /// Which command this is, as a position in the command table.
56 ///
57 /// Resolved once, here, because the name is otherwise looked up twice more
58 /// on the way to running it: once to work out which key to prefetch and once
59 /// to dispatch. A position rather than a reference because this struct is
60 /// queued by the thousand and two bytes is what it costs.
61 ///
62 /// Past the end of the table for a name that is no command, which needs no
63 /// flag of its own and no `Option`, because that is what the lookup already
64 /// answers and what the dispatcher already has a reply for.
65 pub(crate) spec: u16,
66}
67
68impl Cmd {
69 /// The connection this command arrived on.
70 #[must_use]
71 pub const fn conn(&self) -> ConnId {
72 self.conn
73 }
74}
75
76/// What one connection's replies did on their way to the socket.
77pub(crate) enum Wrote {
78 /// The socket took less than was offered, so what is left is held for the
79 /// next flush and the connection stays on the dirty list.
80 Owed,
81 /// Everything went out and the connection is still open.
82 Done,
83 /// Everything went out and the connection ended with it. The client id is
84 /// the one thing the server has to hear about, because a waiter is found by
85 /// it and the slot is about to belong to somebody else.
86 Ended(u64),
87}
88
89/// One connection's state.
90struct Conn {
91 live: bool,
92 session: Session,
93 out: Out,
94 /// What has arrived and not yet been framed away.
95 buf: Vec<u8>,
96 /// How much of `buf` the framing has consumed.
97 head: usize,
98 /// The decoder holding a command that has not all arrived.
99 partial: Option<u32>,
100 /// Commands framed out of this buffer and not yet run.
101 pending: u32,
102 /// This connection is on its way out, once what is buffered has gone.
103 closing: bool,
104 /// A protocol error waiting for the commands in front of it to answer.
105 ///
106 /// The framing finds the error before any of the batch it was framed with
107 /// has run, and writing the error there would put it in front of replies
108 /// the client is still owed. Redis answers in order, so this waits until
109 /// nothing is pending and goes out last.
110 deferred: Option<ProtocolError>,
111 /// Everything still queued for this connection is thrown away unanswered.
112 ///
113 /// `QUIT` sets this and a protocol error does not, which is the difference
114 /// between the two ways a connection ends. A client that pipelines `QUIT`
115 /// and then `SET` has said goodbye and then said something after it, and
116 /// Redis answers the goodbye and drops the rest. A client that sends two
117 /// good commands and then a malformed one gets both good ones answered,
118 /// because they were complete and correct before the stream went wrong.
119 skip: bool,
120 /// The peer is gone, so there is nothing to answer and nothing to write.
121 gone: bool,
122 /// Already on the dirty list.
123 dirty: bool,
124 /// This client is parked on a blocking command.
125 ///
126 /// While it is set, framing stops: whatever the client pipelined behind its
127 /// `BLPOP` stays in the read buffer unread, which is what a client waiting
128 /// for an answer means and is what Redis does with the same bytes.
129 blocked: bool,
130 /// Commands framed before it blocked and not run yet.
131 ///
132 /// A batch is framed before any of it runs, so a `BLPOP` can be the first of
133 /// sixty four commands and the other sixty three are already on their way to
134 /// the reactor when it parks. They come back here and go to the front of the
135 /// queue when the client wakes up, in the order they arrived.
136 ///
137 /// They are still counted in `pending`, which is what stops the read buffer
138 /// being compacted under the offsets they hold.
139 parked: Vec<Cmd>,
140 /// What the two buffers were holding the last time anybody counted.
141 ///
142 /// The connection's share of `INFO memory`, kept here so that reporting it
143 /// is a subtraction against this rather than a walk over every connection.
144 held: usize,
145}
146
147impl Conn {
148 fn new(id: u64) -> Conn {
149 // Accept time, which is the one moment a connection is allowed to cost
150 // an allocation. Everything after this reuses these two buffers.
151 yo_alloc::allow(|| Conn {
152 live: true,
153 session: Session::new(id),
154 out: Out::with_capacity(Proto::Resp2, OUT_BUF),
155 buf: Vec::with_capacity(READ_BUF),
156 head: 0,
157 partial: None,
158 pending: 0,
159 closing: false,
160 deferred: None,
161 skip: false,
162 gone: false,
163 dirty: false,
164 blocked: false,
165 parked: Vec::new(),
166 held: 0,
167 })
168 }
169
170 /// What the two buffers cost the process, which is the room they are
171 /// holding and not the bytes in use: both keep their capacity between
172 /// batches on purpose.
173 fn size(&self) -> usize {
174 self.buf.capacity() + self.out.capacity()
175 }
176
177 /// Back to how it was at accept time, buffers kept.
178 fn reset(&mut self, id: u64) {
179 self.live = true;
180 self.session = Session::new(id);
181 self.out.clear();
182 // The protocol lives in the reply buffer and the reply buffer is kept,
183 // so it has to be put back by hand. Without this a client that opened a
184 // connection into a slot the last client had spoken RESP3 on would be
185 // answered in RESP3 without ever sending `HELLO`, which is a nil it
186 // cannot parse on the first `GET` that misses.
187 self.out.set_proto(Proto::Resp2);
188 self.buf.clear();
189 self.head = 0;
190 self.partial = None;
191 self.pending = 0;
192 self.closing = false;
193 self.deferred = None;
194 self.skip = false;
195 self.gone = false;
196 self.dirty = false;
197 self.blocked = false;
198 // The room it took stays, the way the two buffers' does.
199 self.parked.clear();
200 }
201
202 /// Drop what the framing has already read, when nothing points into it.
203 ///
204 /// A framed command's arguments are offsets from the front of this buffer,
205 /// so this waits for the batch to run. After a batch is where a pipelining
206 /// connection spends most of its life, so that is not much of a wait.
207 ///
208 /// A half read command is not in the way. Its decoder was handed
209 /// `buf[head..]` and every offset it kept is from the front of that slice,
210 /// and `head` does not move until the command is complete, so the bytes it
211 /// is waiting on are exactly the bytes this keeps. They arrive at the front
212 /// instead of at `head` and the decoder cannot tell the difference.
213 ///
214 /// Waiting for it anyway is what made a read buffer grow to everything the
215 /// connection had ever sent. The framing loop only ever stops on an
216 /// incomplete command, and a buffer that ends on a command boundary gives
217 /// one of those on the next turn round: an empty slice, nothing decoded,
218 /// `Step::Incomplete`. So a connection that is exactly up to date always had
219 /// a decoder parked on it, this always returned early, and `head` walked
220 /// forward with the bytes behind it kept forever. Measured on server3, four
221 /// connections sending 100000 sets each held 16 MiB of read buffer apiece,
222 /// and fifty connections sending 8000 each held 1 MiB apiece: in both cases
223 /// every byte the connection had ever sent.
224 fn compact(&mut self) {
225 if self.pending > 0 || self.head == 0 {
226 return;
227 }
228 if self.head == self.buf.len() {
229 self.buf.clear();
230 } else {
231 self.buf.drain(..self.head);
232 }
233 self.head = 0;
234 }
235}
236
237/// The connection side of the server, and all of it belongs to one thread.
238///
239/// Connections, their buffers, the decoder pool, the framing and the queue of
240/// work it produces. There is one of these per I/O thread and they share
241/// nothing, which is why none of it is behind a lock and none of it is atomic.
242pub(crate) struct Front<S> {
243 sink: S,
244 conns: Vec<Conn>,
245 /// Connection slots that closed and can be handed out again.
246 free: Vec<ConnId>,
247 /// The decoder pool.
248 argvs: Vec<Argv>,
249 spare: Vec<u32>,
250 /// Framed and not yet handed to the reactor.
251 ready: VecDeque<Cmd>,
252 /// Connections this batch wrote to.
253 dirty: Vec<ConnId>,
254 /// Where a protocol error line is built before it is copied into a reply.
255 scratch: Vec<u8>,
256 limits: Limits,
257 /// How much the buffers have grown or shrunk since anybody last asked.
258 ///
259 /// `INFO memory` reports what every connection is holding and that total
260 /// lives on the server, which this side cannot reach. So the change is kept
261 /// here and taken by [`Wire`] at the end of whatever call made it, which is
262 /// as timely as reporting it on the spot and does not put the server on the
263 /// other end of a framing call.
264 ///
265 /// [`Wire`]: crate::engine::Wire
266 moved: isize,
267}
268
269impl<S: Sink> Front<S> {
270 /// A front with no connections and nothing pooled.
271 pub(crate) fn new(sink: S) -> Front<S> {
272 Front {
273 sink,
274 conns: Vec::new(),
275 free: Vec::new(),
276 argvs: Vec::new(),
277 spare: Vec::new(),
278 ready: VecDeque::with_capacity(BATCH_MAX),
279 dirty: Vec::with_capacity(16),
280 scratch: Vec::with_capacity(128),
281 limits: Limits::default(),
282 moved: 0,
283 }
284 }
285
286 /// Where the replies went.
287 pub(crate) const fn sink(&self) -> &S {
288 &self.sink
289 }
290
291 /// The same, mutably.
292 pub(crate) const fn sink_mut(&mut self) -> &mut S {
293 &mut self.sink
294 }
295
296 /// Change the protocol limits, which is `proto-max-bulk-len` and friends.
297 pub(crate) fn set_limits(&mut self, limits: Limits) {
298 self.limits = limits;
299 }
300
301 /// Open a connection under the given client id and give back its slot.
302 ///
303 /// The id comes from the caller because CLIENT LIST and CLIENT KILL name a
304 /// client by it across the whole server, so two fronts handing out the same
305 /// number would be two clients answering to one name. A front has no way to
306 /// reach the other fronts, so the one thing they share mints it.
307 ///
308 /// Reuses a closed connection's slot and its two buffers when there is one,
309 /// so a server with a churning client population allocates for the high
310 /// water mark and not for the total.
311 pub(crate) fn open(&mut self, id: u64) -> ConnId {
312 let at = match self.free.pop() {
313 Some(at) => {
314 // A reused slot keeps its buffers, so what it holds is already
315 // counted and this only puts the id back in service.
316 self.conns[at as usize].reset(id);
317 at
318 }
319 None => {
320 let conn = Conn::new(id);
321 yo_alloc::allow(|| self.conns.push(conn));
322 (self.conns.len() - 1) as ConnId
323 }
324 };
325 // The session carries the slot from here, because the slot is what a
326 // subscription on the server names and the front is the only place that
327 // knows it. Both arms above make a fresh session, so this is the one
328 // place it has to be said.
329 self.conns[at as usize].session.set_conn(at);
330 self.note_size(at);
331 at
332 }
333
334 /// Take bytes off a connection and frame whatever commands they complete.
335 ///
336 /// Anything left over stays in the connection's buffer, half a command
337 /// included, so the caller hands over whatever the socket gave it without
338 /// looking at it.
339 pub(crate) fn feed(&mut self, conn: ConnId, bytes: &[u8]) {
340 {
341 let c = &mut self.conns[conn as usize];
342 if !c.live || c.closing {
343 return;
344 }
345 // The buffer is sized for a command at accept time, so this only
346 // grows for a client sending a bulk larger than that, which is a
347 // real allocation for a real reason.
348 yo_alloc::allow(|| c.buf.extend_from_slice(bytes));
349 }
350 self.frame(conn);
351 self.note_size(conn);
352 }
353
354 /// Note what this connection's buffers are holding now, if it has changed
355 /// since the last time anybody asked.
356 ///
357 /// Once per read and once per flush, which is where a buffer can grow, and
358 /// two loads and a compare when nothing has moved. The alternative is a
359 /// walk over every connection on a turn of the loop, which puts the cost of
360 /// a report nobody has asked for on the command path.
361 fn note_size(&mut self, conn: ConnId) {
362 let c = &mut self.conns[conn as usize];
363 let now = c.size();
364 if now == c.held {
365 return;
366 }
367 let delta = now as isize - c.held as isize;
368 c.held = now;
369 self.moved += delta;
370 }
371
372 /// How much the buffers have moved since this was last called.
373 pub(crate) fn buffer_delta(&mut self) -> isize {
374 core::mem::take(&mut self.moved)
375 }
376
377 /// Move as many complete commands as possible out of the read buffer.
378 ///
379 /// Nothing at all while the client is parked. The bytes stay where they are
380 /// and `head` does not move, so a client that pipelines `BLPOP` and then
381 /// `PING` gets the `PING` answered when the `BLPOP` is, and in that order.
382 fn frame(&mut self, conn: ConnId) {
383 if self.conns[conn as usize].blocked {
384 return;
385 }
386 loop {
387 let base = self.conns[conn as usize].head;
388 let slot = match self.conns[conn as usize].partial.take() {
389 Some(slot) => slot,
390 None => self.take_decoder(),
391 };
392
393 let step = {
394 let c = &self.conns[conn as usize];
395 self.argvs[slot as usize].decode(&c.buf[base..], &self.limits)
396 };
397
398 match step {
399 Ok(Step::Command { consumed }) => {
400 self.conns[conn as usize].head += consumed;
401 if self.argvs[slot as usize].is_empty() {
402 // `*0` and a blank inline line: consumed, not answered.
403 self.spare.push(slot);
404 } else {
405 if self.ready.len() == self.ready.capacity() {
406 yo_alloc::allow(|| self.ready.reserve(BATCH_MAX));
407 }
408 // Here and not later, because the name is in front of
409 // the argument list that was just decoded and this is
410 // the last place that holds both it and nothing else to
411 // do. Everything downstream takes the number.
412 let spec = {
413 let c = &self.conns[conn as usize];
414 let args = Args::new(&self.argvs[slot as usize], &c.buf[base..]);
415 lookup_index(args.name())
416 };
417 self.ready.push_back(Cmd {
418 conn,
419 slot,
420 base,
421 spec,
422 });
423 self.conns[conn as usize].pending += 1;
424 }
425 }
426 Ok(Step::Incomplete) => {
427 // Hold the decoder so the rest of this command resumes
428 // where it stopped instead of being read again from the
429 // front every time more of it arrives.
430 self.conns[conn as usize].partial = Some(slot);
431 break;
432 }
433 Err(e) => {
434 self.spare.push(slot);
435 let c = &mut self.conns[conn as usize];
436 // Held rather than written, so it lands behind the replies
437 // to the commands that were framed in front of it out of
438 // the same read.
439 c.deferred = Some(e);
440 // Redis closes after a protocol error and so do we: the two
441 // ends no longer agree on where the next command starts.
442 c.closing = true;
443 self.soil(conn);
444 break;
445 }
446 }
447 }
448 self.conns[conn as usize].compact();
449 }
450
451 /// A decoder from the pool, or a new one the first time round.
452 ///
453 /// The one from the pool is reset before it goes out, because a decoder can
454 /// come back to the pool part way through a command: a protocol error stops
455 /// framing where it is, and a connection that hangs up with half a command
456 /// in its buffer hands its decoder back too. Either one leaves a resume
457 /// point behind, and a resume point is an offset into a buffer that is
458 /// about to stop being the same buffer. A decoder taken here is always
459 /// starting a command, never continuing one, since a continuation comes off
460 /// the connection's own `partial` and never off the pool.
461 fn take_decoder(&mut self) -> u32 {
462 match self.spare.pop() {
463 Some(slot) => {
464 self.argvs[slot as usize].reset();
465 slot
466 }
467 None => yo_alloc::allow(|| {
468 self.argvs.push(Argv::with_capacity(ARGV_HINT));
469 // Every slot handed out here comes back to `spare` exactly
470 // once, so `spare` never holds more than `argvs` has slots.
471 // Sizing it here means the pushes that give a slot back never
472 // touch the allocator, and those are on the command path while
473 // this is not: a decoder is made once per depth of pipelining
474 // the connection has ever reached. `spare` is empty right now,
475 // which is why we are down here at all.
476 self.spare.reserve(self.argvs.len());
477 (self.argvs.len() - 1) as u32
478 }),
479 }
480 }
481
482 /// Note that this connection has something to write.
483 pub(crate) fn soil(&mut self, conn: ConnId) {
484 let c = &mut self.conns[conn as usize];
485 if !c.dirty {
486 c.dirty = true;
487 if self.dirty.len() == self.dirty.capacity() {
488 yo_alloc::allow(|| self.dirty.reserve(16));
489 }
490 self.dirty.push(conn);
491 }
492 }
493
494 /// The session on a connection, for the server side of it going away.
495 ///
496 /// `None` for a slot that is already free, so that closing twice is not two
497 /// chances to hand back the same watches.
498 pub(crate) fn session_mut(&mut self, conn: ConnId) -> Option<&mut Session> {
499 let c = &mut self.conns[conn as usize];
500 c.live.then_some(&mut c.session)
501 }
502
503 /// Hand the slot and its buffers back, and say which client has gone.
504 ///
505 /// `None` for a slot that was already closed. The id is what the server
506 /// finds a waiter by, and the caller forgets it before anything else runs,
507 /// because this slot is on the free list from here and the next accept
508 /// hands it to somebody else.
509 pub(crate) fn close(&mut self, conn: ConnId) -> Option<u64> {
510 {
511 let c = &mut self.conns[conn as usize];
512 if !c.live {
513 return None;
514 }
515 if let Some(slot) = c.partial.take() {
516 self.spare.push(slot);
517 }
518 c.live = false;
519 c.dirty = false;
520 c.blocked = false;
521 c.out.clear();
522 c.buf.clear();
523 c.head = 0;
524 }
525 let client = self.conns[conn as usize].session.id();
526 self.sink.closed(conn);
527 yo_alloc::allow(|| self.free.push(conn));
528 Some(client)
529 }
530
531 /// Move up to `max` framed commands into `into`.
532 ///
533 /// The reactor wants a batch it owns, and the front keeps the buffers, so
534 /// what crosses between them is this: numbers, no borrows.
535 pub(crate) fn take_ready(&mut self, into: &mut Vec<Cmd>, max: usize) -> usize {
536 let n = max.min(self.ready.len());
537 into.extend(self.ready.drain(..n));
538 n
539 }
540
541 /// Offer one connection's replies to the sink.
542 pub(crate) fn write_out(&mut self, conn: ConnId) -> Wrote {
543 {
544 let c = &self.conns[conn as usize];
545 if !c.live {
546 return Wrote::Done;
547 }
548 }
549 // A protocol error goes out once everything in front of it has.
550 if self.conns[conn as usize].pending == 0
551 && let Some(e) = self.conns[conn as usize].deferred.take()
552 {
553 self.scratch.clear();
554 e.write_reply(&mut self.scratch);
555 self.conns[conn as usize].out.raw(&self.scratch);
556 }
557
558 let taken = {
559 let c = &self.conns[conn as usize];
560 if c.out.is_empty() {
561 0
562 } else {
563 // One write for the whole batch's replies, never one per reply.
564 self.sink.write(conn, c.out.as_slice())
565 }
566 };
567
568 let c = &mut self.conns[conn as usize];
569 if taken >= c.out.len() {
570 c.out.clear();
571 } else {
572 c.out.consume(taken);
573 }
574
575 if !c.out.is_empty() {
576 return Wrote::Owed;
577 }
578 c.dirty = false;
579 let ending = c.closing && c.pending == 0;
580 if ending {
581 if let Some(client) = self.close(conn) {
582 return Wrote::Ended(client);
583 }
584 } else {
585 c.compact();
586 self.note_size(conn);
587 }
588 Wrote::Done
589 }
590
591 /// The dirty list, taken so the caller can walk it and reach the rest of
592 /// the front at the same time. The capacity comes back with it, so this is
593 /// not an allocation.
594 pub(crate) fn take_dirty(&mut self) -> Vec<ConnId> {
595 core::mem::take(&mut self.dirty)
596 }
597
598 /// The dirty list, given back with whatever is still owed on it.
599 pub(crate) fn give_dirty(&mut self, dirty: Vec<ConnId>) {
600 self.dirty = dirty;
601 }
602
603 /// How many connections are open.
604 pub(crate) fn clients(&self) -> usize {
605 self.conns.iter().filter(|c| c.live).count()
606 }
607
608 /// Commands framed and waiting for the reactor.
609 pub(crate) fn ready(&self) -> usize {
610 self.ready.len()
611 }
612
613 /// Connections with a reply that has not gone out yet.
614 pub(crate) fn owed(&self) -> usize {
615 self.dirty.len()
616 }
617
618 /// Decoders in the pool, which is the high water mark of one batch.
619 pub(crate) fn decoders(&self) -> usize {
620 self.argvs.len()
621 }
622
623 /// What every connection's read and reply buffers are holding.
624 ///
625 /// The walk is fine here because this is a test and a report, and the
626 /// number the running server uses is the one kept by `note_size`.
627 pub(crate) fn buffer_bytes(&self) -> usize {
628 self.conns.iter().map(Conn::size).sum()
629 }
630
631 /// Whether the slot is open.
632 pub(crate) fn live(&self, conn: ConnId) -> bool {
633 self.conns[conn as usize].live
634 }
635
636 /// Whether the peer has gone.
637 pub(crate) fn gone(&self, conn: ConnId) -> bool {
638 self.conns[conn as usize].gone
639 }
640
641 /// Commands framed out of this connection's buffer and not yet run.
642 pub(crate) fn pending(&self, conn: ConnId) -> u32 {
643 self.conns[conn as usize].pending
644 }
645
646 /// Whether this client is parked on a blocking command.
647 pub(crate) fn blocked(&self, conn: ConnId) -> bool {
648 self.conns[conn as usize].blocked
649 }
650
651 /// The client id, which is what the server knows a connection by.
652 pub(crate) fn client(&self, conn: ConnId) -> u64 {
653 self.conns[conn as usize].session.id()
654 }
655
656 /// The database this connection has selected.
657 pub(crate) fn db(&self, conn: ConnId) -> usize {
658 self.conns[conn as usize].session.db()
659 }
660
661 /// Whether this slot is still the client the server thinks it is.
662 ///
663 /// A slot is reused and a client id is not, so a waiter that named a client
664 /// is only about this connection while both agree.
665 pub(crate) fn answers(&self, conn: ConnId, client: u64) -> bool {
666 let c = &self.conns[conn as usize];
667 c.live && c.session.id() == client
668 }
669
670 /// Where a reply for this connection goes.
671 pub(crate) fn out(&mut self, conn: ConnId) -> &mut Out {
672 &mut self.conns[conn as usize].out
673 }
674
675 /// The peer went away.
676 pub(crate) fn mark_gone(&mut self, conn: ConnId) {
677 let c = &mut self.conns[conn as usize];
678 c.gone = true;
679 c.closing = true;
680 }
681
682 /// The client said goodbye.
683 ///
684 /// Anything it pipelined behind the `QUIT` was sent before it knew the
685 /// answer, and running it would be acting on a connection that has already
686 /// been said goodbye to.
687 pub(crate) fn quit(&mut self, conn: ConnId) {
688 let c = &mut self.conns[conn as usize];
689 c.closing = true;
690 c.skip = true;
691 }
692
693 /// The client is waiting on a blocking command.
694 pub(crate) fn block(&mut self, conn: ConnId) {
695 self.conns[conn as usize].blocked = true;
696 }
697
698 /// Hold a command that was framed with the batch that blocked.
699 pub(crate) fn park(&mut self, conn: ConnId, cmd: Cmd) {
700 yo_alloc::allow(|| self.conns[conn as usize].parked.push(cmd));
701 }
702
703 /// The client is not waiting any more: give it back its commands.
704 ///
705 /// The ones it had already sent go to the front of the queue in the order
706 /// they arrived, ahead of anything any other connection has waiting, because
707 /// they were framed before any of that was. Then framing starts again on
708 /// whatever arrived while it was parked.
709 pub(crate) fn unpark(&mut self, conn: ConnId) {
710 let mut parked = {
711 let c = &mut self.conns[conn as usize];
712 c.blocked = false;
713 core::mem::take(&mut c.parked)
714 };
715 // Back to front, since each one goes on the front.
716 while let Some(cmd) = parked.pop() {
717 if self.ready.len() == self.ready.capacity() {
718 yo_alloc::allow(|| self.ready.reserve(BATCH_MAX));
719 }
720 self.ready.push_front(cmd);
721 }
722 // Empty now, and back where it lives so its room is not paid for twice.
723 self.conns[conn as usize].parked = parked;
724 if !self.conns[conn as usize].closing {
725 self.frame(conn);
726 }
727 }
728
729 /// A command is off the queue: take it out of the count, and say whether it
730 /// should run at all.
731 ///
732 /// It should not when the peer has gone or has said goodbye, and the answer
733 /// is `false` rather than an early return because the decoder still has to
734 /// come back and the slot still has to be released.
735 pub(crate) fn start(&mut self, cmd: &Cmd) -> bool {
736 let c = &mut self.conns[cmd.conn as usize];
737 c.pending -= 1;
738 !(c.gone || c.skip)
739 }
740
741 /// The three things running a command needs from this side: the arguments,
742 /// the session they run against, and where the reply goes.
743 pub(crate) fn parts(&mut self, cmd: &Cmd) -> (Args<'_>, &mut Session, &mut Out) {
744 let c = &mut self.conns[cmd.conn as usize];
745 let args = Args::new(&self.argvs[cmd.slot as usize], &c.buf[cmd.base..]);
746 (args, &mut c.session, &mut c.out)
747 }
748
749 /// The arguments alone, for a caller that is only reading them.
750 pub(crate) fn args(&self, cmd: &Cmd) -> Args<'_> {
751 let c = &self.conns[cmd.conn as usize];
752 Args::new(&self.argvs[cmd.slot as usize], &c.buf[cmd.base..])
753 }
754
755 /// The command is finished with its decoder.
756 pub(crate) fn done(&mut self, cmd: &Cmd) {
757 self.spare.push(cmd.slot);
758 }
759}
760
761#[cfg(test)]
762mod tests {
763 use super::*;
764 use crate::engine::Recorder;
765
766 /// The wire bytes for a command, built the way a client would.
767 fn wire(args: &[&[u8]]) -> Vec<u8> {
768 let mut b = format!("*{}\r\n", args.len()).into_bytes();
769 for a in args {
770 b.extend_from_slice(format!("${}\r\n", a.len()).as_bytes());
771 b.extend_from_slice(a);
772 b.extend_from_slice(b"\r\n");
773 }
774 b
775 }
776
777 /// A front and one connection on it. No server anywhere, which is the
778 /// point: framing is this side's work alone.
779 fn front() -> (Front<Recorder>, ConnId) {
780 let mut f = Front::new(Recorder::new());
781 let conn = f.open(1);
782 (f, conn)
783 }
784
785 #[test]
786 fn a_pipelined_read_frames_every_command_in_it() {
787 let (mut f, conn) = front();
788 let mut bytes = wire(&[b"SET", b"k", b"v"]);
789 bytes.extend_from_slice(&wire(&[b"GET", b"k"]));
790 f.feed(conn, &bytes);
791
792 let mut batch = Vec::new();
793 assert_eq!(f.take_ready(&mut batch, 64), 2);
794 assert_eq!(f.args(&batch[0]).name(), b"SET");
795 assert_eq!(f.args(&batch[1]).name(), b"GET");
796 assert_eq!(f.pending(conn), 2);
797 }
798
799 #[test]
800 fn a_command_split_across_reads_is_framed_once_it_is_whole() {
801 let (mut f, conn) = front();
802 let bytes = wire(&[b"SET", b"k", b"v"]);
803 let (head, tail) = bytes.split_at(9);
804
805 f.feed(conn, head);
806 let mut batch = Vec::new();
807 assert_eq!(f.take_ready(&mut batch, 64), 0);
808
809 f.feed(conn, tail);
810 assert_eq!(f.take_ready(&mut batch, 64), 1);
811 assert_eq!(f.args(&batch[0]).name(), b"SET");
812 }
813
814 #[test]
815 fn a_protocol_error_stops_the_framing_and_closes_the_connection() {
816 let (mut f, conn) = front();
817 f.feed(conn, b"*x\r\n");
818 assert_eq!(f.take_ready(&mut Vec::new(), 64), 0);
819 assert_eq!(f.owed(), 1);
820
821 // Nothing is owed to the client afterwards and the slot has gone back,
822 // which is what a closed connection means on this side.
823 assert!(matches!(f.write_out(conn), Wrote::Ended(_)));
824 assert!(!f.live(conn));
825 assert!(f.sink().sent(conn).starts_with(b"-ERR"));
826 }
827
828 #[test]
829 fn a_closed_slot_is_handed_out_again_with_its_buffers() {
830 let (mut f, conn) = front();
831 f.feed(conn, &wire(&[b"PING"]));
832 let held = f.buffer_bytes();
833 assert_eq!(f.close(conn), Some(1));
834
835 let next = f.open(2);
836 assert_eq!(next, conn, "the slot comes back");
837 assert_eq!(f.client(next), 2, "the client id does not");
838 assert_eq!(f.buffer_bytes(), held, "and neither buffer was given up");
839 }
840
841 #[test]
842 fn the_buffers_are_reported_as_they_move_and_only_once() {
843 let (mut f, conn) = front();
844 assert!(f.buffer_delta() > 0, "accept made two buffers");
845 assert_eq!(f.buffer_delta(), 0, "and nobody is told about them twice");
846
847 f.feed(conn, &wire(&[b"PING"]));
848 assert_eq!(f.buffer_delta(), 0, "a command that fits moves nothing");
849 }
850}