TokioMiddleTechnical
Какова возможность Tokio приостанавливать и продвигать время в тестах (tokio::time::pause())?
tokio::time::pause() замораживает внутренние часы Tokio; tokio::time::advance(d).await сдвигает время без реального ожидания, делая тесты с таймерами мгновенными и детерминированными.
Управление временем в тестах Tokio
Tokio предоставляет возможность «заморозить» внутренние часы и вручную продвигать время вперёд без реального ожидания. Это делает тесты с таймаутами, rate-limiter'ами и периодическими задачами мгновенными и детерминированными.
Ключевые функции
tokio::time::pause()— останавливает автоматическое продвижение времени.tokio::time::advance(Duration)— сдвигает внутренние часы вперёд на указанный интервал и будит все таймеры, которые должны были сработать.tokio::time::resume()— возобновляет нормальное течение времени.
Функции работают только в рантайме, собранном с feature test-util. Атрибут #[tokio::test] автоматически подключает этот feature.
Пример: тестирование таймаута
use tokio::time::{self, Duration, Instant};
#[tokio::test]
async fn test_timeout_fires() {
time::pause();
let start = Instant::now();
let result = tokio::time::timeout(
Duration::from_secs(30),
async {
time::sleep(Duration::from_secs(60)).await;
42_u32
},
);
// Продвигаем время на 31 секунду — таймаут должен сработать
time::advance(Duration::from_secs(31)).await;
assert!(result.await.is_err(), "timeout should have fired");
// Реально прошли миллисекунды, а не 31 секунда
assert!(start.elapsed() < Duration::from_secs(1));
}
Пример: периодический tick
use tokio::time::{self, Duration, interval};
#[tokio::test]
async fn test_interval_ticks_three_times() {
time::pause();
let mut ticker = interval(Duration::from_secs(10));
let mut count = 0_u32;
// Первый тик — немедленный
ticker.tick().await;
count += 1;
for _ in 0..2 {
time::advance(Duration::from_secs(10)).await;
ticker.tick().await;
count += 1;
}
assert_eq!(count, 3);
}
Пример: rate-limiter тест
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use tokio::time::{self, Duration};
async fn rate_limited_action(counter: Arc<AtomicU32>) {
counter.fetch_add(1, Ordering::SeqCst);
time::sleep(Duration::from_millis(100)).await;
}
#[tokio::test]
async fn test_only_one_per_100ms() {
time::pause();
let counter = Arc::new(AtomicU32::new(0));
let c = counter.clone();
tokio::spawn(async move {
for _ in 0..5 {
rate_limited_action(c.clone()).await;
}
});
// Даём выполниться первому вызову
time::advance(Duration::from_millis(50)).await;
assert_eq!(counter.load(Ordering::SeqCst), 1);
// Ждём ещё — запускается второй
time::advance(Duration::from_millis(100)).await;
assert_eq!(counter.load(Ordering::SeqCst), 2);
}
Cargo.toml — нужен feature
[dev-dependencies]
tokio = { version = "1", features = ["full", "test-util"] }
Подводные камни
- Требуется flavor current_thread:
pause()работает только в однопоточном рантайме (current_thread, который и является дефолтом для#[tokio::test]). Вmulti_threadвызов паникует. - advance() — async: нужно
.await, иначе задачи не получат шанс выполниться и таймеры не сработают. - Сторонние таймеры не управляются:
std::thread::sleepиstd::time::Instantиспользуют системные часы, не подвластныеpause(). - Забытый resume(): если тест завершился аварийно без
resume(), последующие тесты в том же потоке могут получить замороженное время — изолируйте тесты или используйтеtokio::time::resume()в деструкторе. - auto-advance при отсутствии задач: когда все задачи заблокированы на таймерах, рантайм автоматически прыгает к ближайшему дедлайну — это может удивить, если вы ожидаете явного контроля.
- Не работает без test-util: в production-бинарниках
pause()/advance()не скомпилируются без feature-флага.
Common mistakes
- Отвечать определением без production-сценария.
- Не называть runtime boundary, security boundary или failure mode.
- Игнорировать версию API, observability и тестовую проверку.
What the interviewer is testing
- Объясняет механизм своими словами и без выдуманных API.
- Называет реальные риски, диагностику и критерий корректности.
- Связывает ответ с текущей документацией и миграционными ограничениями.