git_zero_rustix/network/server/
deadline.rs1use core::time::Duration;
3use git_zero::{Error, Result};
4use rustix::{
5 event::{PollFd, PollFlags, Timespec, poll},
6 fd::AsFd,
7 net::{RecvFlags, SendFlags, recv, send, sockopt},
8 time::{ClockId, clock_gettime},
9};
10
11#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
15pub struct RequestDeadlines {
16 pub total: Option<Duration>,
17 pub idle: Option<Duration>,
18}
19
20pub type ResponseDeadlines = RequestDeadlines;
23
24pub(super) struct DeadlineReader<'a, S: AsFd> {
25 socket: &'a S,
26 deadlines: RequestDeadlines,
27 total_end: Option<Duration>,
28 progress: Duration,
29 socket_timeout: Option<Duration>,
30}
31
32fn now() -> Result<Duration> {
33 let time = clock_gettime(ClockId::Monotonic);
34 Ok(Duration::new(
35 time.tv_sec.try_into().map_err(|_| Error::Backend)?,
36 time.tv_nsec.try_into().map_err(|_| Error::Backend)?,
37 ))
38}
39
40fn end(start: Duration, duration: Option<Duration>) -> Result<Option<Duration>> {
41 duration
42 .map(|value| start.checked_add(value).ok_or(Error::InvalidRequest))
43 .transpose()
44}
45
46impl<'a, S: AsFd> DeadlineReader<'a, S> {
47 pub(super) fn new(socket: &'a S, deadlines: RequestDeadlines) -> Result<Self> {
48 Self::configured(socket, deadlines, sockopt::Timeout::Recv)
49 }
50
51 fn configured(
52 socket: &'a S,
53 deadlines: RequestDeadlines,
54 option: sockopt::Timeout,
55 ) -> Result<Self> {
56 if [deadlines.total, deadlines.idle]
57 .into_iter()
58 .flatten()
59 .any(|value| value.is_zero())
60 {
61 return Err(Error::InvalidRequest);
62 }
63 let start = now()?;
64 let total_end = end(start, deadlines.total)?;
65 end(start, deadlines.idle)?;
66 let socket_timeout = sockopt::socket_timeout(socket, option).map_err(|_| Error::Backend)?;
67 Ok(Self {
68 socket,
69 deadlines,
70 total_end,
71 progress: start,
72 socket_timeout,
73 })
74 }
75
76 fn host_end(&self) -> Result<Option<Duration>> {
77 Ok(self
78 .total_end
79 .into_iter()
80 .chain(end(self.progress, self.deadlines.idle)?)
81 .min())
82 }
83
84 pub(super) fn check(&self) -> Result<()> {
85 let time = now()?;
86 if self.host_end()?.is_some_and(|deadline| time >= deadline) {
87 return Err(Error::LimitExceeded);
88 }
89 Ok(())
90 }
91
92 fn ready(&self, flags: PollFlags, call_end: Option<Duration>) -> Result<()> {
93 loop {
94 let time = now()?;
95 let host_end = self.host_end()?;
96 if host_end.is_some_and(|deadline| time >= deadline) {
97 return Err(Error::LimitExceeded);
98 }
99 if call_end.is_some_and(|deadline| time >= deadline) {
100 return Err(Error::Backend);
101 }
102 let deadline = host_end.into_iter().chain(call_end).min();
103 let remaining = deadline.map(|deadline| deadline.saturating_sub(time));
104 let wait = remaining.map(|value| {
107 let millis = value
108 .as_millis()
109 .saturating_add(u128::from(value.subsec_nanos() % 1_000_000 != 0));
110 let millis = millis.clamp(1, i32::MAX as u128) as u64;
111 Timespec {
112 tv_sec: (millis / 1000) as _,
113 tv_nsec: ((millis % 1000) * 1_000_000) as _,
114 }
115 });
116 let mut descriptors = [PollFd::new(self.socket, flags)];
117 match poll(&mut descriptors, wait.as_ref()) {
118 Ok(0) | Err(rustix::io::Errno::INTR) => continue,
119 Ok(_) => return Ok(()),
120 Err(_) => return Err(Error::Backend),
121 }
122 }
123 }
124
125 pub(super) fn receive(&mut self, storage: &mut [u8]) -> Result<usize> {
126 let call_end = end(now()?, self.socket_timeout)?;
127 loop {
128 self.ready(PollFlags::IN, call_end)?;
129 match recv(self.socket, &mut *storage, RecvFlags::DONTWAIT) {
132 Ok((count, _)) => {
133 self.check()?;
134 if count > 0 {
135 self.progress = now()?;
136 }
137 return Ok(count);
138 }
139 Err(rustix::io::Errno::INTR | rustix::io::Errno::AGAIN) => continue,
140 Err(_) => return Err(Error::Backend),
141 }
142 }
143 }
144}
145
146pub(super) struct DeadlineWriter<'a, S: AsFd>(DeadlineReader<'a, S>);
148
149impl<'a, S: AsFd> DeadlineWriter<'a, S> {
150 pub(super) fn new(socket: &'a S, deadlines: ResponseDeadlines) -> Result<Self> {
151 let state = DeadlineReader::configured(socket, deadlines, sockopt::Timeout::Send)?;
152 #[cfg(target_vendor = "apple")]
153 sockopt::set_socket_nosigpipe(socket, true).map_err(|_| Error::Backend)?;
154 Ok(Self(state))
155 }
156}
157
158impl<S: AsFd> git_zero::io::Write for DeadlineWriter<'_, S> {
159 fn write_all(&mut self, mut bytes: &[u8]) -> Result<()> {
160 self.0.check()?;
161 while !bytes.is_empty() {
162 let call_end = end(now()?, self.0.socket_timeout)?;
163 let count = loop {
164 self.0.ready(PollFlags::OUT, call_end)?;
165 #[cfg(target_vendor = "apple")]
166 let flags = SendFlags::DONTWAIT;
167 #[cfg(not(target_vendor = "apple"))]
168 let flags = SendFlags::DONTWAIT | SendFlags::NOSIGNAL;
169 match send(self.0.socket, bytes, flags) {
170 Ok(0) => return Err(Error::Backend),
171 Ok(count) => break count,
172 Err(rustix::io::Errno::INTR | rustix::io::Errno::AGAIN) => continue,
173 Err(_) => return Err(Error::Backend),
174 }
175 };
176 self.0.check()?;
177 self.0.progress = now()?;
178 bytes = &bytes[count..];
179 }
180 Ok(())
181 }
182}