#prog #rust #rustasync #rustlib #amazingopensource
Вот
Странно, что я раньше про это не рассказывал. Для tokio есть инструментарий, который позволяет собирать информацию об исполнении тасок в программе на Rust на tokio и потом эту информацию отображать. Конкретно tokio-console реализует CLI с TUI, но за счёт формата, определённого отдельно, бекенд для визуализации можно сделать и другой.
Вот
Странно, что я раньше про это не рассказывал. Для tokio есть инструментарий, который позволяет собирать информацию об исполнении тасок в программе на Rust на tokio и потом эту информацию отображать. Конкретно tokio-console реализует CLI с TUI, но за счёт формата, определённого отдельно, бекенд для визуализации можно сделать и другой.
#prog #rust #rustasync #suckassstory
Ask not what the compiler can do for you
TL;DR: из-за того, что
Ask not what the compiler can do for you
TL;DR: из-за того, что
async_trait
боксит футуры и стирает типы, в асинхронном коде, использующем этот крейт, можно организовать безусловную рекурсию, и ни компилятор, ни clippy этого не отловят.GitHub
Ask not what the compiler can do for you
An Open Source Financial Switch to make Payments fast, reliable and affordable - juspay/hyperswitch
Блог*
#prog #rust #rustasync #article Efficient indexing with Quickwit Rust actor framework Или немного о том, почему разработчики Quickwit решили реализовать свой акторный фреймворк. Также в тексте есть ссылка на статью Actors with Tokio, которая описывает, как…
#prog #rust #rustasync
Если вы делаете что-то с акторами в Rust, может быть полезно иметь у актора возможность отправлять сообщения самому себе. К сожалению, способ в лоб — хранить отправляющую половинку канала у себя — приводит к тому, что теперь невозможно полагаться на ошибку получения из канала как на сигнал к прекращению работы актора: Sender у актора удерживает живым весь канал, и потому, если других Sender-ов нет, recv на канале навечно зависает на попытке получить из канала сообщение.
Для решения этой проблемы в tokio есть WeakSender (и WeakUnboundedSender), которые можно считать аналогом
Кстати, эта фича была добавлена по запросу, который как раз ссылается на уже упомянутую в Блог*е статью Actors with tokio. Запрос на аналогичную функциональность для каналов в std есть аж с 2016 года, но до сих пор не реализован. И в crossbeam-channel этого тоже нет, увы.
Если вы делаете что-то с акторами в Rust, может быть полезно иметь у актора возможность отправлять сообщения самому себе. К сожалению, способ в лоб — хранить отправляющую половинку канала у себя — приводит к тому, что теперь невозможно полагаться на ошибку получения из канала как на сигнал к прекращению работы актора: Sender у актора удерживает живым весь канал, и потому, если других Sender-ов нет, recv на канале навечно зависает на попытке получить из канала сообщение.
Для решения этой проблемы в tokio есть WeakSender (и WeakUnboundedSender), которые можно считать аналогом
std::sync::Weak<ChannelInner<T>>
(что, в общем-то, недалеко от истины). Эти Sender-ы позволяют отправлять сообщения в канал при апгрейде до полноценных версий, но сами по себе не поддерживают канал в живом состоянии — идеально для того, чтобы держать в акторе.Кстати, эта фича была добавлена по запросу, который как раз ссылается на уже упомянутую в Блог*е статью Actors with tokio. Запрос на аналогичную функциональность для каналов в std есть аж с 2016 года, но до сих пор не реализован. И в crossbeam-channel этого тоже нет, увы.
docs.rs
WeakSender in tokio::sync::mpsc - Rust
A sender that does not prevent the channel from being closed.
#prog #rust #rustasync #article
The State of Async Rust: Runtimes
Обзор текущей экосистемы Rust в разрезе оснований async. Автор советует по возможности использовать синхронный код.
The choice to use Arc or Mutex might be indicative of a design that hasn't fully embraced the ownership and borrowing principles that Rust emphasizes. It's worth reconsidering if the shared state is genuinely necessary or if there's an alternative design that could minimize or eliminate the need for shared mutable state.
The problem, of course, is that Tokio imposes this design on you. It's not your choice to make.
<...>
Multi-threaded-by-default runtimes cause accidental complexity completely unrelated to the task of writing async code.
The State of Async Rust: Runtimes
Обзор текущей экосистемы Rust в разрезе оснований async. Автор советует по возможности использовать синхронный код.
The choice to use Arc or Mutex might be indicative of a design that hasn't fully embraced the ownership and borrowing principles that Rust emphasizes. It's worth reconsidering if the shared state is genuinely necessary or if there's an alternative design that could minimize or eliminate the need for shared mutable state.
The problem, of course, is that Tokio imposes this design on you. It's not your choice to make.
<...>
Multi-threaded-by-default runtimes cause accidental complexity completely unrelated to the task of writing async code.
Corrode Rust Consulting
The State of Async Rust: Runtimes | corrode Rust Consulting
Recently, I found myself returning to a compelling series of blog posts titled
Zero-cost futures in Rust
by Aaron Turon about what would become the foundation of Rust’s async ecosyste…
Zero-cost futures in Rust
by Aaron Turon about what would become the foundation of Rust’s async ecosyste…
#prog #rust #rustasync #article
На этот раз — целая серия статей от Cliff L. Biffle. Все из них прямо или косвенно связаны с lilos — подходящая для встраиваемых систем операционная система реального времени, которая использует растовый async для запуска тасок (= приложений в этой OS) и потребляет очень мало ресурсов как в долговременной памяти, так и в оперативной:
This is a wee operating system written to support the
lilos has been deployed in real embedded systems since 2019, running continuously. I've built about a dozen systems around it of varying complexity, on half a dozen varieties of microcontroller. It works pretty okay! Perhaps you will find it useful too.
Первая статья, которую я хочу затронуть — это How to think about `async`/`await` in Rust. В ней рассказывается о принципиальном отличии async fn от обычных функций — о том, что async fn суть машина состояний и потому в случае асинхронных функций контроль над исполнением кода имеет вызывающая сторона — а также немного о том, в какие практические следствия это вытекает.
Вторая статья: Composing concurrency in drivers (An example of why I like async for embedded). На примере наброска I2C-драйвера автор показывает, как можно добавить в код драйвера обработку ошибок и прерываний, не внося изменений в бизнес-логику протокола. Эта возможность весьма существенно полагается на особенности реализации async в Rust. Если быть более конкретным, итоговый код полагается на futures::select_biased!.
Как несложно догадаться, для корректной работы подобного кода исполняемые футуры должны быть cancel safe. Как сказано в документации к tokio::select!:
Cancellation safety can be defined in the following way: If you have a future that has not yet completed, then it must be a no-op to drop that future and recreate it. This definition is motivated by the situation where a
И cancellation safety обладает рядом крайне неприятных свойств:
* Она (или её отсутствие, что немаловажно) редко задокументирована.
* Cancellation unsafety заразительна: если футура не является cancel safe, то поверх неё невозможно сделать cancel safe обёртку.
* Cancellation safety (в отличие от weak exception safety) не композируется: можно взять два куска асинхронного кода, каждый из которых по отдельности cancel safe, и совместить их так, что получится cancel unsafe код.
* Так как cancellation safety является довольно сложным семантическим свойством (и, как справедливо замечают в документации tokio, cancel unsafe код — это не всегда что-то плохое), cancellation safety никак не отслеживается компилятором.
(конкретно последний пункт можно было бы, в теории, починить, используя линейные типы — а не афинные, как сейчас в Rust — но, как писал лодочник, впихнуть их в уже существующий язык с широкой экосистемой весьма сложно)
Имеющуюся плачевную ситуацию можно до какой-то степени улучшить, предоставив набор документированных cancel safe примитивных операций. И вот тут lilos заметно отличается от прочих асинк-экосистем. В статье с провокационным названием Mutex without lock, Queue without push: cancel safety in lilos автор рассказывает, как он смог сделать API в lilos cancel safe, причём в более строгом смысле, чем обычно. Именно, автор вводит несколько уровней cancellaton safety:
На этот раз — целая серия статей от Cliff L. Biffle. Все из них прямо или косвенно связаны с lilos — подходящая для встраиваемых систем операционная система реального времени, которая использует растовый async для запуска тасок (= приложений в этой OS) и потребляет очень мало ресурсов как в долговременной памяти, так и в оперативной:
This is a wee operating system written to support the
async
style of programming in Rust on microcontrollers. It fits in about 2 kiB of Flash and uses about 20 bytes of RAM (before your tasks are added). In that space, you get a full async
runtime with multiple tasks, support for complex concurrency via join
and select
, and a lot of convenient but simple APIs.lilos has been deployed in real embedded systems since 2019, running continuously. I've built about a dozen systems around it of varying complexity, on half a dozen varieties of microcontroller. It works pretty okay! Perhaps you will find it useful too.
Первая статья, которую я хочу затронуть — это How to think about `async`/`await` in Rust. В ней рассказывается о принципиальном отличии async fn от обычных функций — о том, что async fn суть машина состояний и потому в случае асинхронных функций контроль над исполнением кода имеет вызывающая сторона — а также немного о том, в какие практические следствия это вытекает.
Вторая статья: Composing concurrency in drivers (An example of why I like async for embedded). На примере наброска I2C-драйвера автор показывает, как можно добавить в код драйвера обработку ошибок и прерываний, не внося изменений в бизнес-логику протокола. Эта возможность весьма существенно полагается на особенности реализации async в Rust. Если быть более конкретным, итоговый код полагается на futures::select_biased!.
Как несложно догадаться, для корректной работы подобного кода исполняемые футуры должны быть cancel safe. Как сказано в документации к tokio::select!:
Cancellation safety can be defined in the following way: If you have a future that has not yet completed, then it must be a no-op to drop that future and recreate it. This definition is motivated by the situation where a
select!
is used in a loop. Without this guarantee, you would lose your progress when another branch completes and you restart the select!
by going around the loop.И cancellation safety обладает рядом крайне неприятных свойств:
* Она (или её отсутствие, что немаловажно) редко задокументирована.
* Cancellation unsafety заразительна: если футура не является cancel safe, то поверх неё невозможно сделать cancel safe обёртку.
* Cancellation safety (в отличие от weak exception safety) не композируется: можно взять два куска асинхронного кода, каждый из которых по отдельности cancel safe, и совместить их так, что получится cancel unsafe код.
* Так как cancellation safety является довольно сложным семантическим свойством (и, как справедливо замечают в документации tokio, cancel unsafe код — это не всегда что-то плохое), cancellation safety никак не отслеживается компилятором.
(конкретно последний пункт можно было бы, в теории, починить, используя линейные типы — а не афинные, как сейчас в Rust — но, как писал лодочник, впихнуть их в уже существующий язык с широкой экосистемой весьма сложно)
Имеющуюся плачевную ситуацию можно до какой-то степени улучшить, предоставив набор документированных cancel safe примитивных операций. И вот тут lilos заметно отличается от прочих асинк-экосистем. В статье с провокационным названием Mutex without lock, Queue without push: cancel safety in lilos автор рассказывает, как он смог сделать API в lilos cancel safe, причём в более строгом смысле, чем обычно. Именно, автор вводит несколько уровней cancellaton safety:
GitHub
GitHub - cbiffle/lilos: A wee async RTOS for Cortex-M
A wee async RTOS for Cortex-M. Contribute to cbiffle/lilos development by creating an account on GitHub.
#prog #rust #rustasync
Хозяйке на заметку
(или "Антон читает за вас документацию tokio")
1. Если вам требуется заспавнить несколько тасок с одинаковыми типами возврата, в количестве, известном только в рантайме, и дождаться результата исполнения каждой из них или только некоторых, то в tokio есть для этого готовый примитив: JoinSet.
Базовый пример использования:
Также можно отменить скопом все таски в наборе через abort_all. Также можно переиспользовать JoinSet, используя detach_all. Это уберёт все таски из
2. Если у нас есть несколько тасок, которые отсылают результаты своей работы через канал, и мы хотим ограничить число одновременно исполняемых тасок, то это можно сделать при помощи канала ограниченной ёмкости и API, описанного в статье Mutex without lock, Queue without push: cancel safety in lilos (о которой я уже рассказывал):
И надо предупредить (хотя навряд ли вы в такое вляпаетесь намеренно): очевидно, если Permit-ов утечёт достаточно много, вы можете оказаться в дурацкой ситуации, когда в канале есть место, но он не работает ни за отправку, ни на получение.
Хозяйке на заметку
(или "Антон читает за вас документацию tokio")
1. Если вам требуется заспавнить несколько тасок с одинаковыми типами возврата, в количестве, известном только в рантайме, и дождаться результата исполнения каждой из них или только некоторых, то в tokio есть для этого готовый примитив: JoinSet.
Базовый пример использования:
use tokio::task::JoinSet;
#[tokio::main]
async fn main() {
let mut set = JoinSet::new();
for i in 0..10 {
// таски спавнятся при помощи spawn, поэтому
// семантика та же: сразу начинают выполняться
// и паникует вне асинхронного контекста tokio
set.spawn(async move { i });
}
let mut seen = [false; 10];
// мы не обязаны получать все таски до конца
while let Some(res) = set.join_next().await {
let idx = res.unwrap();
seen[idx] = true;
}
for i in 0..10 {
assert!(seen[i]);
}
}
Также можно отменить скопом все таски в наборе через abort_all. Также можно переиспользовать JoinSet, используя detach_all. Это уберёт все таски из
JoinSet
, но они продолжат исполняться рантаймом.2. Если у нас есть несколько тасок, которые отсылают результаты своей работы через канал, и мы хотим ограничить число одновременно исполняемых тасок, то это можно сделать при помощи канала ограниченной ёмкости и API, описанного в статье Mutex without lock, Queue without push: cancel safety in lilos (о которой я уже рассказывал):
let (tx, mut rx) = tokio::sync::mpsc::channel(limit);
for _ in 0..n_tasks {
let work = async {
// какая-то полезная работа
};
let tx = tx.clone();
let task = async move {
let send_permit = tx.reserve_owned().unwrap();
let result = work.await;
let _ = send_permit.send(result);
};
tokio::spawn(task);
}
// дропаем свою копию Sender-а, чтобы не заблокироваться
// на чтении из канала, в который никто не пишет
drop(tx);
while let Some(res) = rx.recv().await {
// обрабатываем результат
}
И надо предупредить (хотя навряд ли вы в такое вляпаетесь намеренно): очевидно, если Permit-ов утечёт достаточно много, вы можете оказаться в дурацкой ситуации, когда в канале есть место, но он не работает ни за отправку, ни на получение.
docs.rs
JoinSet in tokio::task - Rust
A collection of tasks spawned on a Tokio runtime.
#prog #rust #rustasync #article
Common Mistakes with Rust Async
Common mistake №0: using async Rust
Ну а если серьёзно — неплохая подборка неочевидных ошибок при использовании async. Правда, если вы читали Блог*, как минимум часть из них вам наверняка будет уже знакома.
Common Mistakes with Rust Async
Qovery
Common Mistakes with Rust Async
At Qovery, we start to have our fair share of Async Rust and to say the least it is not without caveats. Let’s be honest, Async Rust is hard. It has many more rough edges than Sync Rust and requires a different mindset, but it solves a problem space well…
#prog #rust #rustasync #article
Making Async Rust Reliable
О том, чего не хватает текущему асинку в расте.
И нет, речь не о том, что основа асинка сама по себе ненадёжная — скорее о том, что различные фичи имеют неочевидное совместное поведение.
Making Async Rust Reliable
О том, чего не хватает текущему асинку в расте.
И нет, речь не о том, что основа асинка сама по себе ненадёжная — скорее о том, что различные фичи имеют неочевидное совместное поведение.
Tyler Mandry
Making Async Rust Reliable
Last year was an important year for Async Rust, culminating in the release of async fn in traits, one of Rust’s most long-awaited language features. I’m really proud of the work and expertise the Async Working Group has put in to this effort, and for the…