[{"data":1,"prerenderedAt":4693},["ShallowReactive",2],{"content:\u002F03-concurrency\u002F03-context":3},{"title":4,"description":5,"path":6,"body":7},"Context в Go","Представьте: пользователь отправил HTTP-запрос, который запускает несколько горутин — одна ходит в базу, другая в Redis, третья вызывает внешний API. Пользователь не дождался и закрыл вкладку. Без механизма отмены все три горутины продолжат работу — тратят ресурсы, держат соединения, делают бессмысленную работу. context.Context — это именно тот механизм, который решает эту проблему.","\u002F03-concurrency\u002F03-context",{"type":8,"value":9,"toc":4668},"minimark",[10,14,23,26,31,37,162,169,267,270,272,276,279,380,383,385,389,392,447,456,458,462,468,734,743,748,877,892,1059,1061,1065,1068,1209,1218,1222,1225,1436,1440,1611,1615,1618,1740,1742,1746,1756,2021,2025,2028,2138,2141,2145,2153,2207,2210,2212,2216,2219,2460,2462,2466,2470,2627,2630,2634,2690,2694,2747,2749,2753,2762,2790,2801,2809,2821,2829,2837,2862,2870,2874,2876,2881,2887,2893,2910,2915,3256,3258,3263,3268,3273,3282,3286,3817,3819,3824,3829,3834,3839,4158,4163,4171,4175,4242,4244,4248,4297,4460,4664],[11,12,4],"h1",{"id":13},"context-в-go",[15,16,17,18,22],"p",{},"Представьте: пользователь отправил HTTP-запрос, который запускает несколько горутин — одна ходит в базу, другая в Redis, третья вызывает внешний API. Пользователь не дождался и закрыл вкладку. Без механизма отмены все три горутины продолжат работу — тратят ресурсы, держат соединения, делают бессмысленную работу. ",[19,20,21],"code",{},"context.Context"," — это именно тот механизм, который решает эту проблему.",[24,25],"hr",{},[27,28,30],"h2",{"id":29},"что-такое-context","Что такое Context",[15,32,33,36],{},[19,34,35],{},"Context"," — это интерфейс из четырёх методов:",[38,39,44],"pre",{"className":40,"code":41,"language":42,"meta":43,"style":43},"language-go shiki shiki-themes github-dark","type Context interface {\n    Done() \u003C-chan struct{}      \u002F\u002F канал, закрывается при отмене\n    Err() error                \u002F\u002F причина отмены (nil если не отменён)\n    Deadline() (time.Time, bool) \u002F\u002F дедлайн если установлен\n    Value(key any) any         \u002F\u002F значение по ключу\n}\n","go","",[19,45,46,66,88,102,132,156],{"__ignoreMap":43},[47,48,51,55,59,62],"span",{"class":49,"line":50},"line",1,[47,52,54],{"class":53},"snl16","type",[47,56,58],{"class":57},"svObZ"," Context",[47,60,61],{"class":53}," interface",[47,63,65],{"class":64},"s95oV"," {\n",[47,67,69,72,75,78,81,84],{"class":49,"line":68},2,[47,70,71],{"class":57},"    Done",[47,73,74],{"class":64},"() ",[47,76,77],{"class":53},"\u003C-chan",[47,79,80],{"class":53}," struct",[47,82,83],{"class":64},"{}      ",[47,85,87],{"class":86},"sAwPA","\u002F\u002F канал, закрывается при отмене\n",[47,89,91,94,96,99],{"class":49,"line":90},3,[47,92,93],{"class":57},"    Err",[47,95,74],{"class":64},[47,97,98],{"class":53},"error",[47,100,101],{"class":86},"                \u002F\u002F причина отмены (nil если не отменён)\n",[47,103,105,108,111,114,117,120,123,126,129],{"class":49,"line":104},4,[47,106,107],{"class":57},"    Deadline",[47,109,110],{"class":64},"() (",[47,112,113],{"class":57},"time",[47,115,116],{"class":64},".",[47,118,119],{"class":57},"Time",[47,121,122],{"class":64},", ",[47,124,125],{"class":53},"bool",[47,127,128],{"class":64},") ",[47,130,131],{"class":86},"\u002F\u002F дедлайн если установлен\n",[47,133,135,138,141,145,148,150,153],{"class":49,"line":134},5,[47,136,137],{"class":57},"    Value",[47,139,140],{"class":64},"(",[47,142,144],{"class":143},"s9osk","key",[47,146,147],{"class":57}," any",[47,149,128],{"class":64},[47,151,152],{"class":57},"any",[47,154,155],{"class":86},"         \u002F\u002F значение по ключу\n",[47,157,159],{"class":49,"line":158},6,[47,160,161],{"class":64},"}\n",[15,163,164,165,168],{},"Контекст всегда передаётся явно, первым аргументом, по соглашению называется ",[19,166,167],{},"ctx",":",[38,170,172],{"className":40,"code":171,"language":42,"meta":43,"style":43},"func ProcessOrder(ctx context.Context, orderID int) error {\n    user, err := fetchUser(ctx, orderID)\n    if err != nil {\n        return err\n    }\n    return saveResult(ctx, user)\n}\n",[19,173,174,207,221,238,246,251,262],{"__ignoreMap":43},[47,175,176,179,182,184,186,189,191,193,195,198,201,203,205],{"class":49,"line":50},[47,177,178],{"class":53},"func",[47,180,181],{"class":57}," ProcessOrder",[47,183,140],{"class":64},[47,185,167],{"class":143},[47,187,188],{"class":57}," context",[47,190,116],{"class":64},[47,192,35],{"class":57},[47,194,122],{"class":64},[47,196,197],{"class":143},"orderID",[47,199,200],{"class":53}," int",[47,202,128],{"class":64},[47,204,98],{"class":53},[47,206,65],{"class":64},[47,208,209,212,215,218],{"class":49,"line":68},[47,210,211],{"class":64},"    user, err ",[47,213,214],{"class":53},":=",[47,216,217],{"class":57}," fetchUser",[47,219,220],{"class":64},"(ctx, orderID)\n",[47,222,223,226,229,232,236],{"class":49,"line":90},[47,224,225],{"class":53},"    if",[47,227,228],{"class":64}," err ",[47,230,231],{"class":53},"!=",[47,233,235],{"class":234},"sDLfK"," nil",[47,237,65],{"class":64},[47,239,240,243],{"class":49,"line":104},[47,241,242],{"class":53},"        return",[47,244,245],{"class":64}," err\n",[47,247,248],{"class":49,"line":134},[47,249,250],{"class":64},"    }\n",[47,252,253,256,259],{"class":49,"line":158},[47,254,255],{"class":53},"    return",[47,257,258],{"class":57}," saveResult",[47,260,261],{"class":64},"(ctx, user)\n",[47,263,265],{"class":49,"line":264},7,[47,266,161],{"class":64},[15,268,269],{},"Это не просто соглашение — это архитектурное решение. Контекст явный, его видно в сигнатуре, и любой читатель кода понимает: функция поддерживает отмену и таймауты.",[24,271],{},[27,273,275],{"id":274},"дерево-контекстов","Дерево контекстов",[15,277,278],{},"Контексты образуют дерево: каждый дочерний контекст наследует свойства родителя. Отмена родителя автоматически отменяет всех потомков — в любую глубину:",[38,280,282],{"className":40,"code":281,"language":42,"meta":43,"style":43},"ctx := context.Background()          \u002F\u002F корень дерева\n\nctxA, cancelA := context.WithCancel(ctx)    \u002F\u002F дочерний A\nctxB, cancelB := context.WithCancel(ctxA)   \u002F\u002F дочерний B от A\nctxC, cancelC := context.WithCancel(ctxA)   \u002F\u002F дочерний C от A\n\ncancelA() \u002F\u002F отменяет A, B и C автоматически\n\u002F\u002F cancelB и cancelC теперь no-op — уже отменены\n",[19,283,284,303,309,327,344,360,364,374],{"__ignoreMap":43},[47,285,286,289,291,294,297,300],{"class":49,"line":50},[47,287,288],{"class":64},"ctx ",[47,290,214],{"class":53},[47,292,293],{"class":64}," context.",[47,295,296],{"class":57},"Background",[47,298,299],{"class":64},"()          ",[47,301,302],{"class":86},"\u002F\u002F корень дерева\n",[47,304,305],{"class":49,"line":68},[47,306,308],{"emptyLinePlaceholder":307},true,"\n",[47,310,311,314,316,318,321,324],{"class":49,"line":90},[47,312,313],{"class":64},"ctxA, cancelA ",[47,315,214],{"class":53},[47,317,293],{"class":64},[47,319,320],{"class":57},"WithCancel",[47,322,323],{"class":64},"(ctx)    ",[47,325,326],{"class":86},"\u002F\u002F дочерний A\n",[47,328,329,332,334,336,338,341],{"class":49,"line":104},[47,330,331],{"class":64},"ctxB, cancelB ",[47,333,214],{"class":53},[47,335,293],{"class":64},[47,337,320],{"class":57},[47,339,340],{"class":64},"(ctxA)   ",[47,342,343],{"class":86},"\u002F\u002F дочерний B от A\n",[47,345,346,349,351,353,355,357],{"class":49,"line":134},[47,347,348],{"class":64},"ctxC, cancelC ",[47,350,214],{"class":53},[47,352,293],{"class":64},[47,354,320],{"class":57},[47,356,340],{"class":64},[47,358,359],{"class":86},"\u002F\u002F дочерний C от A\n",[47,361,362],{"class":49,"line":158},[47,363,308],{"emptyLinePlaceholder":307},[47,365,366,369,371],{"class":49,"line":264},[47,367,368],{"class":57},"cancelA",[47,370,74],{"class":64},[47,372,373],{"class":86},"\u002F\u002F отменяет A, B и C автоматически\n",[47,375,377],{"class":49,"line":376},8,[47,378,379],{"class":86},"\u002F\u002F cancelB и cancelC теперь no-op — уже отменены\n",[15,381,382],{},"Это ключевое свойство: HTTP-сервер может отменить корневой контекст запроса, и все вложенные операции — запросы к БД, внешние вызовы, дочерние горутины — получат сигнал отмены автоматически.",[24,384],{},[27,386,388],{"id":387},"contextbackground-и-contexttodo","context.Background и context.TODO",[15,390,391],{},"Два корневых контекста, которые никогда не отменяются:",[38,393,395],{"className":40,"code":394,"language":42,"meta":43,"style":43},"\u002F\u002F Background — настоящий корень. Используется в main, тестах,\n\u002F\u002F инициализации и как корень для всех остальных контекстов\nctx := context.Background()\n\n\u002F\u002F TODO — заглушка. Используется когда контекст нужен по сигнатуре,\n\u002F\u002F но ещё не определено откуда он должен приходить\nctx := context.TODO()\n",[19,396,397,402,407,420,424,429,434],{"__ignoreMap":43},[47,398,399],{"class":49,"line":50},[47,400,401],{"class":86},"\u002F\u002F Background — настоящий корень. Используется в main, тестах,\n",[47,403,404],{"class":49,"line":68},[47,405,406],{"class":86},"\u002F\u002F инициализации и как корень для всех остальных контекстов\n",[47,408,409,411,413,415,417],{"class":49,"line":90},[47,410,288],{"class":64},[47,412,214],{"class":53},[47,414,293],{"class":64},[47,416,296],{"class":57},[47,418,419],{"class":64},"()\n",[47,421,422],{"class":49,"line":104},[47,423,308],{"emptyLinePlaceholder":307},[47,425,426],{"class":49,"line":134},[47,427,428],{"class":86},"\u002F\u002F TODO — заглушка. Используется когда контекст нужен по сигнатуре,\n",[47,430,431],{"class":49,"line":158},[47,432,433],{"class":86},"\u002F\u002F но ещё не определено откуда он должен приходить\n",[47,435,436,438,440,442,445],{"class":49,"line":264},[47,437,288],{"class":64},[47,439,214],{"class":53},[47,441,293],{"class":64},[47,443,444],{"class":57},"TODO",[47,446,419],{"class":64},[15,448,449,452,453,455],{},[19,450,451],{},"context.TODO()"," — это сигнал команде: \"здесь нужно разобраться с контекстом позже\". Линтеры умеют находить ",[19,454,444],{}," контексты в коде.",[24,457],{},[27,459,461],{"id":460},"withcancel-ручная-отмена","WithCancel — ручная отмена",[15,463,464,465,168],{},"Создаёт контекст с функцией отмены. Отмена происходит явным вызовом ",[19,466,467],{},"cancel",[38,469,472],{"className":40,"code":470,"language":42,"meta":471,"style":43},"func doLongWork(ctx context.Context) error {\n    select {\n    case \u003C-time.After(2 * time.Second): \u002F\u002F работа долгая — 2 секунды\n        return nil\n    case \u003C-ctx.Done(): \u002F\u002F контекст отменили раньше\n        return ctx.Err() \u002F\u002F context.Canceled\n    }\n}\n\nfunc fetchWithCancel() {\n    ctx, cancel := context.WithCancel(context.Background())\n    defer cancel() \u002F\u002F всегда! даже если работа завершилась успешно\n\n    go func() {\n        \u002F\u002F имитируем отмену через секунду\n        time.Sleep(time.Second)\n        cancel()\n    }()\n\n    err := doLongWork(ctx)\n    if err != nil {\n        fmt.Println(err) \u002F\u002F context canceled — отмена пришла раньше, чем завершилась работа\n    }\n}\n","static",[19,473,474,497,504,532,539,557,572,576,580,585,596,616,630,635,646,652,664,672,678,683,696,709,724,729],{"__ignoreMap":43},[47,475,476,478,481,483,485,487,489,491,493,495],{"class":49,"line":50},[47,477,178],{"class":53},[47,479,480],{"class":57}," doLongWork",[47,482,140],{"class":64},[47,484,167],{"class":143},[47,486,188],{"class":57},[47,488,116],{"class":64},[47,490,35],{"class":57},[47,492,128],{"class":64},[47,494,98],{"class":53},[47,496,65],{"class":64},[47,498,499,502],{"class":49,"line":68},[47,500,501],{"class":53},"    select",[47,503,65],{"class":64},[47,505,506,509,512,515,518,520,523,526,529],{"class":49,"line":90},[47,507,508],{"class":53},"    case",[47,510,511],{"class":53}," \u003C-",[47,513,514],{"class":64},"time.",[47,516,517],{"class":57},"After",[47,519,140],{"class":64},[47,521,522],{"class":234},"2",[47,524,525],{"class":53}," *",[47,527,528],{"class":64}," time.Second): ",[47,530,531],{"class":86},"\u002F\u002F работа долгая — 2 секунды\n",[47,533,534,536],{"class":49,"line":104},[47,535,242],{"class":53},[47,537,538],{"class":234}," nil\n",[47,540,541,543,545,548,551,554],{"class":49,"line":134},[47,542,508],{"class":53},[47,544,511],{"class":53},[47,546,547],{"class":64},"ctx.",[47,549,550],{"class":57},"Done",[47,552,553],{"class":64},"(): ",[47,555,556],{"class":86},"\u002F\u002F контекст отменили раньше\n",[47,558,559,561,564,567,569],{"class":49,"line":158},[47,560,242],{"class":53},[47,562,563],{"class":64}," ctx.",[47,565,566],{"class":57},"Err",[47,568,74],{"class":64},[47,570,571],{"class":86},"\u002F\u002F context.Canceled\n",[47,573,574],{"class":49,"line":264},[47,575,250],{"class":64},[47,577,578],{"class":49,"line":376},[47,579,161],{"class":64},[47,581,583],{"class":49,"line":582},9,[47,584,308],{"emptyLinePlaceholder":307},[47,586,588,590,593],{"class":49,"line":587},10,[47,589,178],{"class":53},[47,591,592],{"class":57}," fetchWithCancel",[47,594,595],{"class":64},"() {\n",[47,597,599,602,604,606,608,611,613],{"class":49,"line":598},11,[47,600,601],{"class":64},"    ctx, cancel ",[47,603,214],{"class":53},[47,605,293],{"class":64},[47,607,320],{"class":57},[47,609,610],{"class":64},"(context.",[47,612,296],{"class":57},[47,614,615],{"class":64},"())\n",[47,617,619,622,625,627],{"class":49,"line":618},12,[47,620,621],{"class":53},"    defer",[47,623,624],{"class":57}," cancel",[47,626,74],{"class":64},[47,628,629],{"class":86},"\u002F\u002F всегда! даже если работа завершилась успешно\n",[47,631,633],{"class":49,"line":632},13,[47,634,308],{"emptyLinePlaceholder":307},[47,636,638,641,644],{"class":49,"line":637},14,[47,639,640],{"class":53},"    go",[47,642,643],{"class":53}," func",[47,645,595],{"class":64},[47,647,649],{"class":49,"line":648},15,[47,650,651],{"class":86},"        \u002F\u002F имитируем отмену через секунду\n",[47,653,655,658,661],{"class":49,"line":654},16,[47,656,657],{"class":64},"        time.",[47,659,660],{"class":57},"Sleep",[47,662,663],{"class":64},"(time.Second)\n",[47,665,667,670],{"class":49,"line":666},17,[47,668,669],{"class":57},"        cancel",[47,671,419],{"class":64},[47,673,675],{"class":49,"line":674},18,[47,676,677],{"class":64},"    }()\n",[47,679,681],{"class":49,"line":680},19,[47,682,308],{"emptyLinePlaceholder":307},[47,684,686,689,691,693],{"class":49,"line":685},20,[47,687,688],{"class":64},"    err ",[47,690,214],{"class":53},[47,692,480],{"class":57},[47,694,695],{"class":64},"(ctx)\n",[47,697,699,701,703,705,707],{"class":49,"line":698},21,[47,700,225],{"class":53},[47,702,228],{"class":64},[47,704,231],{"class":53},[47,706,235],{"class":234},[47,708,65],{"class":64},[47,710,712,715,718,721],{"class":49,"line":711},22,[47,713,714],{"class":64},"        fmt.",[47,716,717],{"class":57},"Println",[47,719,720],{"class":64},"(err) ",[47,722,723],{"class":86},"\u002F\u002F context canceled — отмена пришла раньше, чем завершилась работа\n",[47,725,727],{"class":49,"line":726},23,[47,728,250],{"class":64},[47,730,732],{"class":49,"line":731},24,[47,733,161],{"class":64},[15,735,736,739,740,742],{},[19,737,738],{},"defer cancel()"," — обязательное правило. Если не вызвать ",[19,741,467],{},", контекст и все его ресурсы не освободятся до отмены родителя. Это утечка — пусть небольшая, но в долгоживущих сервисах накапливается.",[744,745,747],"h3",{"id":746},"как-проверить-отмену-в-своём-коде","Как проверить отмену в своём коде",[38,749,751],{"className":40,"code":750,"language":42,"meta":43,"style":43},"func doWorkLoop(ctx context.Context) error {\n    for {\n        select {\n        case \u003C-ctx.Done():\n            return ctx.Err() \u002F\u002F context.Canceled или context.DeadlineExceeded\n        default:\n            \u002F\u002F продолжаем работу\n            if err := doStep(); err != nil {\n                return err\n            }\n        }\n    }\n}\n",[19,752,753,776,783,790,804,818,826,831,852,859,864,869,873],{"__ignoreMap":43},[47,754,755,757,760,762,764,766,768,770,772,774],{"class":49,"line":50},[47,756,178],{"class":53},[47,758,759],{"class":57}," doWorkLoop",[47,761,140],{"class":64},[47,763,167],{"class":143},[47,765,188],{"class":57},[47,767,116],{"class":64},[47,769,35],{"class":57},[47,771,128],{"class":64},[47,773,98],{"class":53},[47,775,65],{"class":64},[47,777,778,781],{"class":49,"line":68},[47,779,780],{"class":53},"    for",[47,782,65],{"class":64},[47,784,785,788],{"class":49,"line":90},[47,786,787],{"class":53},"        select",[47,789,65],{"class":64},[47,791,792,795,797,799,801],{"class":49,"line":104},[47,793,794],{"class":53},"        case",[47,796,511],{"class":53},[47,798,547],{"class":64},[47,800,550],{"class":57},[47,802,803],{"class":64},"():\n",[47,805,806,809,811,813,815],{"class":49,"line":134},[47,807,808],{"class":53},"            return",[47,810,563],{"class":64},[47,812,566],{"class":57},[47,814,74],{"class":64},[47,816,817],{"class":86},"\u002F\u002F context.Canceled или context.DeadlineExceeded\n",[47,819,820,823],{"class":49,"line":158},[47,821,822],{"class":53},"        default",[47,824,825],{"class":64},":\n",[47,827,828],{"class":49,"line":264},[47,829,830],{"class":86},"            \u002F\u002F продолжаем работу\n",[47,832,833,836,838,840,843,846,848,850],{"class":49,"line":376},[47,834,835],{"class":53},"            if",[47,837,228],{"class":64},[47,839,214],{"class":53},[47,841,842],{"class":57}," doStep",[47,844,845],{"class":64},"(); err ",[47,847,231],{"class":53},[47,849,235],{"class":234},[47,851,65],{"class":64},[47,853,854,857],{"class":49,"line":582},[47,855,856],{"class":53},"                return",[47,858,245],{"class":64},[47,860,861],{"class":49,"line":587},[47,862,863],{"class":64},"            }\n",[47,865,866],{"class":49,"line":598},[47,867,868],{"class":64},"        }\n",[47,870,871],{"class":49,"line":618},[47,872,250],{"class":64},[47,874,875],{"class":49,"line":632},[47,876,161],{"class":64},[15,878,879,880,883,884,887,888,891],{},"Паттерн ",[19,881,882],{},"select"," с ",[19,885,886],{},"ctx.Done()"," и ",[19,889,890],{},"default"," — стандартный способ встроить поддержку отмены в цикл. Если работа делается не в цикле, а линейно — проверяем перед каждой дорогой операцией:",[38,893,895],{"className":40,"code":894,"language":42,"meta":43,"style":43},"func processOrder(ctx context.Context, id int) error {\n    if err := ctx.Err(); err != nil {\n        return err \u002F\u002F быстрая проверка без select\n    }\n\n    user, err := db.GetUser(ctx, id) \u002F\u002F ctx передаём дальше\n    if err != nil {\n        return err\n    }\n\n    if err := ctx.Err(); err != nil { \u002F\u002F снова проверяем между операциями\n        return err\n    }\n\n    return sendEmail(ctx, user)\n}\n",[19,896,897,927,947,956,960,964,982,994,1000,1004,1008,1032,1038,1042,1046,1055],{"__ignoreMap":43},[47,898,899,901,904,906,908,910,912,914,916,919,921,923,925],{"class":49,"line":50},[47,900,178],{"class":53},[47,902,903],{"class":57}," processOrder",[47,905,140],{"class":64},[47,907,167],{"class":143},[47,909,188],{"class":57},[47,911,116],{"class":64},[47,913,35],{"class":57},[47,915,122],{"class":64},[47,917,918],{"class":143},"id",[47,920,200],{"class":53},[47,922,128],{"class":64},[47,924,98],{"class":53},[47,926,65],{"class":64},[47,928,929,931,933,935,937,939,941,943,945],{"class":49,"line":68},[47,930,225],{"class":53},[47,932,228],{"class":64},[47,934,214],{"class":53},[47,936,563],{"class":64},[47,938,566],{"class":57},[47,940,845],{"class":64},[47,942,231],{"class":53},[47,944,235],{"class":234},[47,946,65],{"class":64},[47,948,949,951,953],{"class":49,"line":90},[47,950,242],{"class":53},[47,952,228],{"class":64},[47,954,955],{"class":86},"\u002F\u002F быстрая проверка без select\n",[47,957,958],{"class":49,"line":104},[47,959,250],{"class":64},[47,961,962],{"class":49,"line":134},[47,963,308],{"emptyLinePlaceholder":307},[47,965,966,968,970,973,976,979],{"class":49,"line":158},[47,967,211],{"class":64},[47,969,214],{"class":53},[47,971,972],{"class":64}," db.",[47,974,975],{"class":57},"GetUser",[47,977,978],{"class":64},"(ctx, id) ",[47,980,981],{"class":86},"\u002F\u002F ctx передаём дальше\n",[47,983,984,986,988,990,992],{"class":49,"line":264},[47,985,225],{"class":53},[47,987,228],{"class":64},[47,989,231],{"class":53},[47,991,235],{"class":234},[47,993,65],{"class":64},[47,995,996,998],{"class":49,"line":376},[47,997,242],{"class":53},[47,999,245],{"class":64},[47,1001,1002],{"class":49,"line":582},[47,1003,250],{"class":64},[47,1005,1006],{"class":49,"line":587},[47,1007,308],{"emptyLinePlaceholder":307},[47,1009,1010,1012,1014,1016,1018,1020,1022,1024,1026,1029],{"class":49,"line":598},[47,1011,225],{"class":53},[47,1013,228],{"class":64},[47,1015,214],{"class":53},[47,1017,563],{"class":64},[47,1019,566],{"class":57},[47,1021,845],{"class":64},[47,1023,231],{"class":53},[47,1025,235],{"class":234},[47,1027,1028],{"class":64}," { ",[47,1030,1031],{"class":86},"\u002F\u002F снова проверяем между операциями\n",[47,1033,1034,1036],{"class":49,"line":618},[47,1035,242],{"class":53},[47,1037,245],{"class":64},[47,1039,1040],{"class":49,"line":632},[47,1041,250],{"class":64},[47,1043,1044],{"class":49,"line":637},[47,1045,308],{"emptyLinePlaceholder":307},[47,1047,1048,1050,1053],{"class":49,"line":648},[47,1049,255],{"class":53},[47,1051,1052],{"class":57}," sendEmail",[47,1054,261],{"class":64},[47,1056,1057],{"class":49,"line":654},[47,1058,161],{"class":64},[24,1060],{},[27,1062,1064],{"id":1063},"withtimeout-и-withdeadline","WithTimeout и WithDeadline",[15,1066,1067],{},"Оба создают контекст, который автоматически отменяется по времени. Разница только в способе задания момента отмены:",[38,1069,1071],{"className":40,"code":1070,"language":42,"meta":43,"style":43},"parent := context.Background()\n\n\u002F\u002F WithTimeout — через продолжительность от текущего момента\nctx, cancel := context.WithTimeout(parent, 5*time.Second)\ndefer cancel()\n_ = ctx\n\n\u002F\u002F WithDeadline — через конкретный момент времени\ndeadline := time.Now().Add(5 * time.Second)\nctx2, cancel2 := context.WithDeadline(parent, deadline)\ndefer cancel2()\n_ = ctx2\n",[19,1072,1073,1086,1090,1095,1119,1128,1139,1143,1148,1176,1191,1200],{"__ignoreMap":43},[47,1074,1075,1078,1080,1082,1084],{"class":49,"line":50},[47,1076,1077],{"class":64},"parent ",[47,1079,214],{"class":53},[47,1081,293],{"class":64},[47,1083,296],{"class":57},[47,1085,419],{"class":64},[47,1087,1088],{"class":49,"line":68},[47,1089,308],{"emptyLinePlaceholder":307},[47,1091,1092],{"class":49,"line":90},[47,1093,1094],{"class":86},"\u002F\u002F WithTimeout — через продолжительность от текущего момента\n",[47,1096,1097,1100,1102,1104,1107,1110,1113,1116],{"class":49,"line":104},[47,1098,1099],{"class":64},"ctx, cancel ",[47,1101,214],{"class":53},[47,1103,293],{"class":64},[47,1105,1106],{"class":57},"WithTimeout",[47,1108,1109],{"class":64},"(parent, ",[47,1111,1112],{"class":234},"5",[47,1114,1115],{"class":53},"*",[47,1117,1118],{"class":64},"time.Second)\n",[47,1120,1121,1124,1126],{"class":49,"line":134},[47,1122,1123],{"class":53},"defer",[47,1125,624],{"class":57},[47,1127,419],{"class":64},[47,1129,1130,1133,1136],{"class":49,"line":158},[47,1131,1132],{"class":64},"_ ",[47,1134,1135],{"class":53},"=",[47,1137,1138],{"class":64}," ctx\n",[47,1140,1141],{"class":49,"line":264},[47,1142,308],{"emptyLinePlaceholder":307},[47,1144,1145],{"class":49,"line":376},[47,1146,1147],{"class":86},"\u002F\u002F WithDeadline — через конкретный момент времени\n",[47,1149,1150,1153,1155,1158,1161,1164,1167,1169,1171,1173],{"class":49,"line":582},[47,1151,1152],{"class":64},"deadline ",[47,1154,214],{"class":53},[47,1156,1157],{"class":64}," time.",[47,1159,1160],{"class":57},"Now",[47,1162,1163],{"class":64},"().",[47,1165,1166],{"class":57},"Add",[47,1168,140],{"class":64},[47,1170,1112],{"class":234},[47,1172,525],{"class":53},[47,1174,1175],{"class":64}," time.Second)\n",[47,1177,1178,1181,1183,1185,1188],{"class":49,"line":587},[47,1179,1180],{"class":64},"ctx2, cancel2 ",[47,1182,214],{"class":53},[47,1184,293],{"class":64},[47,1186,1187],{"class":57},"WithDeadline",[47,1189,1190],{"class":64},"(parent, deadline)\n",[47,1192,1193,1195,1198],{"class":49,"line":598},[47,1194,1123],{"class":53},[47,1196,1197],{"class":57}," cancel2",[47,1199,419],{"class":64},[47,1201,1202,1204,1206],{"class":49,"line":618},[47,1203,1132],{"class":64},[47,1205,1135],{"class":53},[47,1207,1208],{"class":64}," ctx2\n",[15,1210,1211,1214,1215,116],{},[19,1212,1213],{},"WithTimeout(parent, d)"," — это просто синтаксический сахар над ",[19,1216,1217],{},"WithDeadline(parent, time.Now().Add(d))",[744,1219,1221],{"id":1220},"таймауты-в-http-клиенте","Таймауты в HTTP-клиенте",[15,1223,1224],{},"Самый распространённый сценарий — ограничить время на внешний вызов:",[38,1226,1228],{"className":40,"code":1227,"language":42,"meta":43,"style":43},"func callExternalAPI(url string) (*Response, error) {\n    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)\n    defer cancel()\n\n    req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)\n    if err != nil {\n        return nil, err\n    }\n\n    resp, err := http.DefaultClient.Do(req)\n    if err != nil {\n        \u002F\u002F если таймаут — err содержит context.DeadlineExceeded\n        return nil, fmt.Errorf(\"external API call failed: %w\", err)\n    }\n    defer resp.Body.Close()\n\n    \u002F\u002F ...\n}\n",[19,1229,1230,1260,1284,1292,1296,1318,1330,1339,1343,1347,1363,1375,1380,1407,1411,1423,1427,1432],{"__ignoreMap":43},[47,1231,1232,1234,1237,1239,1242,1245,1248,1250,1253,1255,1257],{"class":49,"line":50},[47,1233,178],{"class":53},[47,1235,1236],{"class":57}," callExternalAPI",[47,1238,140],{"class":64},[47,1240,1241],{"class":143},"url",[47,1243,1244],{"class":53}," string",[47,1246,1247],{"class":64},") (",[47,1249,1115],{"class":53},[47,1251,1252],{"class":57},"Response",[47,1254,122],{"class":64},[47,1256,98],{"class":53},[47,1258,1259],{"class":64},") {\n",[47,1261,1262,1264,1266,1268,1270,1272,1274,1277,1280,1282],{"class":49,"line":68},[47,1263,601],{"class":64},[47,1265,214],{"class":53},[47,1267,293],{"class":64},[47,1269,1106],{"class":57},[47,1271,610],{"class":64},[47,1273,296],{"class":57},[47,1275,1276],{"class":64},"(), ",[47,1278,1279],{"class":234},"3",[47,1281,1115],{"class":53},[47,1283,1118],{"class":64},[47,1285,1286,1288,1290],{"class":49,"line":90},[47,1287,621],{"class":53},[47,1289,624],{"class":57},[47,1291,419],{"class":64},[47,1293,1294],{"class":49,"line":104},[47,1295,308],{"emptyLinePlaceholder":307},[47,1297,1298,1301,1303,1306,1309,1312,1315],{"class":49,"line":134},[47,1299,1300],{"class":64},"    req, err ",[47,1302,214],{"class":53},[47,1304,1305],{"class":64}," http.",[47,1307,1308],{"class":57},"NewRequestWithContext",[47,1310,1311],{"class":64},"(ctx, http.MethodGet, url, ",[47,1313,1314],{"class":234},"nil",[47,1316,1317],{"class":64},")\n",[47,1319,1320,1322,1324,1326,1328],{"class":49,"line":158},[47,1321,225],{"class":53},[47,1323,228],{"class":64},[47,1325,231],{"class":53},[47,1327,235],{"class":234},[47,1329,65],{"class":64},[47,1331,1332,1334,1336],{"class":49,"line":264},[47,1333,242],{"class":53},[47,1335,235],{"class":234},[47,1337,1338],{"class":64},", err\n",[47,1340,1341],{"class":49,"line":376},[47,1342,250],{"class":64},[47,1344,1345],{"class":49,"line":582},[47,1346,308],{"emptyLinePlaceholder":307},[47,1348,1349,1352,1354,1357,1360],{"class":49,"line":587},[47,1350,1351],{"class":64},"    resp, err ",[47,1353,214],{"class":53},[47,1355,1356],{"class":64}," http.DefaultClient.",[47,1358,1359],{"class":57},"Do",[47,1361,1362],{"class":64},"(req)\n",[47,1364,1365,1367,1369,1371,1373],{"class":49,"line":598},[47,1366,225],{"class":53},[47,1368,228],{"class":64},[47,1370,231],{"class":53},[47,1372,235],{"class":234},[47,1374,65],{"class":64},[47,1376,1377],{"class":49,"line":618},[47,1378,1379],{"class":86},"        \u002F\u002F если таймаут — err содержит context.DeadlineExceeded\n",[47,1381,1382,1384,1386,1389,1392,1394,1398,1401,1404],{"class":49,"line":632},[47,1383,242],{"class":53},[47,1385,235],{"class":234},[47,1387,1388],{"class":64},", fmt.",[47,1390,1391],{"class":57},"Errorf",[47,1393,140],{"class":64},[47,1395,1397],{"class":1396},"sU2Wk","\"external API call failed: ",[47,1399,1400],{"class":234},"%w",[47,1402,1403],{"class":1396},"\"",[47,1405,1406],{"class":64},", err)\n",[47,1408,1409],{"class":49,"line":637},[47,1410,250],{"class":64},[47,1412,1413,1415,1418,1421],{"class":49,"line":648},[47,1414,621],{"class":53},[47,1416,1417],{"class":64}," resp.Body.",[47,1419,1420],{"class":57},"Close",[47,1422,419],{"class":64},[47,1424,1425],{"class":49,"line":654},[47,1426,308],{"emptyLinePlaceholder":307},[47,1428,1429],{"class":49,"line":666},[47,1430,1431],{"class":86},"    \u002F\u002F ...\n",[47,1433,1434],{"class":49,"line":674},[47,1435,161],{"class":64},[744,1437,1439],{"id":1438},"проверка-причины-отмены","Проверка причины отмены",[38,1441,1443],{"className":40,"code":1442,"language":42,"meta":471,"style":43},"func doWork(ctx context.Context) error {\n    time.Sleep(50 * time.Millisecond)\n    return nil\n}\n\nctx, cancel := context.WithTimeout(context.Background(), time.Second)\ndefer cancel()\n\nerr := doWork(ctx)\nif err != nil {\n    switch ctx.Err() {\n    case context.DeadlineExceeded:\n        fmt.Println(\"таймаут — операция заняла слишком долго\")\n    case context.Canceled:\n        fmt.Println(\"отменено явно через cancel()\")\n    }\n}\n",[19,1444,1445,1468,1485,1491,1495,1499,1516,1524,1528,1539,1552,1563,1570,1583,1590,1603,1607],{"__ignoreMap":43},[47,1446,1447,1449,1452,1454,1456,1458,1460,1462,1464,1466],{"class":49,"line":50},[47,1448,178],{"class":53},[47,1450,1451],{"class":57}," doWork",[47,1453,140],{"class":64},[47,1455,167],{"class":143},[47,1457,188],{"class":57},[47,1459,116],{"class":64},[47,1461,35],{"class":57},[47,1463,128],{"class":64},[47,1465,98],{"class":53},[47,1467,65],{"class":64},[47,1469,1470,1473,1475,1477,1480,1482],{"class":49,"line":68},[47,1471,1472],{"class":64},"    time.",[47,1474,660],{"class":57},[47,1476,140],{"class":64},[47,1478,1479],{"class":234},"50",[47,1481,525],{"class":53},[47,1483,1484],{"class":64}," time.Millisecond)\n",[47,1486,1487,1489],{"class":49,"line":90},[47,1488,255],{"class":53},[47,1490,538],{"class":234},[47,1492,1493],{"class":49,"line":104},[47,1494,161],{"class":64},[47,1496,1497],{"class":49,"line":134},[47,1498,308],{"emptyLinePlaceholder":307},[47,1500,1501,1503,1505,1507,1509,1511,1513],{"class":49,"line":158},[47,1502,1099],{"class":64},[47,1504,214],{"class":53},[47,1506,293],{"class":64},[47,1508,1106],{"class":57},[47,1510,610],{"class":64},[47,1512,296],{"class":57},[47,1514,1515],{"class":64},"(), time.Second)\n",[47,1517,1518,1520,1522],{"class":49,"line":264},[47,1519,1123],{"class":53},[47,1521,624],{"class":57},[47,1523,419],{"class":64},[47,1525,1526],{"class":49,"line":376},[47,1527,308],{"emptyLinePlaceholder":307},[47,1529,1530,1533,1535,1537],{"class":49,"line":582},[47,1531,1532],{"class":64},"err ",[47,1534,214],{"class":53},[47,1536,1451],{"class":57},[47,1538,695],{"class":64},[47,1540,1541,1544,1546,1548,1550],{"class":49,"line":587},[47,1542,1543],{"class":53},"if",[47,1545,228],{"class":64},[47,1547,231],{"class":53},[47,1549,235],{"class":234},[47,1551,65],{"class":64},[47,1553,1554,1557,1559,1561],{"class":49,"line":598},[47,1555,1556],{"class":53},"    switch",[47,1558,563],{"class":64},[47,1560,566],{"class":57},[47,1562,595],{"class":64},[47,1564,1565,1567],{"class":49,"line":618},[47,1566,508],{"class":53},[47,1568,1569],{"class":64}," context.DeadlineExceeded:\n",[47,1571,1572,1574,1576,1578,1581],{"class":49,"line":632},[47,1573,714],{"class":64},[47,1575,717],{"class":57},[47,1577,140],{"class":64},[47,1579,1580],{"class":1396},"\"таймаут — операция заняла слишком долго\"",[47,1582,1317],{"class":64},[47,1584,1585,1587],{"class":49,"line":637},[47,1586,508],{"class":53},[47,1588,1589],{"class":64}," context.Canceled:\n",[47,1591,1592,1594,1596,1598,1601],{"class":49,"line":648},[47,1593,714],{"class":64},[47,1595,717],{"class":57},[47,1597,140],{"class":64},[47,1599,1600],{"class":1396},"\"отменено явно через cancel()\"",[47,1602,1317],{"class":64},[47,1604,1605],{"class":49,"line":654},[47,1606,250],{"class":64},[47,1608,1609],{"class":49,"line":666},[47,1610,161],{"class":64},[744,1612,1614],{"id":1613},"дедлайн-из-родителя-наследуется","Дедлайн из родителя наследуется",[15,1616,1617],{},"Если родительский контекст уже имеет дедлайн — дочерний не может его расширить, только сузить:",[38,1619,1621],{"className":40,"code":1620,"language":42,"meta":43,"style":43},"\u002F\u002F Родитель с дедлайном через 2 секунды\nparent, cancel := context.WithTimeout(context.Background(), 2*time.Second)\ndefer cancel()\n\n\u002F\u002F Попытка дать дочернему 10 секунд — бессмысленно,\n\u002F\u002F родитель отменится через 2 секунды и дочерний тоже\nchild, cancel2 := context.WithTimeout(parent, 10*time.Second)\ndefer cancel2()\n\n\u002F\u002F Дочерний с меньшим таймаутом — имеет смысл\nchild2, cancel3 := context.WithTimeout(parent, 500*time.Millisecond)\ndefer cancel3()\n",[19,1622,1623,1628,1651,1659,1663,1668,1673,1693,1701,1705,1710,1731],{"__ignoreMap":43},[47,1624,1625],{"class":49,"line":50},[47,1626,1627],{"class":86},"\u002F\u002F Родитель с дедлайном через 2 секунды\n",[47,1629,1630,1633,1635,1637,1639,1641,1643,1645,1647,1649],{"class":49,"line":68},[47,1631,1632],{"class":64},"parent, cancel ",[47,1634,214],{"class":53},[47,1636,293],{"class":64},[47,1638,1106],{"class":57},[47,1640,610],{"class":64},[47,1642,296],{"class":57},[47,1644,1276],{"class":64},[47,1646,522],{"class":234},[47,1648,1115],{"class":53},[47,1650,1118],{"class":64},[47,1652,1653,1655,1657],{"class":49,"line":90},[47,1654,1123],{"class":53},[47,1656,624],{"class":57},[47,1658,419],{"class":64},[47,1660,1661],{"class":49,"line":104},[47,1662,308],{"emptyLinePlaceholder":307},[47,1664,1665],{"class":49,"line":134},[47,1666,1667],{"class":86},"\u002F\u002F Попытка дать дочернему 10 секунд — бессмысленно,\n",[47,1669,1670],{"class":49,"line":158},[47,1671,1672],{"class":86},"\u002F\u002F родитель отменится через 2 секунды и дочерний тоже\n",[47,1674,1675,1678,1680,1682,1684,1686,1689,1691],{"class":49,"line":264},[47,1676,1677],{"class":64},"child, cancel2 ",[47,1679,214],{"class":53},[47,1681,293],{"class":64},[47,1683,1106],{"class":57},[47,1685,1109],{"class":64},[47,1687,1688],{"class":234},"10",[47,1690,1115],{"class":53},[47,1692,1118],{"class":64},[47,1694,1695,1697,1699],{"class":49,"line":376},[47,1696,1123],{"class":53},[47,1698,1197],{"class":57},[47,1700,419],{"class":64},[47,1702,1703],{"class":49,"line":582},[47,1704,308],{"emptyLinePlaceholder":307},[47,1706,1707],{"class":49,"line":587},[47,1708,1709],{"class":86},"\u002F\u002F Дочерний с меньшим таймаутом — имеет смысл\n",[47,1711,1712,1715,1717,1719,1721,1723,1726,1728],{"class":49,"line":598},[47,1713,1714],{"class":64},"child2, cancel3 ",[47,1716,214],{"class":53},[47,1718,293],{"class":64},[47,1720,1106],{"class":57},[47,1722,1109],{"class":64},[47,1724,1725],{"class":234},"500",[47,1727,1115],{"class":53},[47,1729,1730],{"class":64},"time.Millisecond)\n",[47,1732,1733,1735,1738],{"class":49,"line":618},[47,1734,1123],{"class":53},[47,1736,1737],{"class":57}," cancel3",[47,1739,419],{"class":64},[24,1741],{},[27,1743,1745],{"id":1744},"withvalue-передача-значений","WithValue — передача значений",[15,1747,1748,1751,1752,1755],{},[19,1749,1750],{},"WithValue"," создаёт контекст, несущий пару ключ-значение. Значение доступно через ",[19,1753,1754],{},"ctx.Value(key)"," в любом дочернем контексте:",[38,1757,1759],{"className":40,"code":1758,"language":42,"meta":43,"style":43},"package main\n\nimport (\n    \"context\"\n    \"fmt\"\n)\n\ntype contextKey string \u002F\u002F собственный тип ключа\n\nconst (\n    requestIDKey contextKey = \"requestID\"\n    userIDKey    contextKey = \"userID\"\n)\n\nfunc handler(ctx context.Context) {\n    reqID, ok := ctx.Value(requestIDKey).(string)\n    if !ok {\n        reqID = \"unknown\"\n    }\n    fmt.Println(\"request:\", reqID)\n}\n\nfunc main() {\n    parent := context.Background()\n    ctx := context.WithValue(parent, requestIDKey, \"abc-123\")\n    _ = userIDKey \u002F\u002F показано выше как пример\n    handler(ctx)\n}\n",[19,1760,1761,1769,1773,1781,1792,1801,1805,1809,1821,1825,1832,1845,1858,1862,1866,1885,1905,1915,1925,1929,1944,1948,1952,1961,1974,1994,2008,2016],{"__ignoreMap":43},[47,1762,1763,1766],{"class":49,"line":50},[47,1764,1765],{"class":53},"package",[47,1767,1768],{"class":57}," main\n",[47,1770,1771],{"class":49,"line":68},[47,1772,308],{"emptyLinePlaceholder":307},[47,1774,1775,1778],{"class":49,"line":90},[47,1776,1777],{"class":53},"import",[47,1779,1780],{"class":64}," (\n",[47,1782,1783,1786,1789],{"class":49,"line":104},[47,1784,1785],{"class":1396},"    \"",[47,1787,1788],{"class":57},"context",[47,1790,1791],{"class":1396},"\"\n",[47,1793,1794,1796,1799],{"class":49,"line":134},[47,1795,1785],{"class":1396},[47,1797,1798],{"class":57},"fmt",[47,1800,1791],{"class":1396},[47,1802,1803],{"class":49,"line":158},[47,1804,1317],{"class":64},[47,1806,1807],{"class":49,"line":264},[47,1808,308],{"emptyLinePlaceholder":307},[47,1810,1811,1813,1816,1818],{"class":49,"line":376},[47,1812,54],{"class":53},[47,1814,1815],{"class":57}," contextKey",[47,1817,1244],{"class":53},[47,1819,1820],{"class":86}," \u002F\u002F собственный тип ключа\n",[47,1822,1823],{"class":49,"line":582},[47,1824,308],{"emptyLinePlaceholder":307},[47,1826,1827,1830],{"class":49,"line":587},[47,1828,1829],{"class":53},"const",[47,1831,1780],{"class":64},[47,1833,1834,1837,1839,1842],{"class":49,"line":598},[47,1835,1836],{"class":234},"    requestIDKey",[47,1838,1815],{"class":57},[47,1840,1841],{"class":53}," =",[47,1843,1844],{"class":1396}," \"requestID\"\n",[47,1846,1847,1850,1853,1855],{"class":49,"line":618},[47,1848,1849],{"class":234},"    userIDKey",[47,1851,1852],{"class":57},"    contextKey",[47,1854,1841],{"class":53},[47,1856,1857],{"class":1396}," \"userID\"\n",[47,1859,1860],{"class":49,"line":632},[47,1861,1317],{"class":64},[47,1863,1864],{"class":49,"line":637},[47,1865,308],{"emptyLinePlaceholder":307},[47,1867,1868,1870,1873,1875,1877,1879,1881,1883],{"class":49,"line":648},[47,1869,178],{"class":53},[47,1871,1872],{"class":57}," handler",[47,1874,140],{"class":64},[47,1876,167],{"class":143},[47,1878,188],{"class":57},[47,1880,116],{"class":64},[47,1882,35],{"class":57},[47,1884,1259],{"class":64},[47,1886,1887,1890,1892,1894,1897,1900,1903],{"class":49,"line":654},[47,1888,1889],{"class":64},"    reqID, ok ",[47,1891,214],{"class":53},[47,1893,563],{"class":64},[47,1895,1896],{"class":57},"Value",[47,1898,1899],{"class":64},"(requestIDKey).(",[47,1901,1902],{"class":53},"string",[47,1904,1317],{"class":64},[47,1906,1907,1909,1912],{"class":49,"line":666},[47,1908,225],{"class":53},[47,1910,1911],{"class":53}," !",[47,1913,1914],{"class":64},"ok {\n",[47,1916,1917,1920,1922],{"class":49,"line":674},[47,1918,1919],{"class":64},"        reqID ",[47,1921,1135],{"class":53},[47,1923,1924],{"class":1396}," \"unknown\"\n",[47,1926,1927],{"class":49,"line":680},[47,1928,250],{"class":64},[47,1930,1931,1934,1936,1938,1941],{"class":49,"line":685},[47,1932,1933],{"class":64},"    fmt.",[47,1935,717],{"class":57},[47,1937,140],{"class":64},[47,1939,1940],{"class":1396},"\"request:\"",[47,1942,1943],{"class":64},", reqID)\n",[47,1945,1946],{"class":49,"line":698},[47,1947,161],{"class":64},[47,1949,1950],{"class":49,"line":711},[47,1951,308],{"emptyLinePlaceholder":307},[47,1953,1954,1956,1959],{"class":49,"line":726},[47,1955,178],{"class":53},[47,1957,1958],{"class":57}," main",[47,1960,595],{"class":64},[47,1962,1963,1966,1968,1970,1972],{"class":49,"line":731},[47,1964,1965],{"class":64},"    parent ",[47,1967,214],{"class":53},[47,1969,293],{"class":64},[47,1971,296],{"class":57},[47,1973,419],{"class":64},[47,1975,1977,1980,1982,1984,1986,1989,1992],{"class":49,"line":1976},25,[47,1978,1979],{"class":64},"    ctx ",[47,1981,214],{"class":53},[47,1983,293],{"class":64},[47,1985,1750],{"class":57},[47,1987,1988],{"class":64},"(parent, requestIDKey, ",[47,1990,1991],{"class":1396},"\"abc-123\"",[47,1993,1317],{"class":64},[47,1995,1997,2000,2002,2005],{"class":49,"line":1996},26,[47,1998,1999],{"class":64},"    _ ",[47,2001,1135],{"class":53},[47,2003,2004],{"class":64}," userIDKey ",[47,2006,2007],{"class":86},"\u002F\u002F показано выше как пример\n",[47,2009,2011,2014],{"class":49,"line":2010},27,[47,2012,2013],{"class":57},"    handler",[47,2015,695],{"class":64},[47,2017,2019],{"class":49,"line":2018},28,[47,2020,161],{"class":64},[744,2022,2024],{"id":2023},"почему-ключ-должен-быть-собственным-типом","Почему ключ должен быть собственным типом",[15,2026,2027],{},"Контекст глобален для цепочки вызовов, и коллизии ключей реальны. Если использовать строку как ключ напрямую — любой пакет может случайно перезаписать значение:",[38,2029,2031],{"className":40,"code":2030,"language":42,"meta":43,"style":43},"\u002F\u002F Плохо: строка как ключ — коллизии между пакетами\nctx = context.WithValue(ctx, \"userID\", 42)\nctx = context.WithValue(ctx, \"userID\", 99) \u002F\u002F перезаписали!\n\n\u002F\u002F Хорошо: собственный тип — гарантированно уникален\ntype myPkg struct{ key string }\nctx = context.WithValue(ctx, myPkg{\"userID\"}, 42)\n",[19,2032,2033,2038,2061,2085,2089,2094,2111],{"__ignoreMap":43},[47,2034,2035],{"class":49,"line":50},[47,2036,2037],{"class":86},"\u002F\u002F Плохо: строка как ключ — коллизии между пакетами\n",[47,2039,2040,2042,2044,2046,2048,2051,2054,2056,2059],{"class":49,"line":68},[47,2041,288],{"class":64},[47,2043,1135],{"class":53},[47,2045,293],{"class":64},[47,2047,1750],{"class":57},[47,2049,2050],{"class":64},"(ctx, ",[47,2052,2053],{"class":1396},"\"userID\"",[47,2055,122],{"class":64},[47,2057,2058],{"class":234},"42",[47,2060,1317],{"class":64},[47,2062,2063,2065,2067,2069,2071,2073,2075,2077,2080,2082],{"class":49,"line":90},[47,2064,288],{"class":64},[47,2066,1135],{"class":53},[47,2068,293],{"class":64},[47,2070,1750],{"class":57},[47,2072,2050],{"class":64},[47,2074,2053],{"class":1396},[47,2076,122],{"class":64},[47,2078,2079],{"class":234},"99",[47,2081,128],{"class":64},[47,2083,2084],{"class":86},"\u002F\u002F перезаписали!\n",[47,2086,2087],{"class":49,"line":104},[47,2088,308],{"emptyLinePlaceholder":307},[47,2090,2091],{"class":49,"line":134},[47,2092,2093],{"class":86},"\u002F\u002F Хорошо: собственный тип — гарантированно уникален\n",[47,2095,2096,2098,2101,2103,2106,2108],{"class":49,"line":158},[47,2097,54],{"class":53},[47,2099,2100],{"class":57}," myPkg",[47,2102,80],{"class":53},[47,2104,2105],{"class":64},"{ key ",[47,2107,1902],{"class":53},[47,2109,2110],{"class":64}," }\n",[47,2112,2113,2115,2117,2119,2121,2123,2126,2129,2131,2134,2136],{"class":49,"line":264},[47,2114,288],{"class":64},[47,2116,1135],{"class":53},[47,2118,293],{"class":64},[47,2120,1750],{"class":57},[47,2122,2050],{"class":64},[47,2124,2125],{"class":57},"myPkg",[47,2127,2128],{"class":64},"{",[47,2130,2053],{"class":1396},[47,2132,2133],{"class":64},"}, ",[47,2135,2058],{"class":234},[47,2137,1317],{"class":64},[15,2139,2140],{},"Собственный неэкспортируемый тип ключа — единственный надёжный способ избежать коллизий.",[744,2142,2144],{"id":2143},"что-передавать-через-контекст","Что передавать через контекст",[15,2146,2147,2148,2152],{},"Context — не замена аргументам функции. Через него передают ",[2149,2150,2151],"strong",{},"cross-cutting concerns"," — данные, сквозные для всего запроса:",[38,2154,2156],{"className":40,"code":2155,"language":42,"meta":43,"style":43},"\u002F\u002F Хорошо: через контекст\n\u002F\u002F - Request ID \u002F Trace ID для логирования и трейсинга\n\u002F\u002F - User ID аутентифицированного пользователя\n\u002F\u002F - Tenant ID в мультитенантных системах\n\u002F\u002F - Deadline \u002F таймаут\n\n\u002F\u002F Плохо: через контекст\n\u002F\u002F - Параметры бизнес-логики (передавайте явными аргументами)\n\u002F\u002F - Опциональные зависимости (инъектируйте через конструктор)\n\u002F\u002F - Ошибки (возвращайте явно)\n",[19,2157,2158,2163,2168,2173,2178,2183,2187,2192,2197,2202],{"__ignoreMap":43},[47,2159,2160],{"class":49,"line":50},[47,2161,2162],{"class":86},"\u002F\u002F Хорошо: через контекст\n",[47,2164,2165],{"class":49,"line":68},[47,2166,2167],{"class":86},"\u002F\u002F - Request ID \u002F Trace ID для логирования и трейсинга\n",[47,2169,2170],{"class":49,"line":90},[47,2171,2172],{"class":86},"\u002F\u002F - User ID аутентифицированного пользователя\n",[47,2174,2175],{"class":49,"line":104},[47,2176,2177],{"class":86},"\u002F\u002F - Tenant ID в мультитенантных системах\n",[47,2179,2180],{"class":49,"line":134},[47,2181,2182],{"class":86},"\u002F\u002F - Deadline \u002F таймаут\n",[47,2184,2185],{"class":49,"line":158},[47,2186,308],{"emptyLinePlaceholder":307},[47,2188,2189],{"class":49,"line":264},[47,2190,2191],{"class":86},"\u002F\u002F Плохо: через контекст\n",[47,2193,2194],{"class":49,"line":376},[47,2195,2196],{"class":86},"\u002F\u002F - Параметры бизнес-логики (передавайте явными аргументами)\n",[47,2198,2199],{"class":49,"line":582},[47,2200,2201],{"class":86},"\u002F\u002F - Опциональные зависимости (инъектируйте через конструктор)\n",[47,2203,2204],{"class":49,"line":587},[47,2205,2206],{"class":86},"\u002F\u002F - Ошибки (возвращайте явно)\n",[15,2208,2209],{},"Если что-то нужно везде в цепочке вызовов запроса — контекст. Если что-то нужно конкретной функции — явный аргумент.",[24,2211],{},[27,2213,2215],{"id":2214},"context-в-http-сервере","Context в HTTP-сервере",[15,2217,2218],{},"Стандартная библиотека уже интегрирует Context: каждый входящий запрос несёт контекст, который отменяется когда клиент разрывает соединение:",[38,2220,2222],{"className":40,"code":2221,"language":42,"meta":43,"style":43},"func orderHandler(w http.ResponseWriter, r *http.Request) {\n    ctx := r.Context() \u002F\u002F контекст запроса — отменится если клиент ушёл\n\n    \u002F\u002F Добавляем request ID для трейсинга\n    reqID := r.Header.Get(\"X-Request-ID\")\n    ctx = context.WithValue(ctx, requestIDKey, reqID)\n\n    \u002F\u002F Ограничиваем время обработки\n    ctx, cancel := context.WithTimeout(ctx, 10*time.Second)\n    defer cancel()\n\n    result, err := processOrder(ctx, r)\n    if err != nil {\n        if errors.Is(err, context.Canceled) {\n            \u002F\u002F клиент ушёл — не нужно возвращать ошибку\n            return\n        }\n        http.Error(w, err.Error(), http.StatusInternalServerError)\n        return\n    }\n\n    json.NewEncoder(w).Encode(result)\n}\n",[19,2223,2224,2261,2277,2281,2286,2306,2319,2323,2328,2346,2354,2358,2370,2382,2396,2401,2406,2410,2426,2431,2435,2439,2456],{"__ignoreMap":43},[47,2225,2226,2228,2231,2233,2236,2239,2241,2244,2246,2249,2251,2254,2256,2259],{"class":49,"line":50},[47,2227,178],{"class":53},[47,2229,2230],{"class":57}," orderHandler",[47,2232,140],{"class":64},[47,2234,2235],{"class":143},"w",[47,2237,2238],{"class":57}," http",[47,2240,116],{"class":64},[47,2242,2243],{"class":57},"ResponseWriter",[47,2245,122],{"class":64},[47,2247,2248],{"class":143},"r",[47,2250,525],{"class":53},[47,2252,2253],{"class":57},"http",[47,2255,116],{"class":64},[47,2257,2258],{"class":57},"Request",[47,2260,1259],{"class":64},[47,2262,2263,2265,2267,2270,2272,2274],{"class":49,"line":68},[47,2264,1979],{"class":64},[47,2266,214],{"class":53},[47,2268,2269],{"class":64}," r.",[47,2271,35],{"class":57},[47,2273,74],{"class":64},[47,2275,2276],{"class":86},"\u002F\u002F контекст запроса — отменится если клиент ушёл\n",[47,2278,2279],{"class":49,"line":90},[47,2280,308],{"emptyLinePlaceholder":307},[47,2282,2283],{"class":49,"line":104},[47,2284,2285],{"class":86},"    \u002F\u002F Добавляем request ID для трейсинга\n",[47,2287,2288,2291,2293,2296,2299,2301,2304],{"class":49,"line":134},[47,2289,2290],{"class":64},"    reqID ",[47,2292,214],{"class":53},[47,2294,2295],{"class":64}," r.Header.",[47,2297,2298],{"class":57},"Get",[47,2300,140],{"class":64},[47,2302,2303],{"class":1396},"\"X-Request-ID\"",[47,2305,1317],{"class":64},[47,2307,2308,2310,2312,2314,2316],{"class":49,"line":158},[47,2309,1979],{"class":64},[47,2311,1135],{"class":53},[47,2313,293],{"class":64},[47,2315,1750],{"class":57},[47,2317,2318],{"class":64},"(ctx, requestIDKey, reqID)\n",[47,2320,2321],{"class":49,"line":264},[47,2322,308],{"emptyLinePlaceholder":307},[47,2324,2325],{"class":49,"line":376},[47,2326,2327],{"class":86},"    \u002F\u002F Ограничиваем время обработки\n",[47,2329,2330,2332,2334,2336,2338,2340,2342,2344],{"class":49,"line":582},[47,2331,601],{"class":64},[47,2333,214],{"class":53},[47,2335,293],{"class":64},[47,2337,1106],{"class":57},[47,2339,2050],{"class":64},[47,2341,1688],{"class":234},[47,2343,1115],{"class":53},[47,2345,1118],{"class":64},[47,2347,2348,2350,2352],{"class":49,"line":587},[47,2349,621],{"class":53},[47,2351,624],{"class":57},[47,2353,419],{"class":64},[47,2355,2356],{"class":49,"line":598},[47,2357,308],{"emptyLinePlaceholder":307},[47,2359,2360,2363,2365,2367],{"class":49,"line":618},[47,2361,2362],{"class":64},"    result, err ",[47,2364,214],{"class":53},[47,2366,903],{"class":57},[47,2368,2369],{"class":64},"(ctx, r)\n",[47,2371,2372,2374,2376,2378,2380],{"class":49,"line":632},[47,2373,225],{"class":53},[47,2375,228],{"class":64},[47,2377,231],{"class":53},[47,2379,235],{"class":234},[47,2381,65],{"class":64},[47,2383,2384,2387,2390,2393],{"class":49,"line":637},[47,2385,2386],{"class":53},"        if",[47,2388,2389],{"class":64}," errors.",[47,2391,2392],{"class":57},"Is",[47,2394,2395],{"class":64},"(err, context.Canceled) {\n",[47,2397,2398],{"class":49,"line":648},[47,2399,2400],{"class":86},"            \u002F\u002F клиент ушёл — не нужно возвращать ошибку\n",[47,2402,2403],{"class":49,"line":654},[47,2404,2405],{"class":53},"            return\n",[47,2407,2408],{"class":49,"line":666},[47,2409,868],{"class":64},[47,2411,2412,2415,2418,2421,2423],{"class":49,"line":674},[47,2413,2414],{"class":64},"        http.",[47,2416,2417],{"class":57},"Error",[47,2419,2420],{"class":64},"(w, err.",[47,2422,2417],{"class":57},[47,2424,2425],{"class":64},"(), http.StatusInternalServerError)\n",[47,2427,2428],{"class":49,"line":680},[47,2429,2430],{"class":53},"        return\n",[47,2432,2433],{"class":49,"line":685},[47,2434,250],{"class":64},[47,2436,2437],{"class":49,"line":698},[47,2438,308],{"emptyLinePlaceholder":307},[47,2440,2441,2444,2447,2450,2453],{"class":49,"line":711},[47,2442,2443],{"class":64},"    json.",[47,2445,2446],{"class":57},"NewEncoder",[47,2448,2449],{"class":64},"(w).",[47,2451,2452],{"class":57},"Encode",[47,2454,2455],{"class":64},"(result)\n",[47,2457,2458],{"class":49,"line":726},[47,2459,161],{"class":64},[24,2461],{},[27,2463,2465],{"id":2464},"распространённые-ошибки","Распространённые ошибки",[744,2467,2469],{"id":2468},"хранение-контекста-в-структуре","Хранение контекста в структуре",[38,2471,2473],{"className":40,"code":2472,"language":42,"meta":43,"style":43},"\u002F\u002F Плохо: контекст в структуре\ntype Service struct {\n    ctx context.Context \u002F\u002F антипаттерн\n    db  *sql.DB\n}\n\n\u002F\u002F Хорошо: контекст через аргумент метода\ntype Service struct {\n    db *sql.DB\n}\n\nfunc (s *Service) Process(ctx context.Context, id int) error {\n    return s.db.QueryRowContext(ctx, \"SELECT ...\", id)\n}\n",[19,2474,2475,2480,2491,2504,2519,2523,2527,2532,2542,2555,2559,2563,2605,2623],{"__ignoreMap":43},[47,2476,2477],{"class":49,"line":50},[47,2478,2479],{"class":86},"\u002F\u002F Плохо: контекст в структуре\n",[47,2481,2482,2484,2487,2489],{"class":49,"line":68},[47,2483,54],{"class":53},[47,2485,2486],{"class":57}," Service",[47,2488,80],{"class":53},[47,2490,65],{"class":64},[47,2492,2493,2495,2497,2499,2501],{"class":49,"line":90},[47,2494,1979],{"class":64},[47,2496,1788],{"class":57},[47,2498,116],{"class":64},[47,2500,35],{"class":57},[47,2502,2503],{"class":86}," \u002F\u002F антипаттерн\n",[47,2505,2506,2509,2511,2514,2516],{"class":49,"line":104},[47,2507,2508],{"class":64},"    db  ",[47,2510,1115],{"class":53},[47,2512,2513],{"class":57},"sql",[47,2515,116],{"class":64},[47,2517,2518],{"class":57},"DB\n",[47,2520,2521],{"class":49,"line":134},[47,2522,161],{"class":64},[47,2524,2525],{"class":49,"line":158},[47,2526,308],{"emptyLinePlaceholder":307},[47,2528,2529],{"class":49,"line":264},[47,2530,2531],{"class":86},"\u002F\u002F Хорошо: контекст через аргумент метода\n",[47,2533,2534,2536,2538,2540],{"class":49,"line":376},[47,2535,54],{"class":53},[47,2537,2486],{"class":57},[47,2539,80],{"class":53},[47,2541,65],{"class":64},[47,2543,2544,2547,2549,2551,2553],{"class":49,"line":582},[47,2545,2546],{"class":64},"    db ",[47,2548,1115],{"class":53},[47,2550,2513],{"class":57},[47,2552,116],{"class":64},[47,2554,2518],{"class":57},[47,2556,2557],{"class":49,"line":587},[47,2558,161],{"class":64},[47,2560,2561],{"class":49,"line":598},[47,2562,308],{"emptyLinePlaceholder":307},[47,2564,2565,2567,2570,2573,2575,2578,2580,2583,2585,2587,2589,2591,2593,2595,2597,2599,2601,2603],{"class":49,"line":618},[47,2566,178],{"class":53},[47,2568,2569],{"class":64}," (",[47,2571,2572],{"class":143},"s ",[47,2574,1115],{"class":53},[47,2576,2577],{"class":57},"Service",[47,2579,128],{"class":64},[47,2581,2582],{"class":57},"Process",[47,2584,140],{"class":64},[47,2586,167],{"class":143},[47,2588,188],{"class":57},[47,2590,116],{"class":64},[47,2592,35],{"class":57},[47,2594,122],{"class":64},[47,2596,918],{"class":143},[47,2598,200],{"class":53},[47,2600,128],{"class":64},[47,2602,98],{"class":53},[47,2604,65],{"class":64},[47,2606,2607,2609,2612,2615,2617,2620],{"class":49,"line":632},[47,2608,255],{"class":53},[47,2610,2611],{"class":64}," s.db.",[47,2613,2614],{"class":57},"QueryRowContext",[47,2616,2050],{"class":64},[47,2618,2619],{"class":1396},"\"SELECT ...\"",[47,2621,2622],{"class":64},", id)\n",[47,2624,2625],{"class":49,"line":637},[47,2626,161],{"class":64},[15,2628,2629],{},"Контекст привязан к конкретному запросу и живёт столько, сколько живёт запрос. Структура может жить дольше — хранить в ней контекст означает держать устаревший или уже отменённый контекст.",[744,2631,2633],{"id":2632},"передача-nil-вместо-контекста","Передача nil вместо контекста",[38,2635,2637],{"className":40,"code":2636,"language":42,"meta":43,"style":43},"\u002F\u002F Плохо: nil вызовет панику внутри при обращении к ctx.Done()\nprocessOrder(nil, orderID)\n\n\u002F\u002F Хорошо: если нет контекста — используй Background или TODO\nprocessOrder(context.Background(), orderID)\nprocessOrder(context.TODO(), orderID) \u002F\u002F если контекст появится позже\n",[19,2638,2639,2644,2656,2660,2665,2676],{"__ignoreMap":43},[47,2640,2641],{"class":49,"line":50},[47,2642,2643],{"class":86},"\u002F\u002F Плохо: nil вызовет панику внутри при обращении к ctx.Done()\n",[47,2645,2646,2649,2651,2653],{"class":49,"line":68},[47,2647,2648],{"class":57},"processOrder",[47,2650,140],{"class":64},[47,2652,1314],{"class":234},[47,2654,2655],{"class":64},", orderID)\n",[47,2657,2658],{"class":49,"line":90},[47,2659,308],{"emptyLinePlaceholder":307},[47,2661,2662],{"class":49,"line":104},[47,2663,2664],{"class":86},"\u002F\u002F Хорошо: если нет контекста — используй Background или TODO\n",[47,2666,2667,2669,2671,2673],{"class":49,"line":134},[47,2668,2648],{"class":57},[47,2670,610],{"class":64},[47,2672,296],{"class":57},[47,2674,2675],{"class":64},"(), orderID)\n",[47,2677,2678,2680,2682,2684,2687],{"class":49,"line":158},[47,2679,2648],{"class":57},[47,2681,610],{"class":64},[47,2683,444],{"class":57},[47,2685,2686],{"class":64},"(), orderID) ",[47,2688,2689],{"class":86},"\u002F\u002F если контекст появится позже\n",[744,2691,2693],{"id":2692},"игнорирование-cancel","Игнорирование cancel",[38,2695,2697],{"className":40,"code":2696,"language":42,"meta":43,"style":43},"\u002F\u002F Утечка: cancel никогда не вызывается\nctx, _ := context.WithTimeout(parent, time.Second)\n\n\u002F\u002F Правильно\nctx, cancel := context.WithTimeout(parent, time.Second)\ndefer cancel()\n",[19,2698,2699,2704,2718,2722,2727,2739],{"__ignoreMap":43},[47,2700,2701],{"class":49,"line":50},[47,2702,2703],{"class":86},"\u002F\u002F Утечка: cancel никогда не вызывается\n",[47,2705,2706,2709,2711,2713,2715],{"class":49,"line":68},[47,2707,2708],{"class":64},"ctx, _ ",[47,2710,214],{"class":53},[47,2712,293],{"class":64},[47,2714,1106],{"class":57},[47,2716,2717],{"class":64},"(parent, time.Second)\n",[47,2719,2720],{"class":49,"line":90},[47,2721,308],{"emptyLinePlaceholder":307},[47,2723,2724],{"class":49,"line":104},[47,2725,2726],{"class":86},"\u002F\u002F Правильно\n",[47,2728,2729,2731,2733,2735,2737],{"class":49,"line":134},[47,2730,1099],{"class":64},[47,2732,214],{"class":53},[47,2734,293],{"class":64},[47,2736,1106],{"class":57},[47,2738,2717],{"class":64},[47,2740,2741,2743,2745],{"class":49,"line":158},[47,2742,1123],{"class":53},[47,2744,624],{"class":57},[47,2746,419],{"class":64},[24,2748],{},[27,2750,2752],{"id":2751},"вопросы-на-собеседовании","Вопросы на собеседовании",[15,2754,2755,2758,2761],{},[2149,2756,2757],{},"Q: Что такое Context и зачем он нужен?",[2759,2760],"br",{},"\nA: Механизм для передачи сигнала отмены, дедлайнов и request-scoped данных через цепочку вызовов. Решает задачу: как остановить все горутины запроса если клиент ушёл или истёк таймаут. Контекст передаётся явно первым аргументом — это намеренное решение, делающее поддержку отмены видимой в API.",[15,2763,2764,2767,2769,2770,2772,2773,2776,2777,2779,2780,2782,2783,2786,2787,116],{},[2149,2765,2766],{},"Q: Чем отличаются WithCancel, WithTimeout и WithDeadline?",[2759,2768],{},"\nA: ",[19,2771,320],{}," — отмена только явным вызовом ",[19,2774,2775],{},"cancel()",". ",[19,2778,1106],{}," — автоматическая отмена через заданную продолжительность. ",[19,2781,1187],{}," — автоматическая отмена в конкретный момент времени. ",[19,2784,2785],{},"WithTimeout(ctx, d)"," — синтаксический сахар над ",[19,2788,2789],{},"WithDeadline(ctx, time.Now().Add(d))",[15,2791,2792,2795,2797,2798,2800],{},[2149,2793,2794],{},"Q: Что произойдёт если не вызвать cancel()?",[2759,2796],{},"\nA: Утечка ресурсов: контекст и все его дочерние контексты не будут освобождены до отмены родителя. В долгоживущих сервисах это накапливается. Поэтому ",[19,2799,738],{}," — обязательное правило сразу после создания контекста.",[15,2802,2803,2806,2808],{},[2149,2804,2805],{},"Q: Как устроено дерево контекстов? Что происходит при отмене родителя?",[2759,2807],{},"\nA: Контексты образуют дерево — каждый дочерний наследует свойства родителя. Отмена родителя автоматически рекурсивно отменяет всех потомков. Дочерний может отмениться раньше родителя, но не позже.",[15,2810,2811,2814,2816,2817,2820],{},[2149,2812,2813],{},"Q: Почему нельзя использовать строку как ключ для WithValue?",[2759,2815],{},"\nA: Коллизии между пакетами: разные пакеты могут использовать одинаковые строки и перезаписать значения друг друга. Собственный неэкспортируемый тип ключа (",[19,2818,2819],{},"type myKey struct{}",") гарантирует уникальность — разные пакеты не могут создать одинаковый тип.",[15,2822,2823,2826,2828],{},[2149,2824,2825],{},"Q: Что передавать через контекст, а что нет?",[2759,2827],{},"\nA: Через контекст — cross-cutting concerns запроса: request ID, trace ID, user ID аутентифицированного пользователя, tenant ID. Не через контекст: параметры бизнес-логики (явные аргументы), зависимости (инъекция через конструктор), ошибки (явный возврат). Контекст не замена аргументам функции.",[15,2830,2831,2834,2836],{},[2149,2832,2833],{},"Q: Почему нельзя хранить контекст в структуре?",[2759,2835],{},"\nA: Контекст привязан к конкретному запросу и имеет время жизни запроса. Структура может жить дольше — хранение контекста в ней означает риск использования устаревшего или уже отменённого контекста. Контекст всегда передаётся через аргумент метода.",[15,2838,2839,2842,2844,2845,2848,2849,2852,2853,2855,2856,2859,2860,116],{},[2149,2840,2841],{},"Q: Как проверить причину отмены контекста?",[2759,2843],{},"\nA: Через ",[19,2846,2847],{},"ctx.Err()",": возвращает ",[19,2850,2851],{},"context.Canceled"," при явной отмене через ",[19,2854,2775],{},", и ",[19,2857,2858],{},"context.DeadlineExceeded"," при истечении таймаута или дедлайна. Пока контекст активен — возвращает ",[19,2861,1314],{},[15,2863,2864,2867,2869],{},[2149,2865,2866],{},"Q: Может ли дочерний контекст расширить дедлайн родителя?",[2759,2868],{},"\nA: Нет. Если родитель отменится раньше — дочерний тоже отменится, даже если его собственный дедлайн ещё не наступил. Дочерний контекст может только сузить дедлайн, но не расширить.",[11,2871,2873],{"id":2872},"задачи-context","Задачи: Context",[24,2875],{},[15,2877,2878],{},[2149,2879,2880],{},"Задача 1: Передача request ID",[15,2882,2883,2886],{},[2149,2884,2885],{},"Уровень:"," Лёгкая",[15,2888,2889,2892],{},[2149,2890,2891],{},"Что проверяет:"," WithValue, правильный тип ключа, извлечение значений",[15,2894,2895,2898,2899,2902,2903,2906,2907,116],{},[2149,2896,2897],{},"Условие:"," Реализуй middleware-функцию ",[19,2900,2901],{},"withRequestID(ctx context.Context, id string) context.Context"," и функцию ",[19,2904,2905],{},"getRequestID(ctx context.Context) string",". Используй собственный тип ключа. Если ID нет в контексте — возвращай ",[19,2908,2909],{},"\"unknown\"",[15,2911,2912],{},[2149,2913,2914],{},"Решение:",[38,2916,2918],{"className":40,"code":2917,"language":42,"meta":43,"style":43},"package main\n\nimport (\n    \"context\"\n    \"fmt\"\n)\n\ntype contextKey string\n\nconst requestIDKey contextKey = \"requestID\"\n\nfunc withRequestID(ctx context.Context, id string) context.Context {\n    return context.WithValue(ctx, requestIDKey, id)\n}\n\nfunc getRequestID(ctx context.Context) string {\n    id, ok := ctx.Value(requestIDKey).(string)\n    if !ok || id == \"\" {\n        return \"unknown\"\n    }\n    return id\n}\n\nfunc handle(ctx context.Context) {\n    fmt.Printf(\"[%s] handling request\\n\", getRequestID(ctx))\n}\n\nfunc main() {\n    ctx := context.Background()\n    handle(ctx) \u002F\u002F [unknown] handling request\n\n    ctx = withRequestID(ctx, \"abc-123\")\n    handle(ctx) \u002F\u002F [abc-123] handling request\n}\n",[19,2919,2920,2926,2930,2936,2944,2952,2956,2960,2969,2973,2986,2990,3023,3034,3038,3042,3065,3082,3105,3111,3115,3122,3126,3130,3149,3180,3184,3188,3196,3209,3221,3226,3241,3251],{"__ignoreMap":43},[47,2921,2922,2924],{"class":49,"line":50},[47,2923,1765],{"class":53},[47,2925,1768],{"class":57},[47,2927,2928],{"class":49,"line":68},[47,2929,308],{"emptyLinePlaceholder":307},[47,2931,2932,2934],{"class":49,"line":90},[47,2933,1777],{"class":53},[47,2935,1780],{"class":64},[47,2937,2938,2940,2942],{"class":49,"line":104},[47,2939,1785],{"class":1396},[47,2941,1788],{"class":57},[47,2943,1791],{"class":1396},[47,2945,2946,2948,2950],{"class":49,"line":134},[47,2947,1785],{"class":1396},[47,2949,1798],{"class":57},[47,2951,1791],{"class":1396},[47,2953,2954],{"class":49,"line":158},[47,2955,1317],{"class":64},[47,2957,2958],{"class":49,"line":264},[47,2959,308],{"emptyLinePlaceholder":307},[47,2961,2962,2964,2966],{"class":49,"line":376},[47,2963,54],{"class":53},[47,2965,1815],{"class":57},[47,2967,2968],{"class":53}," string\n",[47,2970,2971],{"class":49,"line":582},[47,2972,308],{"emptyLinePlaceholder":307},[47,2974,2975,2977,2980,2982,2984],{"class":49,"line":587},[47,2976,1829],{"class":53},[47,2978,2979],{"class":234}," requestIDKey",[47,2981,1815],{"class":57},[47,2983,1841],{"class":53},[47,2985,1844],{"class":1396},[47,2987,2988],{"class":49,"line":598},[47,2989,308],{"emptyLinePlaceholder":307},[47,2991,2992,2994,2997,2999,3001,3003,3005,3007,3009,3011,3013,3015,3017,3019,3021],{"class":49,"line":618},[47,2993,178],{"class":53},[47,2995,2996],{"class":57}," withRequestID",[47,2998,140],{"class":64},[47,3000,167],{"class":143},[47,3002,188],{"class":57},[47,3004,116],{"class":64},[47,3006,35],{"class":57},[47,3008,122],{"class":64},[47,3010,918],{"class":143},[47,3012,1244],{"class":53},[47,3014,128],{"class":64},[47,3016,1788],{"class":57},[47,3018,116],{"class":64},[47,3020,35],{"class":57},[47,3022,65],{"class":64},[47,3024,3025,3027,3029,3031],{"class":49,"line":632},[47,3026,255],{"class":53},[47,3028,293],{"class":64},[47,3030,1750],{"class":57},[47,3032,3033],{"class":64},"(ctx, requestIDKey, id)\n",[47,3035,3036],{"class":49,"line":637},[47,3037,161],{"class":64},[47,3039,3040],{"class":49,"line":648},[47,3041,308],{"emptyLinePlaceholder":307},[47,3043,3044,3046,3049,3051,3053,3055,3057,3059,3061,3063],{"class":49,"line":654},[47,3045,178],{"class":53},[47,3047,3048],{"class":57}," getRequestID",[47,3050,140],{"class":64},[47,3052,167],{"class":143},[47,3054,188],{"class":57},[47,3056,116],{"class":64},[47,3058,35],{"class":57},[47,3060,128],{"class":64},[47,3062,1902],{"class":53},[47,3064,65],{"class":64},[47,3066,3067,3070,3072,3074,3076,3078,3080],{"class":49,"line":666},[47,3068,3069],{"class":64},"    id, ok ",[47,3071,214],{"class":53},[47,3073,563],{"class":64},[47,3075,1896],{"class":57},[47,3077,1899],{"class":64},[47,3079,1902],{"class":53},[47,3081,1317],{"class":64},[47,3083,3084,3086,3088,3091,3094,3097,3100,3103],{"class":49,"line":674},[47,3085,225],{"class":53},[47,3087,1911],{"class":53},[47,3089,3090],{"class":64},"ok ",[47,3092,3093],{"class":53},"||",[47,3095,3096],{"class":64}," id ",[47,3098,3099],{"class":53},"==",[47,3101,3102],{"class":1396}," \"\"",[47,3104,65],{"class":64},[47,3106,3107,3109],{"class":49,"line":680},[47,3108,242],{"class":53},[47,3110,1924],{"class":1396},[47,3112,3113],{"class":49,"line":685},[47,3114,250],{"class":64},[47,3116,3117,3119],{"class":49,"line":698},[47,3118,255],{"class":53},[47,3120,3121],{"class":64}," id\n",[47,3123,3124],{"class":49,"line":711},[47,3125,161],{"class":64},[47,3127,3128],{"class":49,"line":726},[47,3129,308],{"emptyLinePlaceholder":307},[47,3131,3132,3134,3137,3139,3141,3143,3145,3147],{"class":49,"line":731},[47,3133,178],{"class":53},[47,3135,3136],{"class":57}," handle",[47,3138,140],{"class":64},[47,3140,167],{"class":143},[47,3142,188],{"class":57},[47,3144,116],{"class":64},[47,3146,35],{"class":57},[47,3148,1259],{"class":64},[47,3150,3151,3153,3156,3158,3161,3164,3167,3170,3172,3174,3177],{"class":49,"line":1976},[47,3152,1933],{"class":64},[47,3154,3155],{"class":57},"Printf",[47,3157,140],{"class":64},[47,3159,3160],{"class":1396},"\"[",[47,3162,3163],{"class":234},"%s",[47,3165,3166],{"class":1396},"] handling request",[47,3168,3169],{"class":234},"\\n",[47,3171,1403],{"class":1396},[47,3173,122],{"class":64},[47,3175,3176],{"class":57},"getRequestID",[47,3178,3179],{"class":64},"(ctx))\n",[47,3181,3182],{"class":49,"line":1996},[47,3183,161],{"class":64},[47,3185,3186],{"class":49,"line":2010},[47,3187,308],{"emptyLinePlaceholder":307},[47,3189,3190,3192,3194],{"class":49,"line":2018},[47,3191,178],{"class":53},[47,3193,1958],{"class":57},[47,3195,595],{"class":64},[47,3197,3199,3201,3203,3205,3207],{"class":49,"line":3198},29,[47,3200,1979],{"class":64},[47,3202,214],{"class":53},[47,3204,293],{"class":64},[47,3206,296],{"class":57},[47,3208,419],{"class":64},[47,3210,3212,3215,3218],{"class":49,"line":3211},30,[47,3213,3214],{"class":57},"    handle",[47,3216,3217],{"class":64},"(ctx) ",[47,3219,3220],{"class":86},"\u002F\u002F [unknown] handling request\n",[47,3222,3224],{"class":49,"line":3223},31,[47,3225,308],{"emptyLinePlaceholder":307},[47,3227,3229,3231,3233,3235,3237,3239],{"class":49,"line":3228},32,[47,3230,1979],{"class":64},[47,3232,1135],{"class":53},[47,3234,2996],{"class":57},[47,3236,2050],{"class":64},[47,3238,1991],{"class":1396},[47,3240,1317],{"class":64},[47,3242,3244,3246,3248],{"class":49,"line":3243},33,[47,3245,3214],{"class":57},[47,3247,3217],{"class":64},[47,3249,3250],{"class":86},"\u002F\u002F [abc-123] handling request\n",[47,3252,3254],{"class":49,"line":3253},34,[47,3255,161],{"class":64},[24,3257],{},[15,3259,3260],{},[2149,3261,3262],{},"Задача 2: Отмена по первому результату",[15,3264,3265,3267],{},[2149,3266,2885],{}," Средняя",[15,3269,3270,3272],{},[2149,3271,2891],{}," WithCancel, паттерн first-result-wins",[15,3274,3275,3277,3278,3281],{},[2149,3276,2897],{}," Напиши функцию ",[19,3279,3280],{},"fastest(ctx context.Context, fns []func(ctx context.Context) int) int"," которая запускает все функции конкурентно и возвращает результат той которая завершится первой. Остальные горутины должны получить сигнал отмены.",[15,3283,3284],{},[2149,3285,2914],{},[38,3287,3289],{"className":40,"code":3288,"language":42,"meta":43,"style":43},"package main\n\nimport (\n    \"context\"\n    \"fmt\"\n    \"time\"\n)\n\nfunc fastest(ctx context.Context, fns []func(ctx context.Context) int) int {\n    ctx, cancel := context.WithCancel(ctx)\n    defer cancel()\n\n    ch := make(chan int, len(fns)) \u002F\u002F буферизованный — горутины не зависнут\n\n    for _, fn := range fns {\n        fn := fn\n        go func() {\n            result := fn(ctx)\n            select {\n            case ch \u003C- result:\n            case \u003C-ctx.Done():\n            }\n        }()\n    }\n\n    result := \u003C-ch\n    cancel() \u002F\u002F отменяем остальные\n    return result\n}\n\nfunc main() {\n    ctx := context.Background()\n\n    fns := []func(ctx context.Context) int{\n        func(ctx context.Context) int {\n            time.Sleep(300 * time.Millisecond)\n            return 1\n        },\n        func(ctx context.Context) int {\n            time.Sleep(100 * time.Millisecond)\n            return 2 \u002F\u002F победитель\n        },\n        func(ctx context.Context) int {\n            time.Sleep(200 * time.Millisecond)\n            return 3\n        },\n    }\n\n    fmt.Println(fastest(ctx, fns)) \u002F\u002F 2\n}\n",[19,3290,3291,3297,3301,3307,3315,3323,3331,3335,3339,3387,3399,3407,3411,3439,3443,3458,3468,3477,3489,3496,3510,3522,3526,3531,3535,3539,3551,3561,3568,3572,3576,3584,3596,3600,3628,3650,3667,3675,3681,3702,3718,3729,3734,3755,3771,3779,3784,3789,3794,3812],{"__ignoreMap":43},[47,3292,3293,3295],{"class":49,"line":50},[47,3294,1765],{"class":53},[47,3296,1768],{"class":57},[47,3298,3299],{"class":49,"line":68},[47,3300,308],{"emptyLinePlaceholder":307},[47,3302,3303,3305],{"class":49,"line":90},[47,3304,1777],{"class":53},[47,3306,1780],{"class":64},[47,3308,3309,3311,3313],{"class":49,"line":104},[47,3310,1785],{"class":1396},[47,3312,1788],{"class":57},[47,3314,1791],{"class":1396},[47,3316,3317,3319,3321],{"class":49,"line":134},[47,3318,1785],{"class":1396},[47,3320,1798],{"class":57},[47,3322,1791],{"class":1396},[47,3324,3325,3327,3329],{"class":49,"line":158},[47,3326,1785],{"class":1396},[47,3328,113],{"class":57},[47,3330,1791],{"class":1396},[47,3332,3333],{"class":49,"line":264},[47,3334,1317],{"class":64},[47,3336,3337],{"class":49,"line":376},[47,3338,308],{"emptyLinePlaceholder":307},[47,3340,3341,3343,3346,3348,3350,3352,3354,3356,3358,3361,3364,3366,3368,3370,3372,3374,3376,3378,3381,3383,3385],{"class":49,"line":582},[47,3342,178],{"class":53},[47,3344,3345],{"class":57}," fastest",[47,3347,140],{"class":64},[47,3349,167],{"class":143},[47,3351,188],{"class":57},[47,3353,116],{"class":64},[47,3355,35],{"class":57},[47,3357,122],{"class":64},[47,3359,3360],{"class":143},"fns",[47,3362,3363],{"class":64}," []",[47,3365,178],{"class":53},[47,3367,140],{"class":64},[47,3369,167],{"class":143},[47,3371,188],{"class":57},[47,3373,116],{"class":64},[47,3375,35],{"class":57},[47,3377,128],{"class":64},[47,3379,3380],{"class":53},"int",[47,3382,128],{"class":64},[47,3384,3380],{"class":53},[47,3386,65],{"class":64},[47,3388,3389,3391,3393,3395,3397],{"class":49,"line":587},[47,3390,601],{"class":64},[47,3392,214],{"class":53},[47,3394,293],{"class":64},[47,3396,320],{"class":57},[47,3398,695],{"class":64},[47,3400,3401,3403,3405],{"class":49,"line":598},[47,3402,621],{"class":53},[47,3404,624],{"class":57},[47,3406,419],{"class":64},[47,3408,3409],{"class":49,"line":618},[47,3410,308],{"emptyLinePlaceholder":307},[47,3412,3413,3416,3418,3421,3423,3426,3428,3430,3433,3436],{"class":49,"line":632},[47,3414,3415],{"class":64},"    ch ",[47,3417,214],{"class":53},[47,3419,3420],{"class":57}," make",[47,3422,140],{"class":64},[47,3424,3425],{"class":53},"chan",[47,3427,200],{"class":53},[47,3429,122],{"class":64},[47,3431,3432],{"class":57},"len",[47,3434,3435],{"class":64},"(fns)) ",[47,3437,3438],{"class":86},"\u002F\u002F буферизованный — горутины не зависнут\n",[47,3440,3441],{"class":49,"line":637},[47,3442,308],{"emptyLinePlaceholder":307},[47,3444,3445,3447,3450,3452,3455],{"class":49,"line":648},[47,3446,780],{"class":53},[47,3448,3449],{"class":64}," _, fn ",[47,3451,214],{"class":53},[47,3453,3454],{"class":53}," range",[47,3456,3457],{"class":64}," fns {\n",[47,3459,3460,3463,3465],{"class":49,"line":654},[47,3461,3462],{"class":64},"        fn ",[47,3464,214],{"class":53},[47,3466,3467],{"class":64}," fn\n",[47,3469,3470,3473,3475],{"class":49,"line":666},[47,3471,3472],{"class":53},"        go",[47,3474,643],{"class":53},[47,3476,595],{"class":64},[47,3478,3479,3482,3484,3487],{"class":49,"line":674},[47,3480,3481],{"class":64},"            result ",[47,3483,214],{"class":53},[47,3485,3486],{"class":57}," fn",[47,3488,695],{"class":64},[47,3490,3491,3494],{"class":49,"line":680},[47,3492,3493],{"class":53},"            select",[47,3495,65],{"class":64},[47,3497,3498,3501,3504,3507],{"class":49,"line":685},[47,3499,3500],{"class":53},"            case",[47,3502,3503],{"class":64}," ch ",[47,3505,3506],{"class":53},"\u003C-",[47,3508,3509],{"class":64}," result:\n",[47,3511,3512,3514,3516,3518,3520],{"class":49,"line":698},[47,3513,3500],{"class":53},[47,3515,511],{"class":53},[47,3517,547],{"class":64},[47,3519,550],{"class":57},[47,3521,803],{"class":64},[47,3523,3524],{"class":49,"line":711},[47,3525,863],{"class":64},[47,3527,3528],{"class":49,"line":726},[47,3529,3530],{"class":64},"        }()\n",[47,3532,3533],{"class":49,"line":731},[47,3534,250],{"class":64},[47,3536,3537],{"class":49,"line":1976},[47,3538,308],{"emptyLinePlaceholder":307},[47,3540,3541,3544,3546,3548],{"class":49,"line":1996},[47,3542,3543],{"class":64},"    result ",[47,3545,214],{"class":53},[47,3547,511],{"class":53},[47,3549,3550],{"class":64},"ch\n",[47,3552,3553,3556,3558],{"class":49,"line":2010},[47,3554,3555],{"class":57},"    cancel",[47,3557,74],{"class":64},[47,3559,3560],{"class":86},"\u002F\u002F отменяем остальные\n",[47,3562,3563,3565],{"class":49,"line":2018},[47,3564,255],{"class":53},[47,3566,3567],{"class":64}," result\n",[47,3569,3570],{"class":49,"line":3198},[47,3571,161],{"class":64},[47,3573,3574],{"class":49,"line":3211},[47,3575,308],{"emptyLinePlaceholder":307},[47,3577,3578,3580,3582],{"class":49,"line":3223},[47,3579,178],{"class":53},[47,3581,1958],{"class":57},[47,3583,595],{"class":64},[47,3585,3586,3588,3590,3592,3594],{"class":49,"line":3228},[47,3587,1979],{"class":64},[47,3589,214],{"class":53},[47,3591,293],{"class":64},[47,3593,296],{"class":57},[47,3595,419],{"class":64},[47,3597,3598],{"class":49,"line":3243},[47,3599,308],{"emptyLinePlaceholder":307},[47,3601,3602,3605,3607,3609,3611,3613,3615,3617,3619,3621,3623,3625],{"class":49,"line":3253},[47,3603,3604],{"class":64},"    fns ",[47,3606,214],{"class":53},[47,3608,3363],{"class":64},[47,3610,178],{"class":53},[47,3612,140],{"class":64},[47,3614,167],{"class":143},[47,3616,188],{"class":57},[47,3618,116],{"class":64},[47,3620,35],{"class":57},[47,3622,128],{"class":64},[47,3624,3380],{"class":53},[47,3626,3627],{"class":64},"{\n",[47,3629,3631,3634,3636,3638,3640,3642,3644,3646,3648],{"class":49,"line":3630},35,[47,3632,3633],{"class":53},"        func",[47,3635,140],{"class":64},[47,3637,167],{"class":143},[47,3639,188],{"class":57},[47,3641,116],{"class":64},[47,3643,35],{"class":57},[47,3645,128],{"class":64},[47,3647,3380],{"class":53},[47,3649,65],{"class":64},[47,3651,3653,3656,3658,3660,3663,3665],{"class":49,"line":3652},36,[47,3654,3655],{"class":64},"            time.",[47,3657,660],{"class":57},[47,3659,140],{"class":64},[47,3661,3662],{"class":234},"300",[47,3664,525],{"class":53},[47,3666,1484],{"class":64},[47,3668,3670,3672],{"class":49,"line":3669},37,[47,3671,808],{"class":53},[47,3673,3674],{"class":234}," 1\n",[47,3676,3678],{"class":49,"line":3677},38,[47,3679,3680],{"class":64},"        },\n",[47,3682,3684,3686,3688,3690,3692,3694,3696,3698,3700],{"class":49,"line":3683},39,[47,3685,3633],{"class":53},[47,3687,140],{"class":64},[47,3689,167],{"class":143},[47,3691,188],{"class":57},[47,3693,116],{"class":64},[47,3695,35],{"class":57},[47,3697,128],{"class":64},[47,3699,3380],{"class":53},[47,3701,65],{"class":64},[47,3703,3705,3707,3709,3711,3714,3716],{"class":49,"line":3704},40,[47,3706,3655],{"class":64},[47,3708,660],{"class":57},[47,3710,140],{"class":64},[47,3712,3713],{"class":234},"100",[47,3715,525],{"class":53},[47,3717,1484],{"class":64},[47,3719,3721,3723,3726],{"class":49,"line":3720},41,[47,3722,808],{"class":53},[47,3724,3725],{"class":234}," 2",[47,3727,3728],{"class":86}," \u002F\u002F победитель\n",[47,3730,3732],{"class":49,"line":3731},42,[47,3733,3680],{"class":64},[47,3735,3737,3739,3741,3743,3745,3747,3749,3751,3753],{"class":49,"line":3736},43,[47,3738,3633],{"class":53},[47,3740,140],{"class":64},[47,3742,167],{"class":143},[47,3744,188],{"class":57},[47,3746,116],{"class":64},[47,3748,35],{"class":57},[47,3750,128],{"class":64},[47,3752,3380],{"class":53},[47,3754,65],{"class":64},[47,3756,3758,3760,3762,3764,3767,3769],{"class":49,"line":3757},44,[47,3759,3655],{"class":64},[47,3761,660],{"class":57},[47,3763,140],{"class":64},[47,3765,3766],{"class":234},"200",[47,3768,525],{"class":53},[47,3770,1484],{"class":64},[47,3772,3774,3776],{"class":49,"line":3773},45,[47,3775,808],{"class":53},[47,3777,3778],{"class":234}," 3\n",[47,3780,3782],{"class":49,"line":3781},46,[47,3783,3680],{"class":64},[47,3785,3787],{"class":49,"line":3786},47,[47,3788,250],{"class":64},[47,3790,3792],{"class":49,"line":3791},48,[47,3793,308],{"emptyLinePlaceholder":307},[47,3795,3797,3799,3801,3803,3806,3809],{"class":49,"line":3796},49,[47,3798,1933],{"class":64},[47,3800,717],{"class":57},[47,3802,140],{"class":64},[47,3804,3805],{"class":57},"fastest",[47,3807,3808],{"class":64},"(ctx, fns)) ",[47,3810,3811],{"class":86},"\u002F\u002F 2\n",[47,3813,3815],{"class":49,"line":3814},50,[47,3816,161],{"class":64},[24,3818],{},[15,3820,3821],{},[2149,3822,3823],{},"Задача 3: Цепочка таймаутов",[15,3825,3826,3828],{},[2149,3827,2885],{}," Сложная",[15,3830,3831,3833],{},[2149,3832,2891],{}," наследование дедлайнов, понимание что дочерний не расширяет родительский",[15,3835,3836,3838],{},[2149,3837,2897],{}," Что выведет код? Объясни почему.",[38,3840,3842],{"className":40,"code":3841,"language":42,"meta":43,"style":43},"package main\n\nimport (\n    \"context\"\n    \"fmt\"\n    \"time\"\n)\n\nfunc operation(ctx context.Context, name string, duration time.Duration) error {\n    select {\n    case \u003C-time.After(duration):\n        fmt.Printf(\"%s: completed\\n\", name)\n        return nil\n    case \u003C-ctx.Done():\n        fmt.Printf(\"%s: cancelled — %v\\n\", name, ctx.Err())\n        return ctx.Err()\n    }\n}\n\nfunc main() {\n    parent, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)\n    defer cancel()\n\n    child, cancelChild := context.WithTimeout(parent, 500*time.Millisecond)\n    defer cancelChild()\n\n    operation(child, \"fast\", 100*time.Millisecond)\n    operation(child, \"slow\", 300*time.Millisecond)\n}\n",[19,3843,3844,3850,3854,3860,3868,3876,3884,3888,3892,3935,3941,3954,3976,3982,3994,4021,4031,4035,4039,4043,4051,4074,4082,4086,4105,4114,4118,4137,4154],{"__ignoreMap":43},[47,3845,3846,3848],{"class":49,"line":50},[47,3847,1765],{"class":53},[47,3849,1768],{"class":57},[47,3851,3852],{"class":49,"line":68},[47,3853,308],{"emptyLinePlaceholder":307},[47,3855,3856,3858],{"class":49,"line":90},[47,3857,1777],{"class":53},[47,3859,1780],{"class":64},[47,3861,3862,3864,3866],{"class":49,"line":104},[47,3863,1785],{"class":1396},[47,3865,1788],{"class":57},[47,3867,1791],{"class":1396},[47,3869,3870,3872,3874],{"class":49,"line":134},[47,3871,1785],{"class":1396},[47,3873,1798],{"class":57},[47,3875,1791],{"class":1396},[47,3877,3878,3880,3882],{"class":49,"line":158},[47,3879,1785],{"class":1396},[47,3881,113],{"class":57},[47,3883,1791],{"class":1396},[47,3885,3886],{"class":49,"line":264},[47,3887,1317],{"class":64},[47,3889,3890],{"class":49,"line":376},[47,3891,308],{"emptyLinePlaceholder":307},[47,3893,3894,3896,3899,3901,3903,3905,3907,3909,3911,3914,3916,3918,3921,3924,3926,3929,3931,3933],{"class":49,"line":582},[47,3895,178],{"class":53},[47,3897,3898],{"class":57}," operation",[47,3900,140],{"class":64},[47,3902,167],{"class":143},[47,3904,188],{"class":57},[47,3906,116],{"class":64},[47,3908,35],{"class":57},[47,3910,122],{"class":64},[47,3912,3913],{"class":143},"name",[47,3915,1244],{"class":53},[47,3917,122],{"class":64},[47,3919,3920],{"class":143},"duration",[47,3922,3923],{"class":57}," time",[47,3925,116],{"class":64},[47,3927,3928],{"class":57},"Duration",[47,3930,128],{"class":64},[47,3932,98],{"class":53},[47,3934,65],{"class":64},[47,3936,3937,3939],{"class":49,"line":587},[47,3938,501],{"class":53},[47,3940,65],{"class":64},[47,3942,3943,3945,3947,3949,3951],{"class":49,"line":598},[47,3944,508],{"class":53},[47,3946,511],{"class":53},[47,3948,514],{"class":64},[47,3950,517],{"class":57},[47,3952,3953],{"class":64},"(duration):\n",[47,3955,3956,3958,3960,3962,3964,3966,3969,3971,3973],{"class":49,"line":618},[47,3957,714],{"class":64},[47,3959,3155],{"class":57},[47,3961,140],{"class":64},[47,3963,1403],{"class":1396},[47,3965,3163],{"class":234},[47,3967,3968],{"class":1396},": completed",[47,3970,3169],{"class":234},[47,3972,1403],{"class":1396},[47,3974,3975],{"class":64},", name)\n",[47,3977,3978,3980],{"class":49,"line":632},[47,3979,242],{"class":53},[47,3981,538],{"class":234},[47,3983,3984,3986,3988,3990,3992],{"class":49,"line":637},[47,3985,508],{"class":53},[47,3987,511],{"class":53},[47,3989,547],{"class":64},[47,3991,550],{"class":57},[47,3993,803],{"class":64},[47,3995,3996,3998,4000,4002,4004,4006,4009,4012,4014,4017,4019],{"class":49,"line":648},[47,3997,714],{"class":64},[47,3999,3155],{"class":57},[47,4001,140],{"class":64},[47,4003,1403],{"class":1396},[47,4005,3163],{"class":234},[47,4007,4008],{"class":1396},": cancelled — ",[47,4010,4011],{"class":234},"%v\\n",[47,4013,1403],{"class":1396},[47,4015,4016],{"class":64},", name, ctx.",[47,4018,566],{"class":57},[47,4020,615],{"class":64},[47,4022,4023,4025,4027,4029],{"class":49,"line":654},[47,4024,242],{"class":53},[47,4026,563],{"class":64},[47,4028,566],{"class":57},[47,4030,419],{"class":64},[47,4032,4033],{"class":49,"line":666},[47,4034,250],{"class":64},[47,4036,4037],{"class":49,"line":674},[47,4038,161],{"class":64},[47,4040,4041],{"class":49,"line":680},[47,4042,308],{"emptyLinePlaceholder":307},[47,4044,4045,4047,4049],{"class":49,"line":685},[47,4046,178],{"class":53},[47,4048,1958],{"class":57},[47,4050,595],{"class":64},[47,4052,4053,4056,4058,4060,4062,4064,4066,4068,4070,4072],{"class":49,"line":698},[47,4054,4055],{"class":64},"    parent, cancel ",[47,4057,214],{"class":53},[47,4059,293],{"class":64},[47,4061,1106],{"class":57},[47,4063,610],{"class":64},[47,4065,296],{"class":57},[47,4067,1276],{"class":64},[47,4069,3766],{"class":234},[47,4071,1115],{"class":53},[47,4073,1730],{"class":64},[47,4075,4076,4078,4080],{"class":49,"line":711},[47,4077,621],{"class":53},[47,4079,624],{"class":57},[47,4081,419],{"class":64},[47,4083,4084],{"class":49,"line":726},[47,4085,308],{"emptyLinePlaceholder":307},[47,4087,4088,4091,4093,4095,4097,4099,4101,4103],{"class":49,"line":731},[47,4089,4090],{"class":64},"    child, cancelChild ",[47,4092,214],{"class":53},[47,4094,293],{"class":64},[47,4096,1106],{"class":57},[47,4098,1109],{"class":64},[47,4100,1725],{"class":234},[47,4102,1115],{"class":53},[47,4104,1730],{"class":64},[47,4106,4107,4109,4112],{"class":49,"line":1976},[47,4108,621],{"class":53},[47,4110,4111],{"class":57}," cancelChild",[47,4113,419],{"class":64},[47,4115,4116],{"class":49,"line":1996},[47,4117,308],{"emptyLinePlaceholder":307},[47,4119,4120,4123,4126,4129,4131,4133,4135],{"class":49,"line":2010},[47,4121,4122],{"class":57},"    operation",[47,4124,4125],{"class":64},"(child, ",[47,4127,4128],{"class":1396},"\"fast\"",[47,4130,122],{"class":64},[47,4132,3713],{"class":234},[47,4134,1115],{"class":53},[47,4136,1730],{"class":64},[47,4138,4139,4141,4143,4146,4148,4150,4152],{"class":49,"line":2018},[47,4140,4122],{"class":57},[47,4142,4125],{"class":64},[47,4144,4145],{"class":1396},"\"slow\"",[47,4147,122],{"class":64},[47,4149,3662],{"class":234},[47,4151,1115],{"class":53},[47,4153,1730],{"class":64},[47,4155,4156],{"class":49,"line":3198},[47,4157,161],{"class":64},[15,4159,4160],{},[2149,4161,4162],{},"Ожидаемый ответ:",[38,4164,4169],{"className":4165,"code":4167,"language":4168},[4166],"language-text","fast: completed\nslow: cancelled — context deadline exceeded\n","text",[19,4170,4167],{"__ignoreMap":43},[15,4172,4173],{},[2149,4174,2914],{},[38,4176,4178],{"className":40,"code":4177,"language":42,"meta":43,"style":43},"\u002F\u002F parent: таймаут 200ms\n\u002F\u002F child: таймаут 500ms — но ограничен parent'ом в 200ms\n\n\u002F\u002F \"fast\" (100ms): завершается за 100ms — успевает до любого дедлайна.\n\u002F\u002F После \"fast\" прошло ~100ms.\n\n\u002F\u002F \"slow\" (300ms): нужно ещё 300ms, итого ~400ms.\n\u002F\u002F Но parent отменится через ~200ms от старта (100ms уже прошло, осталось ~100ms).\n\u002F\u002F child отменяется вместе с parent.\n\u002F\u002F Ошибка: context.DeadlineExceeded.\n\n\u002F\u002F Вывод: дочерний контекст НЕ может расширить дедлайн родителя.\n\u002F\u002F WithTimeout(parent, 500ms) реально даёт min(200ms, 500ms) = 200ms.\n",[19,4179,4180,4185,4190,4194,4199,4204,4208,4213,4218,4223,4228,4232,4237],{"__ignoreMap":43},[47,4181,4182],{"class":49,"line":50},[47,4183,4184],{"class":86},"\u002F\u002F parent: таймаут 200ms\n",[47,4186,4187],{"class":49,"line":68},[47,4188,4189],{"class":86},"\u002F\u002F child: таймаут 500ms — но ограничен parent'ом в 200ms\n",[47,4191,4192],{"class":49,"line":90},[47,4193,308],{"emptyLinePlaceholder":307},[47,4195,4196],{"class":49,"line":104},[47,4197,4198],{"class":86},"\u002F\u002F \"fast\" (100ms): завершается за 100ms — успевает до любого дедлайна.\n",[47,4200,4201],{"class":49,"line":134},[47,4202,4203],{"class":86},"\u002F\u002F После \"fast\" прошло ~100ms.\n",[47,4205,4206],{"class":49,"line":158},[47,4207,308],{"emptyLinePlaceholder":307},[47,4209,4210],{"class":49,"line":264},[47,4211,4212],{"class":86},"\u002F\u002F \"slow\" (300ms): нужно ещё 300ms, итого ~400ms.\n",[47,4214,4215],{"class":49,"line":376},[47,4216,4217],{"class":86},"\u002F\u002F Но parent отменится через ~200ms от старта (100ms уже прошло, осталось ~100ms).\n",[47,4219,4220],{"class":49,"line":582},[47,4221,4222],{"class":86},"\u002F\u002F child отменяется вместе с parent.\n",[47,4224,4225],{"class":49,"line":587},[47,4226,4227],{"class":86},"\u002F\u002F Ошибка: context.DeadlineExceeded.\n",[47,4229,4230],{"class":49,"line":598},[47,4231,308],{"emptyLinePlaceholder":307},[47,4233,4234],{"class":49,"line":618},[47,4235,4236],{"class":86},"\u002F\u002F Вывод: дочерний контекст НЕ может расширить дедлайн родителя.\n",[47,4238,4239],{"class":49,"line":632},[47,4240,4241],{"class":86},"\u002F\u002F WithTimeout(parent, 500ms) реально даёт min(200ms, 500ms) = 200ms.\n",[24,4243],{},[27,4245,4247],{"id":4246},"практика","Практика",[4249,4250,4252,4258,4292],"quiz",{"answer":522,"id":4251,"xp":1688},"concurrency-context-q1",[15,4253,4254,4255,4257],{},"Какой вариант использования ",[19,4256,21],{}," считается нормальным для Go-кода?",[4259,4260,4261],"template",{"v-slot:options":43},[4262,4263,4264,4271,4277,4284],"ul",{},[4265,4266,4267,4268,4270],"li",{},"Хранить ",[19,4269,21],{}," в структуре сервиса и переиспользовать между запросами",[4265,4272,4273,4274,4276],{},"Передавать ",[19,4275,21],{}," первым аргументом функции, которая делает внешнюю работу",[4265,4278,4279,4280,4283],{},"Создавать один глобальный ",[19,4281,4282],{},"context.Background()"," на всё приложение и отменять его после каждого запроса",[4265,4285,4273,4286,4288,4289],{},[19,4287,21],{}," только когда в функции есть ",[19,4290,4291],{},"time.Sleep",[4259,4293,4294],{"v-slot:explanation":43},[15,4295,4296],{},"Контекст описывает конкретную операцию: запрос, команду, джобу, попытку обращения к внешнему сервису. Поэтому его обычно передают первым аргументом по цепочке вызовов. Хранить контекст в структуре опасно: легко смешать жизненные циклы разных операций.",[4298,4299,4303,4306,4446],"predict",{"answer":4300,"id":4301,"xp":4302},"context canceled","concurrency-context-p1","15",[15,4304,4305],{},"Что выведет программа?",[4259,4307,4308],{"v-slot:code":43},[38,4309,4311],{"className":40,"code":4310,"language":42,"meta":43,"style":43},"package main\n\nimport (\n    \"context\"\n    \"fmt\"\n)\n\nfunc main() {\n    ctx, cancel := context.WithCancel(context.Background())\n    cancel()\n\n    select {\n    case \u003C-ctx.Done():\n        fmt.Println(ctx.Err())\n    default:\n        fmt.Println(\"work\")\n    }\n}\n",[19,4312,4313,4319,4323,4329,4337,4345,4349,4353,4361,4377,4383,4387,4393,4405,4418,4425,4438,4442],{"__ignoreMap":43},[47,4314,4315,4317],{"class":49,"line":50},[47,4316,1765],{"class":53},[47,4318,1768],{"class":57},[47,4320,4321],{"class":49,"line":68},[47,4322,308],{"emptyLinePlaceholder":307},[47,4324,4325,4327],{"class":49,"line":90},[47,4326,1777],{"class":53},[47,4328,1780],{"class":64},[47,4330,4331,4333,4335],{"class":49,"line":104},[47,4332,1785],{"class":1396},[47,4334,1788],{"class":57},[47,4336,1791],{"class":1396},[47,4338,4339,4341,4343],{"class":49,"line":134},[47,4340,1785],{"class":1396},[47,4342,1798],{"class":57},[47,4344,1791],{"class":1396},[47,4346,4347],{"class":49,"line":158},[47,4348,1317],{"class":64},[47,4350,4351],{"class":49,"line":264},[47,4352,308],{"emptyLinePlaceholder":307},[47,4354,4355,4357,4359],{"class":49,"line":376},[47,4356,178],{"class":53},[47,4358,1958],{"class":57},[47,4360,595],{"class":64},[47,4362,4363,4365,4367,4369,4371,4373,4375],{"class":49,"line":582},[47,4364,601],{"class":64},[47,4366,214],{"class":53},[47,4368,293],{"class":64},[47,4370,320],{"class":57},[47,4372,610],{"class":64},[47,4374,296],{"class":57},[47,4376,615],{"class":64},[47,4378,4379,4381],{"class":49,"line":587},[47,4380,3555],{"class":57},[47,4382,419],{"class":64},[47,4384,4385],{"class":49,"line":598},[47,4386,308],{"emptyLinePlaceholder":307},[47,4388,4389,4391],{"class":49,"line":618},[47,4390,501],{"class":53},[47,4392,65],{"class":64},[47,4394,4395,4397,4399,4401,4403],{"class":49,"line":632},[47,4396,508],{"class":53},[47,4398,511],{"class":53},[47,4400,547],{"class":64},[47,4402,550],{"class":57},[47,4404,803],{"class":64},[47,4406,4407,4409,4411,4414,4416],{"class":49,"line":637},[47,4408,714],{"class":64},[47,4410,717],{"class":57},[47,4412,4413],{"class":64},"(ctx.",[47,4415,566],{"class":57},[47,4417,615],{"class":64},[47,4419,4420,4423],{"class":49,"line":648},[47,4421,4422],{"class":53},"    default",[47,4424,825],{"class":64},[47,4426,4427,4429,4431,4433,4436],{"class":49,"line":654},[47,4428,714],{"class":64},[47,4430,717],{"class":57},[47,4432,140],{"class":64},[47,4434,4435],{"class":1396},"\"work\"",[47,4437,1317],{"class":64},[47,4439,4440],{"class":49,"line":666},[47,4441,250],{"class":64},[47,4443,4444],{"class":49,"line":674},[47,4445,161],{"class":64},[4259,4447,4448],{"v-slot:hint":43},[15,4449,4450,4451,4453,4454,4456,4457,4459],{},"После вызова ",[19,4452,2775],{}," канал ",[19,4455,886],{}," закрыт. Закрытый канал читается немедленно, поэтому ветка ",[19,4458,890],{}," не выбирается.",[4461,4462,4466,4481,4642],"code-task",{"expected":4463,"id":4464,"xp":4465},"cancelled","concurrency-context-ct1","20",[15,4467,4468,4469,4472,4473,4476,4477,4480],{},"Реализуй ",[19,4470,4471],{},"waitForSignal",": функция должна вернуть сообщение из канала ",[19,4474,4475],{},"ch",", если оно пришло первым, или строку ",[19,4478,4479],{},"\"cancelled\"",", если контекст отменился раньше.",[4259,4482,4483],{"v-slot:template":43},[38,4484,4486],{"className":40,"code":4485,"language":42,"meta":43,"style":43},"package main\n\nimport (\n    \"context\"\n    \"fmt\"\n)\n\nfunc waitForSignal(ctx context.Context, ch \u003C-chan string) string {\n    return \"\"\n}\n\nfunc main() {\n    ctx, cancel := context.WithCancel(context.Background())\n    cancel()\n\n    ch := make(chan string)\n    fmt.Println(waitForSignal(ctx, ch))\n}\n",[19,4487,4488,4494,4498,4504,4512,4520,4524,4528,4560,4567,4571,4575,4583,4599,4605,4609,4625,4638],{"__ignoreMap":43},[47,4489,4490,4492],{"class":49,"line":50},[47,4491,1765],{"class":53},[47,4493,1768],{"class":57},[47,4495,4496],{"class":49,"line":68},[47,4497,308],{"emptyLinePlaceholder":307},[47,4499,4500,4502],{"class":49,"line":90},[47,4501,1777],{"class":53},[47,4503,1780],{"class":64},[47,4505,4506,4508,4510],{"class":49,"line":104},[47,4507,1785],{"class":1396},[47,4509,1788],{"class":57},[47,4511,1791],{"class":1396},[47,4513,4514,4516,4518],{"class":49,"line":134},[47,4515,1785],{"class":1396},[47,4517,1798],{"class":57},[47,4519,1791],{"class":1396},[47,4521,4522],{"class":49,"line":158},[47,4523,1317],{"class":64},[47,4525,4526],{"class":49,"line":264},[47,4527,308],{"emptyLinePlaceholder":307},[47,4529,4530,4532,4535,4537,4539,4541,4543,4545,4547,4549,4552,4554,4556,4558],{"class":49,"line":376},[47,4531,178],{"class":53},[47,4533,4534],{"class":57}," waitForSignal",[47,4536,140],{"class":64},[47,4538,167],{"class":143},[47,4540,188],{"class":57},[47,4542,116],{"class":64},[47,4544,35],{"class":57},[47,4546,122],{"class":64},[47,4548,4475],{"class":143},[47,4550,4551],{"class":53}," \u003C-chan",[47,4553,1244],{"class":53},[47,4555,128],{"class":64},[47,4557,1902],{"class":53},[47,4559,65],{"class":64},[47,4561,4562,4564],{"class":49,"line":582},[47,4563,255],{"class":53},[47,4565,4566],{"class":1396}," \"\"\n",[47,4568,4569],{"class":49,"line":587},[47,4570,161],{"class":64},[47,4572,4573],{"class":49,"line":598},[47,4574,308],{"emptyLinePlaceholder":307},[47,4576,4577,4579,4581],{"class":49,"line":618},[47,4578,178],{"class":53},[47,4580,1958],{"class":57},[47,4582,595],{"class":64},[47,4584,4585,4587,4589,4591,4593,4595,4597],{"class":49,"line":632},[47,4586,601],{"class":64},[47,4588,214],{"class":53},[47,4590,293],{"class":64},[47,4592,320],{"class":57},[47,4594,610],{"class":64},[47,4596,296],{"class":57},[47,4598,615],{"class":64},[47,4600,4601,4603],{"class":49,"line":637},[47,4602,3555],{"class":57},[47,4604,419],{"class":64},[47,4606,4607],{"class":49,"line":648},[47,4608,308],{"emptyLinePlaceholder":307},[47,4610,4611,4613,4615,4617,4619,4621,4623],{"class":49,"line":654},[47,4612,3415],{"class":64},[47,4614,214],{"class":53},[47,4616,3420],{"class":57},[47,4618,140],{"class":64},[47,4620,3425],{"class":53},[47,4622,1244],{"class":53},[47,4624,1317],{"class":64},[47,4626,4627,4629,4631,4633,4635],{"class":49,"line":666},[47,4628,1933],{"class":64},[47,4630,717],{"class":57},[47,4632,140],{"class":64},[47,4634,4471],{"class":57},[47,4636,4637],{"class":64},"(ctx, ch))\n",[47,4639,4640],{"class":49,"line":674},[47,4641,161],{"class":64},[4259,4643,4644],{"v-slot:hints":43},[4262,4645,4646,4651,4659],{},[4265,4647,4648,4649],{},"Используй ",[19,4650,882],{},[4265,4652,4653,4654,4656,4657],{},"Чтение из ",[19,4655,886],{}," должно вернуть ",[19,4658,4479],{},[4265,4660,4653,4661,4663],{},[19,4662,4475],{}," должно вернуть полученную строку",[4665,4666,4667],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}",{"title":43,"searchDepth":68,"depth":68,"links":4669},[4670,4671,4672,4673,4676,4681,4685,4686,4691,4692],{"id":29,"depth":68,"text":30},{"id":274,"depth":68,"text":275},{"id":387,"depth":68,"text":388},{"id":460,"depth":68,"text":461,"children":4674},[4675],{"id":746,"depth":90,"text":747},{"id":1063,"depth":68,"text":1064,"children":4677},[4678,4679,4680],{"id":1220,"depth":90,"text":1221},{"id":1438,"depth":90,"text":1439},{"id":1613,"depth":90,"text":1614},{"id":1744,"depth":68,"text":1745,"children":4682},[4683,4684],{"id":2023,"depth":90,"text":2024},{"id":2143,"depth":90,"text":2144},{"id":2214,"depth":68,"text":2215},{"id":2464,"depth":68,"text":2465,"children":4687},[4688,4689,4690],{"id":2468,"depth":90,"text":2469},{"id":2632,"depth":90,"text":2633},{"id":2692,"depth":90,"text":2693},{"id":2751,"depth":68,"text":2752},{"id":4246,"depth":68,"text":4247},1782749315713]