1use std::collections::HashMap;
17use std::time::Duration;
18
19use hyperactor::context;
20
21use crate::RdmaManagerActor;
22use crate::RdmaManagerMessageClient;
23use crate::RdmaOp;
24use crate::RdmaOpType;
25use crate::backend::RdmaBackendHandle;
26use crate::local_memory::KeepaliveLocalMemory;
27use crate::rdma_components::RdmaRemoteBuffer;
28
29pub struct RdmaAction {
41 entries: Vec<RdmaOp>,
42 local_claims: HashMap<(usize, usize), RdmaOpType>,
46}
47
48impl Default for RdmaAction {
49 fn default() -> Self {
50 Self::new()
51 }
52}
53
54impl RdmaAction {
55 pub fn new() -> Self {
56 Self {
57 entries: Vec::new(),
58 local_claims: HashMap::new(),
59 }
60 }
61
62 fn is_local_write(op_type: RdmaOpType) -> bool {
65 matches!(op_type, RdmaOpType::ReadIntoLocal)
66 }
67
68 pub fn add_read_into_local(
71 &mut self,
72 remote: RdmaRemoteBuffer,
73 local: KeepaliveLocalMemory,
74 ) -> Result<&mut Self, anyhow::Error> {
75 if local.size() < remote.size {
76 anyhow::bail!(
77 "destination local memory size ({}) must be >= remote buffer size ({})",
78 local.size(),
79 remote.size,
80 );
81 }
82 self.record_claim(local.addr(), local.size(), RdmaOpType::ReadIntoLocal)?;
83 self.entries.push(RdmaOp {
84 op_type: RdmaOpType::ReadIntoLocal,
85 local,
86 remote,
87 });
88 Ok(self)
89 }
90
91 pub fn add_write_from_local(
94 &mut self,
95 remote: RdmaRemoteBuffer,
96 local: KeepaliveLocalMemory,
97 ) -> Result<&mut Self, anyhow::Error> {
98 if local.size() > remote.size {
99 anyhow::bail!(
100 "source local memory size ({}) must be <= remote buffer size ({})",
101 local.size(),
102 remote.size,
103 );
104 }
105 self.record_claim(local.addr(), local.size(), RdmaOpType::WriteFromLocal)?;
106 self.entries.push(RdmaOp {
107 op_type: RdmaOpType::WriteFromLocal,
108 local,
109 remote,
110 });
111 Ok(self)
112 }
113
114 fn record_claim(
115 &mut self,
116 addr: usize,
117 size: usize,
118 op_type: RdmaOpType,
119 ) -> Result<(), anyhow::Error> {
120 let mut start = addr;
121 let mut end = addr.saturating_add(size);
122 let new_is_write = Self::is_local_write(op_type);
123
124 let mut to_remove: Vec<(usize, usize)> = Vec::new();
127 for (&(s, e), &existing) in self.local_claims.iter() {
128 if end <= s || e <= start {
129 continue;
130 }
131 if new_is_write || Self::is_local_write(existing) {
132 anyhow::bail!(
133 "RdmaAction data race: existing {:?} claim at [{:#x}, {:#x}) overlaps new {:?} claim at [{:#x}, {:#x})",
134 existing,
135 s,
136 e,
137 op_type,
138 start,
139 end,
140 );
141 }
142 start = start.min(s);
143 end = end.max(e);
144 to_remove.push((s, e));
145 }
146
147 for k in &to_remove {
148 self.local_claims.remove(k);
149 }
150 self.local_claims.insert((start, end), op_type);
151 Ok(())
152 }
153
154 pub async fn submit(
163 &mut self,
164 client: &(impl context::Actor + Send + Sync),
165 timeout: Duration,
166 ) -> Result<(), anyhow::Error> {
167 if self.entries.is_empty() {
168 return Ok(());
169 }
170
171 let handles = RdmaManagerActor::local_handle(client)
173 .get_backend_handles(client)
174 .await?;
175 let mut buckets: Vec<(RdmaBackendHandle, Vec<RdmaOp>)> =
176 handles.into_iter().map(|h| (h, Vec::new())).collect();
177
178 for entry in &self.entries {
181 let op = RdmaOp {
182 op_type: entry.op_type,
183 local: entry.local.clone(),
184 remote: entry.remote.clone(),
185 };
186 match buckets
187 .iter_mut()
188 .find(|(handle, _)| entry.remote.is_compatible_with(handle))
189 {
190 Some((_, ops)) => ops.push(op),
191 None => anyhow::bail!("no compatible RDMA backend for buffer: {:?}", entry.remote),
192 }
193 }
194
195 let pending = buckets.into_iter().filter(|(_, ops)| !ops.is_empty()).map(
198 |(handle, ops)| async move {
199 let name = handle.backend_name();
200 handle
201 .submit(client, ops, timeout)
202 .await
203 .map_err(|e| format!("({name}) {e}"))
204 },
205 );
206 let errors: Vec<String> = futures::future::join_all(pending)
207 .await
208 .into_iter()
209 .filter_map(Result::err)
210 .collect();
211 if errors.is_empty() {
212 Ok(())
213 } else {
214 anyhow::bail!(
215 "RDMA submit failed on {} backend(s):\n{}",
216 errors.len(),
217 errors.join("\n")
218 )
219 }
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 fn write_claim(action: &mut RdmaAction, addr: usize, size: usize) -> Result<(), anyhow::Error> {
229 action.record_claim(addr, size, RdmaOpType::ReadIntoLocal)
230 }
231
232 fn read_claim(action: &mut RdmaAction, addr: usize, size: usize) -> Result<(), anyhow::Error> {
234 action.record_claim(addr, size, RdmaOpType::WriteFromLocal)
235 }
236
237 #[test]
238 fn disjoint_writes_ok() {
239 let mut a = RdmaAction::new();
240 write_claim(&mut a, 0x1000, 0x100).unwrap();
241 write_claim(&mut a, 0x2000, 0x100).unwrap();
242 assert_eq!(a.local_claims.len(), 2);
243 }
244
245 #[test]
246 fn overlapping_writes_error() {
247 let mut a = RdmaAction::new();
248 write_claim(&mut a, 0x1000, 0x100).unwrap();
249 let err = write_claim(&mut a, 0x1080, 0x100).unwrap_err();
250 assert!(err.to_string().contains("data race"));
251 }
252
253 #[test]
254 fn overlapping_reads_merge() {
255 let mut a = RdmaAction::new();
256 read_claim(&mut a, 0x1000, 0x100).unwrap();
257 read_claim(&mut a, 0x1080, 0x100).unwrap();
258 assert_eq!(a.local_claims.len(), 1);
259 let (&(start, end), kind) = a.local_claims.iter().next().unwrap();
260 assert_eq!(start, 0x1000);
261 assert_eq!(end, 0x1180);
262 assert_eq!(*kind, RdmaOpType::WriteFromLocal);
263 }
264
265 #[test]
266 fn cascading_reads_merge() {
267 let mut a = RdmaAction::new();
270 read_claim(&mut a, 0x1000, 0x100).unwrap();
271 read_claim(&mut a, 0x1200, 0x100).unwrap();
272 assert_eq!(a.local_claims.len(), 2);
273 read_claim(&mut a, 0x1080, 0x200).unwrap();
274 assert_eq!(a.local_claims.len(), 1);
275 let (&(start, end), _) = a.local_claims.iter().next().unwrap();
276 assert_eq!(start, 0x1000);
277 assert_eq!(end, 0x1300);
278 }
279
280 #[test]
281 fn write_vs_read_errors() {
282 let mut a = RdmaAction::new();
283 read_claim(&mut a, 0x1000, 0x100).unwrap();
284 let err = write_claim(&mut a, 0x1080, 0x100).unwrap_err();
285 assert!(err.to_string().contains("data race"));
286 }
287
288 #[test]
289 fn read_vs_write_errors() {
290 let mut a = RdmaAction::new();
291 write_claim(&mut a, 0x1000, 0x100).unwrap();
292 let err = read_claim(&mut a, 0x1080, 0x100).unwrap_err();
293 assert!(err.to_string().contains("data race"));
294 }
295
296 #[test]
297 fn touching_ranges_do_not_overlap() {
298 let mut a = RdmaAction::new();
300 write_claim(&mut a, 0x1000, 0x100).unwrap();
301 write_claim(&mut a, 0x1100, 0x100).unwrap();
302 assert_eq!(a.local_claims.len(), 2);
303 }
304}