Как устроен context внутри
Вопрос
Как реализованы WithCancel и WithValue внутри пакета context? Почему поиск значения медленный и как отмена доходит до потомков?
Попробуйте сами
Ответьте вслух за 2 минуты, как на собесе. Потом сверьтесь с тезисами: что прозвучало, а что нет.
Показать ответ и разбор
Коротко
Что должно прозвучать в ответе
cancelCtx: мьютекс, лениво создаваемый канал done, мапа детей, ошибка и причина
При создании потомок регистрируется у ближайшего отменяемого предка; если предок чужой реализации, запускается горутина-наблюдатель
cancel закрывает done, рекурсивно отменяет детей и удаляется из родителя
timerCtx добавляет таймер, который вызывает cancel
valueCtx хранит одну пару ключ-значение и ссылку на родителя, Value идёт вверх по цепочке: O(глубины)
Что могут спросить следом
- Почему не стоит делать десятки WithValue подряд?
- Что делает context.AfterFunc внутри?
Разбор
Каждая обёртка это отдельный узел, ссылающийся на родителя. cancelCtx содержит мьютекс, канал done, который создаётся лениво при первом вызове Done, мапу дочерних отменяемых контекстов и ошибку с причиной. При WithCancel новый узел ищет ближайшего отменяемого предка из пакета context и регистрируется в его мапе детей. Если родитель чужой реализации, запускается горутина, которая ждёт parent.Done() и отменяет потомка. Отмена закрывает done, рекурсивно отменяет всех детей и удаляет себя из родителя. timerCtx это cancelCtx плюс time.AfterFunc, который вызовет cancel по дедлайну. valueCtx хранит ровно одну пару ключ-значение, а Value идёт вверх по цепочке, сравнивая ключи, пока не найдёт или не дойдёт до корня. Поэтому длинная цепочка WithValue делает поиск линейным, и в контексте не стоит держать много значений: лучше одна структура с данными запроса.
Ответьте сами, а не прочитайте
В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.