1.82K subscribers
3.08K photos
121 videos
15 files
3.42K links
Блог со звёздочкой.

Много репостов, немножко программирования.

Небольшое прикольное комьюнити: @decltype_chat_ptr_t
Автор: @insert_reference_here
Download Telegram
#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.
#prog #rust #rustasync #article

На этот раз — целая серия статей от 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:
#prog #rust #rustasync

Хозяйке на заметку

(или "Антон читает за вас документацию 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-ов утечёт достаточно много, вы можете оказаться в дурацкой ситуации, когда в канале есть место, но он не работает ни за отправку, ни на получение.
#prog #rust #rustasync #article

Common Mistakes with Rust Async

Common mistake №0: using async Rust

Ну а если серьёзно — неплохая подборка неочевидных ошибок при использовании async. Правда, если вы читали Блог*, как минимум часть из них вам наверняка будет уже знакома.
#prog #rust #rustasync #article

Making Async Rust Reliable

О том, чего не хватает текущему асинку в расте.

И нет, речь не о том, что основа асинка сама по себе ненадёжная — скорее о том, что различные фичи имеют неочевидное совместное поведение.
Блог*
Photo
#prog #rust #rustasync #article

Цитата из статьи лодочника Let futures be futures, в которой он аргументирует против попыток унифицировать синхронный и асинхронный код.
#prog #rust #rustasync #article

How to configure CPU cores to be used in a Tokio application with core_affinity

TL;DR: крейт core_affinity позволяет пинить текущий тред к конкретному ядру, а у билдера рантайма tokio есть метод on_thread_start, который позволяет установить хук, исполняемый при старте воркер-треда рантайма.
#prog #rust #rustasync #article

Async Rust Challenges in Iroh

Хороший обзор текущих проблем с асинком в Rust (особенно разделяю негодование автора по поводу API tokio с эффектом, зависящим от глобального состояния). Вопреки названию, к конкретной кодовой базе содержимое статьи практически не привязано, так что рекомендую к прочтению.
Блог*
#prog #rust #rustasync #article Async Rust Challenges in Iroh Хороший обзор текущих проблем с асинком в Rust (особенно разделяю негодование автора по поводу API tokio с эффектом, зависящим от глобального состояния). Вопреки названию, к конкретной кодовой…
#prog #rust #rustasync

Panic! At The Async Runtime Shutdown

TL;DR: многопоточный рантайм tokio в процессе завершения работы может дропать одни таски одновременно с опросом других, зависящих от них, что может привести к возникновению паник из-за условных unwrap там, где они, вроде как, вообще не должны быть по логике программы.
#prog #rust #rustasync

Async Closures MVP: Call for Testing!

Разработчики Rust призывают тестировать #![feature(async_closure)] и рассказывают о текущих ограничениях этой фичи
#prog #rust #rustasync #article

How big is your future?

О способах отслеживания больших футур, и в рантайме, и на этапе компиляции. Кстати, вы знали, что tokio::spawn автоматически боксит большие футуры, и через tokio-console можно получить информацию об этом?
#prog #rust #rustasync

Async Rust is about concurrency, not (just) performance
(Alternative title: In defense of async (Rust))

TLDR: I think that the primary benefit of async/await is that it lets us concisely express complex concurrency; any (potential) performance improvements are just a second-order effect. We should thus judge async primarily based on how it simplifies our code, not how (or if) it makes the code faster.
#prog #rust #rustasync #rustlib

async-std официально deprecated. Взамен советуют использовать smol и смежные крейты.

ДАВНО ПОРА