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