1#![cfg_attr(docsrs, feature(doc_cfg))]
21#![deny(missing_docs)]
22
23use std::future::Future;
24use std::sync::Arc;
25use std::time::Duration;
26
27use opendal_core::raw::*;
28use opendal_core::*;
29
30#[derive(Clone)]
120pub struct TimeoutLayer {
121 timeout: Duration,
122 io_timeout: Duration,
123}
124
125impl Default for TimeoutLayer {
126 fn default() -> Self {
127 Self {
128 timeout: Duration::from_secs(60),
129 io_timeout: Duration::from_secs(10),
130 }
131 }
132}
133
134impl TimeoutLayer {
135 pub fn new() -> Self {
137 Self::default()
138 }
139
140 pub fn with_timeout(mut self, timeout: Duration) -> Self {
144 self.timeout = timeout;
145 self
146 }
147
148 pub fn with_io_timeout(mut self, timeout: Duration) -> Self {
152 self.io_timeout = timeout;
153 self
154 }
155}
156
157impl<A: Access> Layer<A> for TimeoutLayer {
158 type LayeredAccess = TimeoutAccessor<A>;
159
160 fn layer(&self, inner: A) -> Self::LayeredAccess {
161 let info = inner.info();
162 info.update_executor(|exec| {
163 Executor::with(TimeoutExecutor::new(exec.into_inner(), self.io_timeout))
164 });
165
166 TimeoutAccessor {
167 inner,
168
169 timeout: self.timeout,
170 io_timeout: self.io_timeout,
171 }
172 }
173}
174
175#[doc(hidden)]
176#[derive(Debug)]
177pub struct TimeoutAccessor<A: Access> {
178 inner: A,
179
180 timeout: Duration,
181 io_timeout: Duration,
182}
183
184impl<A: Access> TimeoutAccessor<A> {
185 async fn timeout<F: Future<Output = Result<T>>, T>(&self, op: Operation, fut: F) -> Result<T> {
186 tokio::time::timeout(self.timeout, fut).await.map_err(|_| {
187 Error::new(ErrorKind::Unexpected, "operation timeout reached")
188 .with_operation(op)
189 .with_context("timeout", self.timeout.as_secs_f64().to_string())
190 .set_temporary()
191 })?
192 }
193
194 async fn io_timeout<F: Future<Output = Result<T>>, T>(
195 &self,
196 op: Operation,
197 fut: F,
198 ) -> Result<T> {
199 tokio::time::timeout(self.io_timeout, fut)
200 .await
201 .map_err(|_| {
202 Error::new(ErrorKind::Unexpected, "io timeout reached")
203 .with_operation(op)
204 .with_context("timeout", self.io_timeout.as_secs_f64().to_string())
205 .set_temporary()
206 })?
207 }
208}
209
210impl<A: Access> LayeredAccess for TimeoutAccessor<A> {
211 type Inner = A;
212 type Reader = TimeoutWrapper<A::Reader>;
213 type Writer = TimeoutWrapper<A::Writer>;
214 type Lister = TimeoutWrapper<A::Lister>;
215 type Deleter = TimeoutWrapper<A::Deleter>;
216
217 fn inner(&self) -> &Self::Inner {
218 &self.inner
219 }
220
221 async fn create_dir(&self, path: &str, args: OpCreateDir) -> Result<RpCreateDir> {
222 self.timeout(Operation::CreateDir, self.inner.create_dir(path, args))
223 .await
224 }
225
226 async fn read(&self, path: &str, args: OpRead) -> Result<(RpRead, Self::Reader)> {
227 self.io_timeout(Operation::Read, self.inner.read(path, args))
228 .await
229 .map(|(rp, r)| (rp, TimeoutWrapper::new(r, self.io_timeout)))
230 }
231
232 async fn write(&self, path: &str, args: OpWrite) -> Result<(RpWrite, Self::Writer)> {
233 self.io_timeout(Operation::Write, self.inner.write(path, args))
234 .await
235 .map(|(rp, r)| (rp, TimeoutWrapper::new(r, self.io_timeout)))
236 }
237
238 async fn copy(&self, from: &str, to: &str, args: OpCopy) -> Result<RpCopy> {
239 self.timeout(Operation::Copy, self.inner.copy(from, to, args))
240 .await
241 }
242
243 async fn rename(&self, from: &str, to: &str, args: OpRename) -> Result<RpRename> {
244 self.timeout(Operation::Rename, self.inner.rename(from, to, args))
245 .await
246 }
247
248 async fn stat(&self, path: &str, args: OpStat) -> Result<RpStat> {
249 self.timeout(Operation::Stat, self.inner.stat(path, args))
250 .await
251 }
252
253 async fn delete(&self) -> Result<(RpDelete, Self::Deleter)> {
254 self.timeout(Operation::Delete, self.inner.delete())
255 .await
256 .map(|(rp, r)| (rp, TimeoutWrapper::new(r, self.io_timeout)))
257 }
258
259 async fn list(&self, path: &str, args: OpList) -> Result<(RpList, Self::Lister)> {
260 self.io_timeout(Operation::List, self.inner.list(path, args))
261 .await
262 .map(|(rp, r)| (rp, TimeoutWrapper::new(r, self.io_timeout)))
263 }
264
265 async fn presign(&self, path: &str, args: OpPresign) -> Result<RpPresign> {
266 self.timeout(Operation::Presign, self.inner.presign(path, args))
267 .await
268 }
269}
270
271struct TimeoutExecutor {
272 exec: Arc<dyn Execute>,
273 timeout: Duration,
274}
275
276impl TimeoutExecutor {
277 fn new(exec: Arc<dyn Execute>, timeout: Duration) -> Self {
278 Self { exec, timeout }
279 }
280}
281
282impl Execute for TimeoutExecutor {
283 fn execute(&self, f: BoxedStaticFuture<()>) {
284 self.exec.execute(f)
285 }
286
287 fn timeout(&self) -> Option<BoxedStaticFuture<()>> {
288 Some(Box::pin(tokio::time::sleep(self.timeout)))
289 }
290}
291
292#[doc(hidden)]
293pub struct TimeoutWrapper<R> {
294 inner: R,
295
296 timeout: Duration,
297}
298
299impl<R> TimeoutWrapper<R> {
300 fn new(inner: R, timeout: Duration) -> Self {
301 Self { inner, timeout }
302 }
303
304 #[inline]
305 async fn io_timeout<F: Future<Output = Result<T>>, T>(
306 timeout: Duration,
307 op: &'static str,
308 fut: F,
309 ) -> Result<T> {
310 tokio::time::timeout(timeout, fut).await.map_err(|_| {
311 Error::new(ErrorKind::Unexpected, "io operation timeout reached")
312 .with_operation(op)
313 .with_context("timeout", timeout.as_secs_f64().to_string())
314 .set_temporary()
315 })?
316 }
317}
318
319impl<R: oio::Read> oio::Read for TimeoutWrapper<R> {
320 async fn read(&mut self) -> Result<Buffer> {
321 let fut = self.inner.read();
322 Self::io_timeout(self.timeout, Operation::Read.into_static(), fut).await
323 }
324}
325
326impl<R: oio::Write> oio::Write for TimeoutWrapper<R> {
327 async fn write(&mut self, bs: Buffer) -> Result<()> {
328 let fut = self.inner.write(bs);
329 Self::io_timeout(self.timeout, Operation::Write.into_static(), fut).await
330 }
331
332 async fn close(&mut self) -> Result<Metadata> {
333 let fut = self.inner.close();
334 Self::io_timeout(self.timeout, Operation::Write.into_static(), fut).await
335 }
336
337 async fn abort(&mut self) -> Result<()> {
338 let fut = self.inner.abort();
339 Self::io_timeout(self.timeout, Operation::Write.into_static(), fut).await
340 }
341}
342
343impl<R: oio::List> oio::List for TimeoutWrapper<R> {
344 async fn next(&mut self) -> Result<Option<oio::Entry>> {
345 let fut = self.inner.next();
346 Self::io_timeout(self.timeout, Operation::List.into_static(), fut).await
347 }
348}
349
350impl<R: oio::Delete> oio::Delete for TimeoutWrapper<R> {
351 async fn delete(&mut self, path: &str, args: OpDelete) -> Result<()> {
352 let fut = self.inner.delete(path, args);
353 Self::io_timeout(self.timeout, Operation::Delete.into_static(), fut).await
354 }
355
356 async fn close(&mut self) -> Result<()> {
357 let fut = self.inner.close();
358 Self::io_timeout(self.timeout, Operation::Delete.into_static(), fut).await
359 }
360}
361
362#[cfg(test)]
363mod tests {
364 use std::future::pending;
365
366 use futures::StreamExt;
367 use tokio::time::sleep;
368 use tokio::time::timeout;
369
370 use super::*;
371
372 #[derive(Debug, Clone, Default)]
373 struct MockService;
374
375 impl Access for MockService {
376 type Reader = oio::Reader;
377 type Writer = oio::Writer;
378 type Lister = oio::Lister;
379 type Deleter = oio::Deleter;
380
381 fn info(&self) -> Arc<AccessorInfo> {
382 let am = AccessorInfo::default();
383 am.set_native_capability(Capability {
384 read: true,
385 delete: true,
386 ..Default::default()
387 });
388
389 am.into()
390 }
391
392 async fn read(&self, _: &str, _: OpRead) -> Result<(RpRead, Self::Reader)> {
394 Ok((RpRead::new(), Box::new(MockReader)))
395 }
396
397 async fn delete(&self) -> Result<(RpDelete, Self::Deleter)> {
399 sleep(Duration::from_secs(u64::MAX)).await;
400
401 Ok((RpDelete::default(), Box::new(())))
402 }
403
404 async fn list(&self, _: &str, _: OpList) -> Result<(RpList, Self::Lister)> {
405 Ok((RpList::default(), Box::new(MockLister)))
406 }
407 }
408
409 #[derive(Debug, Clone, Default)]
410 struct MockReader;
411
412 impl oio::Read for MockReader {
413 fn read(&mut self) -> impl Future<Output = Result<Buffer>> {
414 pending()
415 }
416 }
417
418 #[derive(Debug, Clone, Default)]
419 struct MockLister;
420
421 impl oio::List for MockLister {
422 fn next(&mut self) -> impl Future<Output = Result<Option<oio::Entry>>> {
423 pending()
424 }
425 }
426
427 #[derive(Debug, Clone, Default)]
428 struct MockDeleter;
429
430 impl oio::Delete for MockDeleter {
431 fn delete(&mut self, _: &str, _: OpDelete) -> impl Future<Output = Result<()>> {
432 pending()
433 }
434
435 async fn close(&mut self) -> Result<()> {
436 Ok(())
437 }
438 }
439
440 #[tokio::test]
441 async fn test_operation_timeout() {
442 let srv = MockService;
443 let op = Operator::from_inner(Arc::new(srv))
444 .layer(TimeoutLayer::default().with_timeout(Duration::from_secs(1)));
445
446 let fut = async {
447 let res = op.delete("test").await;
448 assert!(res.is_err());
449 let err = res.unwrap_err();
450 assert_eq!(err.kind(), ErrorKind::Unexpected);
451 assert!(err.to_string().contains("timeout"))
452 };
453
454 timeout(Duration::from_secs(2), fut)
455 .await
456 .expect("this test should not exceed 2 seconds")
457 }
458
459 #[tokio::test]
460 async fn test_io_timeout() {
461 let srv = MockService;
462 let op = Operator::from_inner(Arc::new(srv))
463 .layer(TimeoutLayer::default().with_io_timeout(Duration::from_secs(1)));
464
465 let reader = op.reader("test").await.unwrap();
466
467 let res = reader.read(0..4).await;
468 assert!(res.is_err());
469 let err = res.unwrap_err();
470 assert_eq!(err.kind(), ErrorKind::Unexpected);
471 assert!(err.to_string().contains("timeout"))
472 }
473
474 #[tokio::test]
475 async fn test_list_timeout() {
476 let srv = MockService;
477 let op = Operator::from_inner(Arc::new(srv)).layer(
478 TimeoutLayer::default()
479 .with_timeout(Duration::from_secs(1))
480 .with_io_timeout(Duration::from_secs(1)),
481 );
482
483 let mut lister = op.lister("test").await.unwrap();
484
485 let res = lister.next().await.unwrap();
486 assert!(res.is_err());
487 let err = res.unwrap_err();
488 assert_eq!(err.kind(), ErrorKind::Unexpected);
489 assert!(err.to_string().contains("timeout"))
490 }
491
492 #[tokio::test]
493 async fn test_delete_io_timeout() {
494 use oio::Delete;
495
496 let mut deleter = TimeoutWrapper::new(MockDeleter, Duration::from_secs(1));
497
498 let res = deleter.delete("test", OpDelete::default()).await;
499 assert!(res.is_err());
500 let err = res.unwrap_err();
501 assert_eq!(err.kind(), ErrorKind::Unexpected);
502 assert!(err.to_string().contains("timeout"));
503 }
504
505 #[tokio::test]
506 async fn test_list_timeout_raw() {
507 use oio::List;
508
509 let acc = MockService;
510 let timeout_layer = TimeoutLayer::default()
511 .with_timeout(Duration::from_secs(1))
512 .with_io_timeout(Duration::from_secs(1));
513 let timeout_acc = timeout_layer.layer(acc);
514
515 let (_, mut lister) = Access::list(&timeout_acc, "test", OpList::default())
516 .await
517 .unwrap();
518
519 let res = lister.next().await;
520 assert!(res.is_err());
521 let err = res.unwrap_err();
522 assert_eq!(err.kind(), ErrorKind::Unexpected);
523 assert!(err.to_string().contains("timeout"));
524 }
525}