D
Daria’s room
@dariasroom737 подп.
1.2Kпросмотров
21 декабря 2025 г.
Score: 1.3K
Контексты и неочевидности с WithoutCancel Когда мы создаём контекст с отменой: ctx, cancel := context.WithCancel(parentСtx) родительский контекст хранит ссылки на все дочерние контексты с cancel в отдельной мапе map[canceler]struct{} Поэтому, если не отменить дочерний контекст (или его родителя), референс на него продолжит храниться в мапе (и о чем нам потом любезно сообщит go vet). Об этом пишут прямо в описании либы context: Failing to call the CancelFunc leaks the child and its children until the parent is canceled. The go vet tool checks that CancelFuncs are used on all control-flow paths. То есть, чтобы контекст (если он больше не нужен) не оставался достижимым через своего родителя, нужно просто не забывать делать defer cancel() :) Все проще простого, но... Отчасти для решения и этой проблемы в go 1.21 завезли так называемый context.WithoutCancel, который не регистрируется в cancel map родителя и, соответственно, такой контекст не отменяется, когда отменяется родитель. Основная причина создания такого вида контекста, конечно, более благородная (handling of rollback/cleanup operations in the context of an event & handling of long-running operations triggered by an event) и она одновременно решает проблему утечки memory через cancel map, но создаёт новую потенциальную уязвимость В issue #64478 (который все еще открыт) подробно пояснили, что WithoutCancel безопасен только для синхронного использования, а с асинхронной работой есть риск доступа к невалидным данным. Для этого нужно понимать, что любое обращение к Value() через WithoutCancel идёт к родителю. Примеры: Сценарий 1 - точно unsafe: go func() { ctx := context.WithoutCancel(parent) v := somelibrary.FromContext(ctx) // v может быть span, логгер // использование v внутри горутины, контекст передан замыканием }() Сценарий 2 - спорно, так как дочерний контекст создан синхронно, но горутина использует v позже и там может быть указатель на данные из Value() родителя detachedCtx := context.WithoutCancel(parent) v := somelibrary.FromContext(detachedCtx) go func() { // использование v }() Есть две основные ситуации, когда можно использовать значения из контекста, которые уже стадли невалидными: 1 - Внутри функции (например, middleware или основного хендлера) может быть что-то вроде: v := ctx.Value(key) defer v.Close() когда функция завершается и v.Close() делает объект невалидным. И горутина с WithoutCancel будет работать с уже протухшим объектом. 2 - горутина с WithCancel func f(parent context.Context) { child := context.WithCancel(parent) defer child.Cancel() go g(child) // использует child } func g (ctx context.Context) { v := ctx.Value("logger") v.Log("message") }() тут, в отличие от первого примера, объект может стать невалидным из-за Cancel() контекста, а не напрямую из defer v.Close() После я поняла, что окончательно запуталась и в итоге получается, что если не использовать значения родительского контекста через WithoutCancel, то он почти полностью теряет свой смысл. В таком случае проще и честнее создать отдельный контекст, никак не связанный с родителем - например, context.Background() или context.TODO()
1.2K
просмотров
3178
символов
Нет
эмодзи
Нет
медиа

Другие посты @dariasroom

Все посты канала →
Контексты и неочевидности с WithoutCancel Когда мы создаём к — @dariasroom | PostSniper