[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-deep-evm-24-propagatsiya-konteksta-async-rust-dedlajny":3},{"article":4,"author":55},{"id":5,"category_id":6,"title":7,"slug":8,"excerpt":9,"content_md":10,"content_html":11,"locale":12,"author_id":13,"published":14,"published_at":15,"meta_title":16,"meta_description":17,"focus_keyword":18,"og_image":19,"canonical_url":19,"robots_meta":20,"created_at":15,"updated_at":15,"tags":21,"category_name":35,"related_articles":36},"d0000000-0000-0000-0000-000000000224","a0000000-0000-0000-0000-000000000016","Deep EVM #24: Пропагация контекста в async Rust — дедлайны, отмена и трейсинг","deep-evm-24-propagatsiya-konteksta-async-rust-dedlajny","Как правильно распространять контекст в async Rust: request-scoped данные, дедлайны, graceful shutdown и распределённый трейсинг.","## Проблема контекста в async Rust\n\nВ синхронном мире контекст запроса живёт в стеке вызовов — каждая функция имеет доступ к данным вызывающей стороны через параметры или thread-local storage. В async Rust всё сложнее: future'ы могут переключаться между потоками, task'и могут создавать дочерние task'и, а один запрос может порождать десятки асинхронных операций.\n\nКак передать request ID, дедлайн, данные аутентификации и трейсинг-контекст через всю цепочку async-вызовов?\n\n## Подход 1: Явная передача через параметры\n\nСамый простой и идиоматичный подход:\n\n```rust\nstruct RequestContext {\n    request_id: String,\n    deadline: Instant,\n    user_id: Option\u003CUuid>,\n    trace_id: String,\n}\n\nasync fn handle_request(\n    ctx: RequestContext,\n    pool: &PgPool,\n) -> Result\u003CResponse> {\n    let articles = fetch_articles(&ctx, pool).await?;\n    let enriched = enrich_articles(&ctx, &articles).await?;\n    Ok(Response::new(enriched))\n}\n\nasync fn fetch_articles(\n    ctx: &RequestContext,\n    pool: &PgPool,\n) -> Result\u003CVec\u003CArticle>> {\n    \u002F\u002F Проверяем дедлайн\n    if Instant::now() > ctx.deadline {\n        return Err(Error::DeadlineExceeded);\n    }\n\n    tracing::info!(\n        request_id = %ctx.request_id,\n        \"Fetching articles\"\n    );\n\n    sqlx::query_as!(Article, \"SELECT * FROM articles\")\n        .fetch_all(pool).await\n        .map_err(Into::into)\n}\n```\n\nПлюсы: прозрачность, compile-time проверка.\nМинусы: загрязняет сигнатуры функций.\n\n## Подход 2: tracing::Span как носитель контекста\n\n`tracing` Span'ы автоматически распространяются через async-вызовы:\n\n```rust\nuse tracing::Instrument;\n\nasync fn handle_request(\n    req: Request,\n) -> Result\u003CResponse> {\n    let span = tracing::info_span!(\n        \"request\",\n        request_id = %Uuid::new_v4(),\n        method = %req.method(),\n        path = %req.uri().path(),\n    );\n\n    async {\n        let articles = fetch_articles().await?;\n        let enriched = enrich_articles(&articles).await?;\n        Ok(Response::new(enriched))\n    }\n    .instrument(span)\n    .await\n}\n```\n\nВсе логи внутри инструментированного блока автоматически включают `request_id`, `method` и `path`. Это работает через все уровни вложенности.\n\n## Подход 3: task_local! для request-scoped данных\n\nДля данных, которые нужны везде, но неудобно передавать через параметры:\n\n```rust\ntokio::task_local! {\n    static REQUEST_ID: String;\n    static DEADLINE: Instant;\n}\n\nasync fn middleware(req: Request, next: Next) -> Response {\n    let request_id = Uuid::new_v4().to_string();\n    let deadline = Instant::now() + Duration::from_secs(30);\n\n    REQUEST_ID.scope(request_id, async {\n        DEADLINE.scope(deadline, async {\n            next.run(req).await\n        }).await\n    }).await\n}\n\n\u002F\u002F В любом месте обработки:\nasync fn deep_function() {\n    let rid = REQUEST_ID.with(|id| id.clone());\n    tracing::info!(request_id = %rid, \"Deep operation\");\n}\n```\n\nПлюсы: не загрязняет сигнатуры.\nМинусы: паника при доступе вне scope, невидимые зависимости.\n\n## Дедлайны и таймауты\n\nДедлайн — это абсолютное время, к которому запрос должен завершиться. В отличие от таймаута (относительное время), дедлайн правильно распространяется:\n\n```rust\nasync fn with_deadline\u003CF, T>(\n    deadline: Instant,\n    future: F,\n) -> Result\u003CT, Error>\nwhere\n    F: Future\u003COutput = Result\u003CT, Error>>,\n{\n    tokio::select! {\n        result = future => result,\n        _ = tokio::time::sleep_until(deadline.into()) => {\n            Err(Error::DeadlineExceeded)\n        }\n    }\n}\n```\n\nИспользование:\n\n```rust\nlet deadline = Instant::now() + Duration::from_secs(5);\nlet result = with_deadline(deadline, async {\n    let a = fetch_from_db(deadline).await?;\n    let b = call_external_api(deadline).await?;\n    Ok((a, b))\n}).await;\n```\n\nКаждая подоперация получает тот же дедлайн и может оценить оставшееся время.\n\n## Graceful отмена задач\n\nВ Rust async отмена происходит через drop Future. Но это может оставить ресурсы в неконсистентном состоянии:\n\n```rust\n\u002F\u002F Используем CancellationToken из tokio-util\nuse tokio_util::sync::CancellationToken;\n\nlet token = CancellationToken::new();\nlet child_token = token.child_token();\n\n\u002F\u002F Дочерняя задача\ntokio::spawn(async move {\n    loop {\n        tokio::select! {\n            _ = child_token.cancelled() => {\n                \u002F\u002F Cleanup\n                tracing::info!(\"Task cancelled, cleaning up\");\n                break;\n            }\n            _ = do_work() => {}\n        }\n    }\n});\n\n\u002F\u002F Отмена всех дочерних задач\ntoken.cancel();\n```\n\n## Распределённый трейсинг с OpenTelemetry\n\nДля микросервисной архитектуры контекст нужно передавать между сервисами:\n\n```rust\nuse opentelemetry::global;\nuse tracing_opentelemetry::OpenTelemetrySpanExt;\n\n\u002F\u002F Извлечение контекста из входящего запроса\nfn extract_context(headers: &HeaderMap) -> Context {\n    global::get_text_map_propagator(|propagator| {\n        propagator.extract(&HeaderExtractor(headers))\n    })\n}\n\n\u002F\u002F Инжекция контекста в исходящий запрос\nfn inject_context(headers: &mut HeaderMap) {\n    global::get_text_map_propagator(|propagator| {\n        propagator.inject_context(\n            &Span::current().context(),\n            &mut HeaderInjector(headers),\n        );\n    });\n}\n```\n\nЭто обеспечивает сквозной трейсинг от HTTP-запроса клиента через все микросервисы до базы данных.\n\n## Middleware для Axum\n\nСоберём всё вместе в Axum-middleware:\n\n```rust\nasync fn context_middleware(\n    req: Request,\n    next: Next,\n) -> Response {\n    let request_id = req.headers()\n        .get(\"x-request-id\")\n        .and_then(|v| v.to_str().ok())\n        .unwrap_or(&Uuid::new_v4().to_string())\n        .to_string();\n\n    let span = tracing::info_span!(\n        \"request\",\n        request_id = %request_id,\n        method = %req.method(),\n        path = %req.uri().path(),\n    );\n\n    async move {\n        let start = Instant::now();\n        let response = next.run(req).await;\n        let duration = start.elapsed();\n\n        tracing::info!(\n            status = %response.status(),\n            duration_ms = %duration.as_millis(),\n            \"Request completed\"\n        );\n        response\n    }\n    .instrument(span)\n    .await\n}\n```\n\n## Заключение\n\nПропагация контекста в async Rust требует осознанного подхода. Для трейсинга используйте `tracing::Span` с `.instrument()`. Для дедлайнов — явную передачу `Instant`. Для отмены — `CancellationToken`. Для распределённых систем — OpenTelemetry. Комбинация этих инструментов обеспечивает полную наблюдаемость и контроль над асинхронными операциями.","\u003Ch2 id=\"async-rust\">Проблема контекста в async Rust\u003C\u002Fh2>\n\u003Cp>В синхронном мире контекст запроса живёт в стеке вызовов — каждая функция имеет доступ к данным вызывающей стороны через параметры или thread-local storage. В async Rust всё сложнее: future’ы могут переключаться между потоками, task’и могут создавать дочерние task’и, а один запрос может порождать десятки асинхронных операций.\u003C\u002Fp>\n\u003Cp>Как передать request ID, дедлайн, данные аутентификации и трейсинг-контекст через всю цепочку async-вызовов?\u003C\u002Fp>\n\u003Ch2 id=\"1\">Подход 1: Явная передача через параметры\u003C\u002Fh2>\n\u003Cp>Самый простой и идиоматичный подход:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">struct RequestContext {\n    request_id: String,\n    deadline: Instant,\n    user_id: Option&lt;Uuid&gt;,\n    trace_id: String,\n}\n\nasync fn handle_request(\n    ctx: RequestContext,\n    pool: &amp;PgPool,\n) -&gt; Result&lt;Response&gt; {\n    let articles = fetch_articles(&amp;ctx, pool).await?;\n    let enriched = enrich_articles(&amp;ctx, &amp;articles).await?;\n    Ok(Response::new(enriched))\n}\n\nasync fn fetch_articles(\n    ctx: &amp;RequestContext,\n    pool: &amp;PgPool,\n) -&gt; Result&lt;Vec&lt;Article&gt;&gt; {\n    \u002F\u002F Проверяем дедлайн\n    if Instant::now() &gt; ctx.deadline {\n        return Err(Error::DeadlineExceeded);\n    }\n\n    tracing::info!(\n        request_id = %ctx.request_id,\n        \"Fetching articles\"\n    );\n\n    sqlx::query_as!(Article, \"SELECT * FROM articles\")\n        .fetch_all(pool).await\n        .map_err(Into::into)\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Плюсы: прозрачность, compile-time проверка.\nМинусы: загрязняет сигнатуры функций.\u003C\u002Fp>\n\u003Ch2 id=\"2-tracing-span\">Подход 2: tracing::Span как носитель контекста\u003C\u002Fh2>\n\u003Cp>\u003Ccode>tracing\u003C\u002Fcode> Span’ы автоматически распространяются через async-вызовы:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">use tracing::Instrument;\n\nasync fn handle_request(\n    req: Request,\n) -&gt; Result&lt;Response&gt; {\n    let span = tracing::info_span!(\n        \"request\",\n        request_id = %Uuid::new_v4(),\n        method = %req.method(),\n        path = %req.uri().path(),\n    );\n\n    async {\n        let articles = fetch_articles().await?;\n        let enriched = enrich_articles(&amp;articles).await?;\n        Ok(Response::new(enriched))\n    }\n    .instrument(span)\n    .await\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Все логи внутри инструментированного блока автоматически включают \u003Ccode>request_id\u003C\u002Fcode>, \u003Ccode>method\u003C\u002Fcode> и \u003Ccode>path\u003C\u002Fcode>. Это работает через все уровни вложенности.\u003C\u002Fp>\n\u003Ch2 id=\"3-task-local-request-scoped\">Подход 3: task_local! для request-scoped данных\u003C\u002Fh2>\n\u003Cp>Для данных, которые нужны везде, но неудобно передавать через параметры:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">tokio::task_local! {\n    static REQUEST_ID: String;\n    static DEADLINE: Instant;\n}\n\nasync fn middleware(req: Request, next: Next) -&gt; Response {\n    let request_id = Uuid::new_v4().to_string();\n    let deadline = Instant::now() + Duration::from_secs(30);\n\n    REQUEST_ID.scope(request_id, async {\n        DEADLINE.scope(deadline, async {\n            next.run(req).await\n        }).await\n    }).await\n}\n\n\u002F\u002F В любом месте обработки:\nasync fn deep_function() {\n    let rid = REQUEST_ID.with(|id| id.clone());\n    tracing::info!(request_id = %rid, \"Deep operation\");\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Плюсы: не загрязняет сигнатуры.\nМинусы: паника при доступе вне scope, невидимые зависимости.\u003C\u002Fp>\n\u003Ch2 id=\"\">Дедлайны и таймауты\u003C\u002Fh2>\n\u003Cp>Дедлайн — это абсолютное время, к которому запрос должен завершиться. В отличие от таймаута (относительное время), дедлайн правильно распространяется:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">async fn with_deadline&lt;F, T&gt;(\n    deadline: Instant,\n    future: F,\n) -&gt; Result&lt;T, Error&gt;\nwhere\n    F: Future&lt;Output = Result&lt;T, Error&gt;&gt;,\n{\n    tokio::select! {\n        result = future =&gt; result,\n        _ = tokio::time::sleep_until(deadline.into()) =&gt; {\n            Err(Error::DeadlineExceeded)\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Использование:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">let deadline = Instant::now() + Duration::from_secs(5);\nlet result = with_deadline(deadline, async {\n    let a = fetch_from_db(deadline).await?;\n    let b = call_external_api(deadline).await?;\n    Ok((a, b))\n}).await;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Каждая подоперация получает тот же дедлайн и может оценить оставшееся время.\u003C\u002Fp>\n\u003Ch2 id=\"graceful\">Graceful отмена задач\u003C\u002Fh2>\n\u003Cp>В Rust async отмена происходит через drop Future. Но это может оставить ресурсы в неконсистентном состоянии:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">\u002F\u002F Используем CancellationToken из tokio-util\nuse tokio_util::sync::CancellationToken;\n\nlet token = CancellationToken::new();\nlet child_token = token.child_token();\n\n\u002F\u002F Дочерняя задача\ntokio::spawn(async move {\n    loop {\n        tokio::select! {\n            _ = child_token.cancelled() =&gt; {\n                \u002F\u002F Cleanup\n                tracing::info!(\"Task cancelled, cleaning up\");\n                break;\n            }\n            _ = do_work() =&gt; {}\n        }\n    }\n});\n\n\u002F\u002F Отмена всех дочерних задач\ntoken.cancel();\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"opentelemetry\">Распределённый трейсинг с OpenTelemetry\u003C\u002Fh2>\n\u003Cp>Для микросервисной архитектуры контекст нужно передавать между сервисами:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">use opentelemetry::global;\nuse tracing_opentelemetry::OpenTelemetrySpanExt;\n\n\u002F\u002F Извлечение контекста из входящего запроса\nfn extract_context(headers: &amp;HeaderMap) -&gt; Context {\n    global::get_text_map_propagator(|propagator| {\n        propagator.extract(&amp;HeaderExtractor(headers))\n    })\n}\n\n\u002F\u002F Инжекция контекста в исходящий запрос\nfn inject_context(headers: &amp;mut HeaderMap) {\n    global::get_text_map_propagator(|propagator| {\n        propagator.inject_context(\n            &amp;Span::current().context(),\n            &amp;mut HeaderInjector(headers),\n        );\n    });\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Это обеспечивает сквозной трейсинг от HTTP-запроса клиента через все микросервисы до базы данных.\u003C\u002Fp>\n\u003Ch2 id=\"middleware-axum\">Middleware для Axum\u003C\u002Fh2>\n\u003Cp>Соберём всё вместе в Axum-middleware:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">async fn context_middleware(\n    req: Request,\n    next: Next,\n) -&gt; Response {\n    let request_id = req.headers()\n        .get(\"x-request-id\")\n        .and_then(|v| v.to_str().ok())\n        .unwrap_or(&amp;Uuid::new_v4().to_string())\n        .to_string();\n\n    let span = tracing::info_span!(\n        \"request\",\n        request_id = %request_id,\n        method = %req.method(),\n        path = %req.uri().path(),\n    );\n\n    async move {\n        let start = Instant::now();\n        let response = next.run(req).await;\n        let duration = start.elapsed();\n\n        tracing::info!(\n            status = %response.status(),\n            duration_ms = %duration.as_millis(),\n            \"Request completed\"\n        );\n        response\n    }\n    .instrument(span)\n    .await\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"\">Заключение\u003C\u002Fh2>\n\u003Cp>Пропагация контекста в async Rust требует осознанного подхода. Для трейсинга используйте \u003Ccode>tracing::Span\u003C\u002Fcode> с \u003Ccode>.instrument()\u003C\u002Fcode>. Для дедлайнов — явную передачу \u003Ccode>Instant\u003C\u002Fcode>. Для отмены — \u003Ccode>CancellationToken\u003C\u002Fcode>. Для распределённых систем — OpenTelemetry. Комбинация этих инструментов обеспечивает полную наблюдаемость и контроль над асинхронными операциями.\u003C\u002Fp>\n","ru","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:23.953110Z","Пропагация контекста в async Rust — дедлайны, отмена и трейсинг","Распространение контекста в async Rust: request-scoped данные, дедлайны, CancellationToken и OpenTelemetry.","async rust контекст трейсинг",null,"index, follow",[22,27,31],{"id":23,"name":24,"slug":25,"created_at":26},"c0000000-0000-0000-0000-000000000012","DevOps","devops","2026-03-28T10:44:21.513630Z",{"id":28,"name":29,"slug":30,"created_at":26},"c0000000-0000-0000-0000-000000000022","Performance","performance",{"id":32,"name":33,"slug":34,"created_at":26},"c0000000-0000-0000-0000-000000000001","Rust","rust","Инженерия",[37,43,49],{"id":38,"title":39,"slug":40,"excerpt":41,"locale":12,"category_name":35,"published_at":42},"d0200000-0000-0000-0000-000000000013","Почему Бали становится хабом импакт-технологий Юго-Восточной Азии в 2026 году","pochemu-bali-stanovitsya-khabom-impakt-tekhnologiy-2026","Бали занимает 16-е место среди стартап-экосистем Юго-Восточной Азии. Растущая концентрация Web3-разработчиков, ИИ-стартапов в области устойчивого развития и компаний в сфере эко-тревел-технологий формирует нишу столицы импакт-технологий региона.","2026-03-28T10:44:37.953039Z",{"id":44,"title":45,"slug":46,"excerpt":47,"locale":12,"category_name":35,"published_at":48},"d0200000-0000-0000-0000-000000000012","Защита данных в ASEAN: чек-лист разработчика для мультистранового комплаенса","zashchita-dannykh-asean-chek-list-razrabotchika-komplaens","Семь стран ASEAN имеют собственные законы о защите данных с разными моделями согласия, требованиями к локализации и штрафами. Практический чек-лист для разработчиков мультистрановых приложений.","2026-03-28T10:44:37.944001Z",{"id":50,"title":51,"slug":52,"excerpt":53,"locale":12,"category_name":35,"published_at":54},"d0200000-0000-0000-0000-000000000011","Цифровая трансформация Индонезии на $29 миллиардов: возможности для софтверных компаний","tsifrovaya-transformatsiya-indonezii-29-milliardov-vozmozhnosti-dlya-kompaniy","Рынок IT-услуг Индонезии вырастет с $24,37 млрд в 2025 году до $29,03 млрд в 2026 году. Облачная инфраструктура, искусственный интеллект, электронная коммерция и дата-центры обеспечивают самый быстрый рост в Юго-Восточной Азии.","2026-03-28T10:44:37.917095Z",{"id":13,"name":56,"slug":57,"bio":58,"photo_url":19,"linkedin":19,"role":59,"created_at":60,"updated_at":60},"Open Soft Team","open-soft-team","The engineering team at Open Soft, building premium software solutions from Bali, Indonesia.","Engineering Team","2026-03-28T08:31:22.226811Z"]