В экосистеме Cats Effect есть утилита
Hotswap, предназначенная для управления жизненным циклом заменяемых "на горячую" ресурсов через общий ресурсный скоуп.
К сожалению, она непригодна для конкурентного использования: внутренняя стейт-машина не защищена от конкуретных попыток свопа, а релиз заменяемого ресурса не отслеживает его использование другими файберами, что легко приводит к утечке.
Для
trace4cats понадобилась подобная штука, и мы с
Chris Jansen сделали конкуретную обёртку над
Hotswap
, лишённую перечисленных недостатков.
При этом:
- конкуретные свопы (
swap
) блокируют* друг друга, защищая внутренний
Hotswap
;
- каждый доступ (
access
) к текущей версии ресурса контролируется отдельным ресурсным скоупом: если исполнение хотя бы одного файбера находится внутри такого скоупа, ресурс гарантированно не будет финализирован при свопе — своп заблокируется, пока ресурс не будет освобожден всеми пользователями;
- своп не блокируется конкуретными чтениями во время аллокации нового ресурса: как только новый ресурс создан, он может быть прочитан из других файберов;
- конкуретные доступы к ресурсу не блокируют друг друга и практически никогда не блокируется свопом, но всё же есть небольшой шанс обратиться к ресурсу именно в момент перед перезаписью ссылки на него, но не успеть захватить блокировку до того, как это сделает финалайзер в свопе.
* здесь и далее имеется в виду семантическая блокировка
Запаблишили пока отдельной
либой под Scala 2.12, 2.13 и 3, правда только для CE3 🤷🏻♂️ Может быть в будущем оно переедет в
cats.effect.std
. Критика
Hotswap
отражена в этом
issue.
Какие уроки я вынес для себя из этой задачки:
- сложно понять
cancelation и корректно учесть в коде все ситауции с отменой;
- написать такой же хороший код для CE2 сложно: там аллокация ресурса в принципе неотменяема, поэтому нельзя использовать
Resource
для содания и композиции отменяемых скоупов, а в CE3 есть
Poll
(см. разницу между
withPermit
в CE2 и
permit
в CE3 на
Semaphore
);
- написать вообще хороший конкарренси код с первого раза невозможно: мы написали с пятого — и то с между 2-й и 3-й попытками прошло много времени, прежде чем мы обнаружили проблему.
Ещё из приятного: свой первый кросс-релиз под Scala 3 сделал за несколько минут.