Ну ладно, я сразу к делу.
Когда я вижу какую то очередную статью типа "как корректно завершить приложение на go" в которой вижу вот такие кусочки кода, то сразу хватаюсь за голову...
Ну ладно, я не буду тут паясничать, сразу скажу, что не так. Во-первых, в представленном коде defer cancel - это как если вы выходя из дома решили не закрывать дверь - она же сама постепенно закроется, пока вы спускаетесь по лестнице. Я сейчас, как автор этой "сломанной статьи" приведу пример из официальной документации GO.
Не понятно, что не так? А вот что: скажи те ка мне, что отменяет автор? Какой то контекст? Что то, что работало в его приложении в рамках контекста, а потом с помощью отмены контекста получило сигнал о прерывании работы? Звучит логично, но в коде написано совершенно не то - когда приложение получило сигнал о прерывании os.SIGTERM, автор создает пустой контекст с таймаутом и завершает его отложенным вызовом при выходе из процедуры main...
Нет, ну правда, мы видим, как по этим гайдам джуны пишут свой код и мало того, что сами не понимают, что такое контекст и как им пользоваться, так еще и свои гайды пишут с копированием кода друг у друга (это я уже просебя, наверн).
Давайте раз и навсегда (в стопервый раз и навсегда) запомним.
context.Сontext
Контекст - это ничего. Это просто пустышка. Но пустышка со смыслом. А вернее - это и есть смысл. Вот смотри, пошел я с утра в магазин - операция не пустяковая и при этом асинхронная - дома жена ждет, когда я принесу ей еду из магазина. Но вот я иду, а ей уже позвонили и предложили бесплатную пиццу. Она звонит мне и говорит: "не надо еду покупать, возвращайся назад". В этой дурацкой истории есть все:
- контекст = иду за едой;
- событие, которое обессмысливает контекст = доставка пиццы;
- отмена контекста = звонок мне на телефон.
Как это реализовать в го?
done - это переменная с помощью которой моя жена поняла, что я выполнил свою работу, при чем тут разницы нет - купил я уже еду или просто получил сигнал об отмене - done говорит, что я уже вернулся домой и можно выполнять процессы, которые от этого зависели. Хотите узнать подробности по этому пункту - ждите следующей статьи.
На 28 строке я создаю контекст - в этом месте я еще не иду в магазин за едой, но уже знаю, что если я задержался дольше, чем на 3 секунды - то "нахер" мне такая еда... все отменится.
30я строка - отдельной рутиной я запускаю свой поход за хлебом. Передаю ей контекст, через который жена может мне позвонить и все отменить (который, кстати сказать, отменится сам через 3 секунды), а вот мне еще права на запись в канал done, чтоб жена знала, что я вернулся домой (сама то она ведь не поймет).
Дальше всякая фигня по коду для того, чтобы знать, когда все нужно отменить. Давайте кратко для тех, кто не в теме:
signal.Notify([канал], [список сигналов ОС, которые будем получать])
- SIGINT - сигнал об остановке нашего приложения от ОС
- SIGTERM - сигнал о завершении нашего приложения от ОС
после вызова signal.Notify эти типы сигналов не будут завершать ваше приложение, а будут поступать в канал, который вы прочтете и сами завершите свое приложение.
На строке 35 сигнал будет получен и... о боже! Почему cancel без defer? Ладно, больше не паясничаю... defer - это отложенный запуск, процедура вызываемая с defer будет выполнена только после выхода из текущей процедуры (той в которой написан defer). Зачем нам сообщать об отмене по выходу из main? Мы что совсем идиоты? Ведь выход из main освободит все ресурсы немедленно и наша отмена вообще никому не будет нужна... Это как если бы мне позвонила жена, я бы поднял трубку, а она решила "похер, лучше я разведусь..." и положила трубку... ахах.... Было бы неприятно. Ну так вот и defer - это неприятно, если ты не знаешь четко, что это такое и что он нужен там, где ты его пишешь.
Дальше все просто - ждем done, потому что пиццу можно есть только, когда муж вернулся, иначе будет горько...
Давай еще пару советов и без сарказма
Используйте всегда стандартный гошный context.Сontext - все остальные реализации написаны теми, кто хочет прославиться. Все, что вам нужно уже имеется в стандартных библиотеках. Но если ты гуру, то напиши свой... и вообще, зачем ты тогда читаешь это?
Жди, когда ресурсы освободятся, если это требуется и не жди - если всем наплевать. Вот из предыдущего примера... На самом деле можно не ждать и есть пиццу отдельно, если вы питаетесь отдельно или не боитесь, что жена отъест больший кусок. Но в нашем случае это процедура main, а выход из нее - это не то же самое, что выход из любой другой процедуры, поэтому я загрузил читателя этим вопросом.
Вот как, кстати, может выглядеть процедура похода за едой, которая чувствительна к контексту. Сорян, не все влезло в фоточку.
Видите, там наверху есть defer close(done) - вот нормальный отложенный вызов - когда мы выйдем из процедуры, канал done закроется, сообщив вызывающей рутине о завершении процесса. Проще говоря: "эй, я все сделал и уже почистил за собой!". Ну а http.DefaultClient.Do само должно следить за актуальностью контекста, который был ассоциирован с http реквестом и прерывать выполнение, почуяв cancel или timeout. Таковы правила.
Ну ок, а как быть, если у нас есть самописные длинные асинхронные процессы, как правильно получать и обрабатывать отмену контекста в них? Да все очень просто...
Циклите? Просто проверяйте в начале каждой итерации, что <- ctx.Done канал не закрылся.
Читаете другой канал? Читайте из него, а еще и читайте из ctx.Done, тогда будете знать, когда остановиться... Ну и все в таком духе, паттернов несколько, а ctx.Done() один.
Напоследок
Ну и если ты уже успел воспользоваться советом джуна, который написал статью, которую я прочитал и решил написать эту статью, которую ты прочитал...
Не делай так... handleTermination получает адрес функции cancel и запускает sendTerminationMessage, который вроде как должен выполнить все нужные делишки, которые нужно выполнить перед завершением, а потом сделать cancel...
Никогда не передавай cancel в другую функцию, если ты это делаешь, то не понял смысл контекстов. Вместо этого подумай лучше, как сделать так, чтобы не таскать с собой вещи, которые потом не будешь знать куда девать... Инкапсулируй!
Представь архитектуру твоего приложения как слоеный пирог - каждый слой имеет свой контекст... Тебе бы было не очень удобно, если бы в твоем слоеном пироге слои перемешивались между собой?
Обозначь четкие границы контекстов и не пиши гайдов, если не знаешь, как это работает...