Skip to main content

coreshift_core/io/
drain.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/
4
5//! High-level process I/O management.
6//!
7//! This module provides the [`DrainState`] structure, which coordinates the
8//! simultaneous reading from process output pipes and writing to process
9//! input pipes.
10//!
11//! This is an advanced helper for callers that already own child-process file
12//! descriptors and want non-blocking drain semantics without reimplementing
13//! the bookkeeping.
14
15use crate::CoreError;
16use crate::fd::{Fd, Token};
17use crate::io::buffer::{BufferState, ChunkSink, ReadState};
18use crate::io::writer::WriterState;
19
20/// Time bound (ms) for a [`DrainState::write_input`] `poll(POLLOUT)` wait. The
21/// pty master is `O_NONBLOCK`, so a write returns `EAGAIN` once the child's tty
22/// input buffer is full; the write then waits for writability. Bounded so a
23/// wedged child (never draining its stdin) cannot stall the caller forever.
24const WRITE_INPUT_POLL_TIMEOUT_MS: i32 = 2_000;
25
26#[inline(always)]
27fn errno() -> i32 {
28    std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
29}
30
31/// Associates a file descriptor with an optional reactor token and the current
32/// per-direction interest registered with the reactor.
33pub(crate) struct FdSlot {
34    /// Token assigned by the reactor for this descriptor. `None` while the fd
35    /// is not registered (never added, or removed wholesale on teardown).
36    pub token: Option<Token>,
37    /// The managed file descriptor.
38    pub fd: Fd,
39    /// Current readable interest (`EPOLLIN`) registered with the reactor.
40    pub readable: bool,
41    /// Current writable interest (`EPOLLOUT`) registered with the reactor.
42    pub writable: bool,
43}
44
45/// Orchestrates non-blocking process I/O.
46///
47/// `DrainState` tracks the state of stdin, stdout, and stderr pipes for a
48/// single process. It handles the multiplexing of data between these pipes
49/// and internal buffers.
50///
51/// # Example
52/// ```no_run
53/// # use coreshift_core::io::DrainState;
54/// # use coreshift_core::reactor::Reactor;
55/// # fn example(mut drain: DrainState<fn(&[u8]) -> bool>, mut reactor: Reactor) -> Result<(), Box<dyn std::error::Error>> {
56/// while !drain.is_done() {
57///     let mut events = Vec::new();
58///     reactor.wait(&mut events, 64, -1)?;
59///     for ev in events {
60///         // Map event tokens to drain calls...
61///     }
62/// }
63/// # Ok(())
64/// # }
65/// ```
66#[repr(align(64))]
67pub struct DrainState<F>
68where
69    F: FnMut(&[u8]) -> bool,
70{
71    pub(crate) stdout_slot: Option<FdSlot>,
72    pub(crate) stderr_slot: Option<FdSlot>,
73    pub(crate) stdin_slot: Option<FdSlot>,
74
75    pub(crate) buffer: BufferState,
76    pub(crate) writer: WriterState,
77
78    pub(crate) early_exit: Option<F>,
79
80    /// `true` when the stdout slot is a pty master rather than a pipe. A pty
81    /// master reports EOF as `EIO` (returned once the session leader and all
82    /// slave holders have closed), so the read path maps a stdout `EIO` to a
83    /// clean [`ReadState::Eof`] instead of surfacing it as an I/O error.
84    pub(crate) pty_master: bool,
85}
86
87impl<F> DrainState<F>
88where
89    F: FnMut(&[u8]) -> bool,
90{
91    /// Initialize a new drain state for the provided descriptors.
92    ///
93    /// This consumes the descriptors and sets them to non-blocking mode.
94    /// `chunk_sink` enables streaming mode: every retained output chunk is
95    /// forwarded to the sink instead of being accumulated into the internal
96    /// buffers.
97    ///
98    /// ### Errors
99    /// - `EBADF`: One of the provided file descriptors is invalid.
100    #[allow(clippy::too_many_arguments)] // published constructor; grouped params would break the API
101    pub fn new(
102        stdin_fd: Option<Fd>,
103        stdin_buf: Option<Box<[u8]>>,
104        stdout_fd: Option<Fd>,
105        stderr_fd: Option<Fd>,
106        limit: usize,
107        early_exit: Option<F>,
108        chunk_sink: Option<ChunkSink>,
109        pty_master: bool,
110    ) -> Result<Self, CoreError> {
111        let stdin_slot = if stdin_buf.is_some() {
112            if let Some(fd) = stdin_fd {
113                fd.set_nonblock()?;
114                Some(FdSlot {
115                    token: None,
116                    fd,
117                    readable: false,
118                    writable: false,
119                })
120            } else {
121                None
122            }
123        } else {
124            None
125        };
126
127        let stdout_slot = if let Some(fd) = stdout_fd {
128            fd.set_nonblock()?;
129            Some(FdSlot {
130                token: None,
131                fd,
132                readable: false,
133                writable: false,
134            })
135        } else {
136            None
137        };
138
139        let stderr_slot = if let Some(fd) = stderr_fd {
140            fd.set_nonblock()?;
141            Some(FdSlot {
142                token: None,
143                fd,
144                readable: false,
145                writable: false,
146            })
147        } else {
148            None
149        };
150
151        Ok(Self {
152            stdin_slot,
153            stdout_slot,
154            stderr_slot,
155            buffer: BufferState::new(limit, chunk_sink),
156            writer: WriterState::new(stdin_buf),
157            early_exit,
158            pty_master,
159        })
160    }
161
162    /// Returns `true` if all pipes have been closed or fully drained.
163    #[inline(always)]
164    pub fn is_done(&self) -> bool {
165        self.stdin_slot.is_none() && self.stdout_slot.is_none() && self.stderr_slot.is_none()
166    }
167
168    /// Apply a new window size (`TIOCSWINSZ`) to the pty master's terminal.
169    ///
170    /// Sending a signal to the foreground process group after a resize is the
171    /// caller's job (SIGWINCH); this only updates the kernel's `winsize` so a
172    /// subsequent `TIOCGWINSZ`/`SIGWINCH`-driven refresh reads the new size.
173    ///
174    /// ### Errors
175    /// - `EINVAL`: No pty master is present (non-pty spawn or stream already
176    ///   closed), or `rows`/`cols` is zero.
177    /// - `ENOTTY`: The stdout descriptor is not a terminal.
178    pub(crate) fn resize_pty(&self, rows: u16, cols: u16) -> Result<(), CoreError> {
179        if rows == 0 || cols == 0 {
180            return Err(CoreError::sys(
181                libc::EINVAL,
182                "resize_pty: rows and cols must be non-zero",
183            ));
184        }
185        let Some(slot) = &self.stdout_slot else {
186            return Err(CoreError::sys(libc::EINVAL, "resize_pty: no pty master"));
187        };
188        let ws = libc::winsize {
189            ws_row: rows,
190            ws_col: cols,
191            ws_xpixel: 0,
192            ws_ypixel: 0,
193        };
194        let r = unsafe { libc::ioctl(slot.fd.raw(), libc::TIOCSWINSZ as libc::Ioctl, &ws) };
195        crate::error::syscall_ret(r, "TIOCSWINSZ")
196    }
197
198    /// Write bytes to the pty master — the child's stdin on a pty spawn.
199    ///
200    /// The master is `O_NONBLOCK`, so a full write waits for `POLLOUT` (bounded
201    /// by [`WRITE_INPUT_POLL_TIMEOUT_MS`]) when the tty input buffer is full,
202    /// and returns once every byte has been accepted by the line discipline.
203    ///
204    /// ### Errors
205    /// - `EINVAL`: Not a pty spawn, or the pty master is already closed.
206    /// - `EIO`: All slave holders have closed (master-side write failure).
207    /// - `ETIMEDOUT`: The child did not drain its input within the poll bound.
208    pub(crate) fn write_input(&self, bytes: &[u8]) -> Result<usize, CoreError> {
209        if !self.pty_master {
210            return Err(CoreError::sys(libc::EINVAL, "write_input: not a pty spawn"));
211        }
212        let Some(slot) = &self.stdout_slot else {
213            return Err(CoreError::sys(
214                libc::EINVAL,
215                "write_input: pty master closed",
216            ));
217        };
218        let fd = slot.fd.raw();
219        let mut written = 0usize;
220        while written < bytes.len() {
221            let n = unsafe {
222                libc::write(
223                    fd,
224                    bytes[written..].as_ptr() as *const libc::c_void,
225                    bytes.len() - written,
226                )
227            };
228            if n < 0 {
229                let e = errno();
230                if e == libc::EINTR {
231                    continue;
232                }
233                if e == libc::EAGAIN {
234                    let mut pfd = libc::pollfd {
235                        fd,
236                        events: libc::POLLOUT,
237                        revents: 0,
238                    };
239                    let rc = unsafe { libc::poll(&mut pfd, 1, WRITE_INPUT_POLL_TIMEOUT_MS) };
240                    if rc < 0 {
241                        let pe = errno();
242                        if pe == libc::EINTR {
243                            continue;
244                        }
245                        return Err(CoreError::sys(pe, "write_input:poll"));
246                    }
247                    if rc == 0 {
248                        return Err(CoreError::sys(
249                            libc::ETIMEDOUT,
250                            "write_input: tty input buffer stayed full",
251                        ));
252                    }
253                    continue;
254                }
255                return Err(CoreError::sys(e, "write_input"));
256            }
257            written += n as usize;
258        }
259        Ok(written)
260    }
261
262    /// Write bytes to the pty master without blocking.
263    ///
264    /// Returns `Ok(Some(n))` with the number of bytes written (which may be
265    /// less than `bytes.len()` when the tty input buffer fills mid-write), or
266    /// `Ok(None)` when `EAGAIN` on the first byte (buffer full). Never blocks
267    /// — the caller owns the input queue and re-arms `POLLOUT` interest on
268    /// `EAGAIN` (register writable), writing again on readiness.
269    ///
270    /// ### Errors
271    /// - `EINVAL`: Not a pty spawn, or the pty master is already closed.
272    /// - `EIO`: All slave holders have closed (master-side write failure).
273    pub(crate) fn write_input_nonblock(&self, bytes: &[u8]) -> Result<Option<usize>, CoreError> {
274        if !self.pty_master {
275            return Err(CoreError::sys(
276                libc::EINVAL,
277                "write_input_nonblock: not a pty spawn",
278            ));
279        }
280        let Some(slot) = &self.stdout_slot else {
281            return Err(CoreError::sys(
282                libc::EINVAL,
283                "write_input_nonblock: pty master closed",
284            ));
285        };
286        let fd = slot.fd.raw();
287        let mut written = 0usize;
288        while written < bytes.len() {
289            let n = unsafe {
290                libc::write(
291                    fd,
292                    bytes[written..].as_ptr() as *const libc::c_void,
293                    bytes.len() - written,
294                )
295            };
296            if n < 0 {
297                let e = errno();
298                if e == libc::EINTR {
299                    continue;
300                }
301                if e == libc::EAGAIN {
302                    return Ok(if written == 0 { None } else { Some(written) });
303                }
304                return Err(CoreError::sys(e, "write_input_nonblock"));
305            }
306            written += n as usize;
307        }
308        Ok(Some(written))
309    }
310
311    /// Perform a non-blocking write to stdin if pending.
312    ///
313    /// Returns `Ok(true)` if the write buffer is empty or the descriptor is
314    /// closed.
315    ///
316    /// ### Errors
317    /// - `EPIPE`: The child process closed its reading end of the pipe.
318    /// - `EIO`: Low-level I/O error.
319    #[inline(always)]
320    pub fn write_stdin(&mut self) -> Result<bool, CoreError> {
321        let fd = if let Some(s) = &self.stdin_slot {
322            &s.fd
323        } else {
324            return Ok(true);
325        };
326
327        let done = self.writer.write_to_fd(fd)?;
328        if done {
329            self.stdin_slot.take();
330            return Ok(true);
331        }
332        Ok(false)
333    }
334
335    /// Read from a slot's descriptor, mapping a pty-master `EIO` EOF to a
336    /// clean [`ReadState::Eof`]. All other errors propagate.
337    ///
338    /// Associated fn (no `self` receiver) so callers can borrow `buffer` and
339    /// `early_exit` mutably while a `stdout_slot`/`stderr_slot` borrow is
340    /// still live — disjoint field borrows the compiler can see.
341    #[inline(always)]
342    fn read_from_slot(
343        buffer: &mut BufferState,
344        fd: &Fd,
345        is_stdout: bool,
346        early_exit: &mut Option<F>,
347        pty_master: bool,
348    ) -> Result<ReadState, CoreError> {
349        match buffer.read_from_fd(fd, is_stdout, early_exit) {
350            Err(e) if pty_master && is_stdout && e.raw_os_error() == Some(libc::EIO) => {
351                Ok(ReadState::Eof)
352            }
353            other => other,
354        }
355    }
356
357    /// Perform a non-blocking read from stdout or stderr.
358    ///
359    /// Returns `Ok(true)` if the stream reached EOF, the early-exit condition
360    /// was met, or the stream is paused on a full sink queue (the caller
361    /// resumes it later). In the paused case the slot is retained.
362    ///
363    /// ### Errors
364    /// - `EOVERFLOW`: The captured output exceeded the specified limit.
365    /// - `EIO`: Low-level I/O error.
366    #[inline(always)]
367    pub fn read_fd(&mut self, is_stdout: bool) -> Result<bool, CoreError> {
368        let pty_master = self.pty_master;
369        let read_state = {
370            let slot = if is_stdout {
371                &self.stdout_slot
372            } else {
373                &self.stderr_slot
374            };
375            let fd = if let Some(s) = slot {
376                &s.fd
377            } else {
378                return Ok(true);
379            };
380            Self::read_from_slot(
381                &mut self.buffer,
382                fd,
383                is_stdout,
384                &mut self.early_exit,
385                pty_master,
386            )?
387        };
388
389        match read_state {
390            ReadState::Open => Ok(false),
391            ReadState::Paused => Ok(false),
392            ReadState::Eof | ReadState::EarlyExit => {
393                if is_stdout {
394                    self.stdout_slot.take();
395                } else {
396                    self.stderr_slot.take();
397                }
398                Ok(true)
399            }
400        }
401    }
402
403    /// Extract all active slots for cleanup or reactor removal.
404    pub(crate) fn take_all_slots(&mut self) -> Vec<FdSlot> {
405        let mut slots = Vec::new();
406        if let Some(slot) = self.stdin_slot.take() {
407            slots.push(slot);
408        }
409        if let Some(slot) = self.stdout_slot.take() {
410            slots.push(slot);
411        }
412        if let Some(slot) = self.stderr_slot.take() {
413            slots.push(slot);
414        }
415        slots
416    }
417
418    pub(crate) fn register_with_reactor(
419        &mut self,
420        reactor: &mut crate::reactor::Reactor,
421    ) -> Result<(), CoreError> {
422        register_slot(reactor, &mut self.stdin_slot, false, true)?;
423        register_slot(reactor, &mut self.stdout_slot, true, false)?;
424        register_slot(reactor, &mut self.stderr_slot, true, false)?;
425        Ok(())
426    }
427
428    pub(crate) fn stdout_matches(&self, token: Token) -> bool {
429        self.stdout_slot
430            .as_ref()
431            .is_some_and(|slot| slot.token == Some(token))
432    }
433
434    pub(crate) fn stderr_matches(&self, token: Token) -> bool {
435        self.stderr_slot
436            .as_ref()
437            .is_some_and(|slot| slot.token == Some(token))
438    }
439
440    pub(crate) fn stdin_matches(&self, token: Token) -> bool {
441        self.stdin_slot
442            .as_ref()
443            .is_some_and(|slot| slot.token == Some(token))
444    }
445
446    pub(crate) fn drop_stdout(
447        &mut self,
448        reactor: &mut crate::reactor::Reactor,
449    ) -> Result<(), CoreError> {
450        if let Some(slot) = self.stdout_slot.take() {
451            del_slot(reactor, &slot)?;
452        }
453        Ok(())
454    }
455
456    pub(crate) fn drop_stderr(
457        &mut self,
458        reactor: &mut crate::reactor::Reactor,
459    ) -> Result<(), CoreError> {
460        if let Some(slot) = self.stderr_slot.take() {
461            del_slot(reactor, &slot)?;
462        }
463        Ok(())
464    }
465
466    pub(crate) fn drop_stdin(
467        &mut self,
468        reactor: &mut crate::reactor::Reactor,
469    ) -> Result<(), CoreError> {
470        if let Some(slot) = self.stdin_slot.take() {
471            del_slot(reactor, &slot)?;
472        }
473        self.writer.buf = None;
474        Ok(())
475    }
476
477    pub(crate) fn handle_stdout_ready(
478        &mut self,
479        reactor: &mut crate::reactor::Reactor,
480    ) -> Result<(), CoreError> {
481        if let Some(slot) = &self.stdout_slot {
482            let read_state = Self::read_from_slot(
483                &mut self.buffer,
484                &slot.fd,
485                true,
486                &mut self.early_exit,
487                self.pty_master,
488            )?;
489            match read_state {
490                ReadState::Open => {}
491                ReadState::Paused => {
492                    // Sink queue full: remove the fd from the reactor so the
493                    // edge-triggered readiness does not spin the loop; the
494                    // caller re-registers via `resume_stdout` when it has
495                    // drained the queue.
496                    self.pause_stdout(reactor)?;
497                }
498                ReadState::Eof | ReadState::EarlyExit => {
499                    self.drop_stdout(reactor)?;
500                }
501            }
502        }
503        Ok(())
504    }
505
506    pub(crate) fn handle_stderr_ready(
507        &mut self,
508        reactor: &mut crate::reactor::Reactor,
509    ) -> Result<(), CoreError> {
510        if let Some(slot) = &self.stderr_slot {
511            let read_state = Self::read_from_slot(
512                &mut self.buffer,
513                &slot.fd,
514                false,
515                &mut self.early_exit,
516                self.pty_master,
517            )?;
518            match read_state {
519                ReadState::Open => {}
520                ReadState::Paused => {
521                    self.pause_stderr(reactor)?;
522                }
523                ReadState::Eof | ReadState::EarlyExit => {
524                    self.drop_stderr(reactor)?;
525                }
526            }
527        }
528        Ok(())
529    }
530
531    /// Remove the stdout fd from the reactor while its sink queue is full.
532    /// The slot is retained (token cleared) so the stream can be resumed.
533    pub(crate) fn pause_stdout(
534        &mut self,
535        reactor: &mut crate::reactor::Reactor,
536    ) -> Result<(), CoreError> {
537        pause_slot(reactor, &mut self.stdout_slot)
538    }
539
540    /// Remove the stderr fd from the reactor while its sink queue is full.
541    pub(crate) fn pause_stderr(
542        &mut self,
543        reactor: &mut crate::reactor::Reactor,
544    ) -> Result<(), CoreError> {
545        pause_slot(reactor, &mut self.stderr_slot)
546    }
547
548    /// Return whether the stdout stream is paused on a full sink queue.
549    pub fn stdout_paused(&self) -> bool {
550        self.buffer.stdout_paused()
551    }
552
553    /// Return whether the stderr stream is paused on a full sink queue.
554    pub fn stderr_paused(&self) -> bool {
555        self.buffer.stderr_paused()
556    }
557
558    /// Re-deliver the held stdout chunk (if any) and re-register the fd when
559    /// the sink has room again. Returns `true` when the stream is resumed,
560    /// `false` when the sink is still full and the stream stays paused.
561    pub fn resume_stdout(
562        &mut self,
563        reactor: &mut crate::reactor::Reactor,
564    ) -> Result<bool, CoreError> {
565        if !self.buffer.deliver_pending_stdout()? {
566            return Ok(false);
567        }
568        register_slot(reactor, &mut self.stdout_slot, true, false)?;
569        Ok(true)
570    }
571
572    /// Re-deliver the held stderr chunk (if any) and re-register the fd when
573    /// the sink has room again. Returns `true` when the stream is resumed,
574    /// `false` when the sink is still full and the stream stays paused.
575    pub fn resume_stderr(
576        &mut self,
577        reactor: &mut crate::reactor::Reactor,
578    ) -> Result<bool, CoreError> {
579        if !self.buffer.deliver_pending_stderr()? {
580            return Ok(false);
581        }
582        register_slot(reactor, &mut self.stderr_slot, true, false)?;
583        Ok(true)
584    }
585
586    /// Take the un-delivered stdout chunk (streaming mode), if any.
587    pub(crate) fn take_stdout_pending(&mut self) -> Option<Vec<u8>> {
588        self.buffer.take_stdout_pending()
589    }
590
591    /// Take the un-delivered stderr chunk (streaming mode), if any.
592    pub(crate) fn take_stderr_pending(&mut self) -> Option<Vec<u8>> {
593        self.buffer.take_stderr_pending()
594    }
595
596    pub(crate) fn handle_stdin_writable(
597        &mut self,
598        reactor: &mut crate::reactor::Reactor,
599    ) -> Result<(), CoreError> {
600        if let Some(slot) = &self.stdin_slot {
601            let done = self.writer.write_to_fd(&slot.fd)?;
602            if done {
603                self.drop_stdin(reactor)?;
604            }
605        }
606        Ok(())
607    }
608
609    /// Consume the state and return (stdout, stderr) buffers.
610    pub fn into_parts(mut self) -> (Vec<u8>, Vec<u8>) {
611        let (stdout, stderr, _, _) = std::mem::take(&mut self.buffer).into_parts();
612        (stdout, stderr)
613    }
614
615    /// Return whether the combined stdout+stderr output limit was exceeded.
616    #[inline(always)]
617    pub fn output_limit_exceeded(&self) -> bool {
618        self.buffer.output_limit_exceeded()
619    }
620
621    /// Return whether stdout was explicitly stopped by the early-exit predicate.
622    #[inline(always)]
623    pub fn stdout_early_exited(&self) -> bool {
624        self.buffer.stdout_early_exited()
625    }
626
627    /// Consume the state and return buffers plus drain flags.
628    pub(crate) fn into_parts_with_state(mut self) -> (Vec<u8>, Vec<u8>, bool, bool) {
629        std::mem::take(&mut self.buffer).into_parts()
630    }
631}
632
633/// Register one slot with the given per-direction interest, leaving it in
634/// place on failure. When the fd is already registered the interest is updated
635/// with `EPOLL_CTL_MOD` (direction-preserving: pause/resume toggle ONE
636/// registration instead of del + add), which re-arms edge-triggered
637/// readiness for the same token.
638fn register_slot(
639    reactor: &mut crate::reactor::Reactor,
640    slot: &mut Option<FdSlot>,
641    readable: bool,
642    writable: bool,
643) -> Result<(), CoreError> {
644    let Some(s) = slot.as_mut() else {
645        return Ok(());
646    };
647    if let Some(token) = s.token {
648        if s.readable == readable && s.writable == writable {
649            return Ok(());
650        }
651        reactor.mod_(&s.fd, token, readable, writable)?;
652        s.readable = readable;
653        s.writable = writable;
654        return Ok(());
655    }
656    s.token = Some(reactor.add(&s.fd, readable, writable)?);
657    s.readable = readable;
658    s.writable = writable;
659    Ok(())
660}
661
662/// Remove a slot's fd from the reactor, skipping an already-deregistered
663/// (tokenless) slot. `ENOENT` is tolerated: the fd may already have been
664/// removed by reactor teardown.
665fn del_slot(reactor: &crate::reactor::Reactor, slot: &FdSlot) -> Result<(), CoreError> {
666    if slot.token.is_none() {
667        return Ok(());
668    }
669    match reactor.del(&slot.fd) {
670        Ok(()) => Ok(()),
671        Err(e) if e.raw_os_error() == Some(libc::ENOENT) => Ok(()),
672        Err(e) => Err(e),
673    }
674}
675
676/// Pause a slot's READABLE interest while keeping any writable interest and
677/// the registration itself (direction-preserving `EPOLL_CTL_MOD`). The token
678/// is retained so a later resume re-arms the same registration. `ENOENT` is
679/// tolerated for the teardown race.
680fn pause_slot(
681    reactor: &mut crate::reactor::Reactor,
682    slot: &mut Option<FdSlot>,
683) -> Result<(), CoreError> {
684    let Some(s) = slot.as_mut() else {
685        return Ok(());
686    };
687    let Some(token) = s.token else {
688        return Ok(());
689    };
690    if !s.readable {
691        return Ok(());
692    }
693    match reactor.mod_(&s.fd, token, false, s.writable) {
694        Ok(()) => {
695            s.readable = false;
696            Ok(())
697        }
698        Err(e) if e.raw_os_error() == Some(libc::ENOENT) => {
699            // Registration already gone (teardown race); drop the stale token.
700            s.token = None;
701            s.readable = false;
702            Ok(())
703        }
704        Err(e) => Err(e),
705    }
706}