В пятницу классно поразгоняли с ребятами на наших внутренних техпосиделках про переезд на .NET 10.
Хедлайнером у нас был ослепительный @granstel, который перевёл свой сервис на .NET 10 первым (как минимум в Додо а может и в мире, кто знает). Сервис некритичный, но полезный — он является бэком для этой охуенной интерактивной карты. Идеальная площадка для тестов.
Так вот что удалось выяснить в процессе переезда/обсуждений:
1) В целом всё работает
2) Но есть один довольно критичный ишуй
В версии 10.0.100 внезапно сломан хендлинг вложенных try/catch/finally.
Как показано на пояснительном дикпике выше 🍆
Если внутренний finally бросает исключение, то:
внутренний catch не вызывается,
внутренний finally не доходит до конца,
внешний finally не вызывается вообще,
исключение вылетает наружу без обработки.
Довольно жёсткая штука — потому что using и await using разворачиваются компилятором именно во вложенные try/finally. А значит, при dispose может соскочить вся цепочка освобождения ресурсов.
То есть вложенный try/catch в принципе не перехватывает исключение из вложенного finally. А это уже влияет и на using, и на await using.
То есть вот такой код может быть потенциально опасным, если disposableTwo в своем диспоузе кинет исключение
using System;
using System.Threading.Tasks;
await using var disOne = new FirstDisposable("ONE");
await using var disTwo = new SecondDisposable("TWO");
Console.WriteLine("Before throw");
throw new Exception("FUCK");
sealed class FirstDisposable : IAsyncDisposable
{
private readonly string _name;
public FirstDisposable(string name) => _name = name;
public async ValueTask DisposeAsync()
{
Console.WriteLine($"DisposeAsync({_name}) START");
await Task.Delay(10);
Console.WriteLine($"DisposeAsync({_name}) END");
// На .NET 10.0.100 этот DisposeAsync вообще НЕ вызовется,
// потому что исключение из DisposeAsync(TWO) оборвёт цепочку.
Console.WriteLine($"NOTE: DisposeAsync({_name}) WAS CALLED — но это произойдёт только на .NET 8");
}
}
sealed class SecondDisposable : IAsyncDisposable
{
private readonly string _name;
public SecondDisposable(string name) => _name = name;
public ValueTask DisposeAsync()
{
Console.WriteLine($"DisposeAsync({_name}) THROWS — сейчас всё сломается");
throw new Exception($"DisposeAsync boom: {_name}");
}
}
Это утечки коннектов, файлов, сокетов, транзакций — что угодно.
Учитывая, что многие библиотеки используют IAsyncDisposable — в общем может стрельнут.
Все уже пофикшено судя по всему, наверняка заедет в следующую версию.
🚧 Вообще не всем дано переехать сразу.
Например, я попытался перевезти свою Базу знаний на новые рельсы, но столкнулся с тем, что нужный пакет Pomelo.EntityFrameworkCore.MySql пока отсутствует для .NET 10.
Так что переехать сходу смогут не все.
Может, даже и хорошо — с таким багом можно и потерпеть 🎃
