Я вспомнил пароль от канала
Ввожу в курс дела: я активно занят разработкой вебсервера. Естественно, у меня часто возникают курьёзы, и просто забавные ситуации, о которых я здесь и буду рассказывать
Начну, пожалуй, с того, как пофиксив проблему, я обнаружил баг в тестах
Суть в том, что я люблю переиспользуемость. Больше переиспользуемости богу переиспользуемости! И, исходя из этого, мапа с заголовками запроса каждый раз была одна и та же. И она никак не очищалась. То есть, заголовки предыдущего запроса вдруг оказывались с заголовками следующего
Починив проблему, тесты парсера вдруг улетели в дедлок. Потупив полчаса, я осознал, что все тесты проходят, кроме одного - странная особенность фреймворка. Посмотрев подробнее, что не так, я осознал, что я хочу видеть в запросе заголовок, как и во всех остальных тестах. А в самом запросе его нет. И работало это как раз таки потому, что другие тесты оставляли после себя этот самый заголовок!
Забавно фиксить проблему в коде, в следствии чего находить баг в тестах.
Ввожу в курс дела: я активно занят разработкой вебсервера. Естественно, у меня часто возникают курьёзы, и просто забавные ситуации, о которых я здесь и буду рассказывать
Начну, пожалуй, с того, как пофиксив проблему, я обнаружил баг в тестах
Суть в том, что я люблю переиспользуемость. Больше переиспользуемости богу переиспользуемости! И, исходя из этого, мапа с заголовками запроса каждый раз была одна и та же. И она никак не очищалась. То есть, заголовки предыдущего запроса вдруг оказывались с заголовками следующего
Починив проблему, тесты парсера вдруг улетели в дедлок. Потупив полчаса, я осознал, что все тесты проходят, кроме одного - странная особенность фреймворка. Посмотрев подробнее, что не так, я осознал, что я хочу видеть в запросе заголовок, как и во всех остальных тестах. А в самом запросе его нет. И работало это как раз таки потому, что другие тесты оставляли после себя этот самый заголовок!
Забавно фиксить проблему в коде, в следствии чего находить баг в тестах.
🔥2
Кстати, ещё интересных штук о заголовках.
У парсера есть свой буфер, куда он кладёт текущий заголовок (ключ и значение). Как только он кончается - его нужно куда-то копировать, иначе записанное значение будет перезаписано последующими
Решение в лоб - на каждое значение аллоцировать свой массив, который будет после аккуратно складироваться в sync.Pool. Но почему бы не взять один огромный слайс, в котором будут лежать все значения?
Ведь у нас есть слайсы, которые могут указывать только на свою часть!
Да, у этого способа есть свои проблемы. Например, при росте буфера, все его предыдущие версии будут оставаться висеть, и GC не сможет их очистить, потому что они всё ещё нам нужны. Однако случается это один раз (потом буфер будет больше, и вмещать в себе всё, что надо), и висеть мёртвым грузом старые версии будут лишь на время обработки запроса, после чего со спокойной душой смогут быть очищены GC.
Преимущество этого способа в том, что нам даже sync.Pool не нужен - вот он, один наш слайс, всегда здесь, при окончании обработки запроса просто
У парсера есть свой буфер, куда он кладёт текущий заголовок (ключ и значение). Как только он кончается - его нужно куда-то копировать, иначе записанное значение будет перезаписано последующими
Решение в лоб - на каждое значение аллоцировать свой массив, который будет после аккуратно складироваться в sync.Pool. Но почему бы не взять один огромный слайс, в котором будут лежать все значения?
Ведь у нас есть слайсы, которые могут указывать только на свою часть!
Да, у этого способа есть свои проблемы. Например, при росте буфера, все его предыдущие версии будут оставаться висеть, и GC не сможет их очистить, потому что они всё ещё нам нужны. Однако случается это один раз (потом буфер будет больше, и вмещать в себе всё, что надо), и висеть мёртвым грузом старые версии будут лишь на время обработки запроса, после чего со спокойной душой смогут быть очищены GC.
Преимущество этого способа в том, что нам даже sync.Pool не нужен - вот он, один наш слайс, всегда здесь, при окончании обработки запроса просто
buffer = buffer[:0], и готово!Начал трогать CI в виде гитхаб-экшонов. Оказывается, не настолько и страшная штука!
При каждом пулл-реквесте в указанные ветки (у меня это мастер) прогоняет тесты, билдит примеры (проверяя их тем самым на работоспособность), и заодно прикрутил ещё CodeQL-анализ. Последнее, правда, пока что бесполезное, но выглядит круто, и в случае чего - перепроверяет, что в мастере будет работоспособная штука
Был у меня ещё, к слову, забавный момент. У меня также подключён deepsource, и при одном из PR он заблокировал мерж из-за потенциального бага. Оказывается, я передавал инстанс не по указателю, а по значению, из-за чего важная часть сервера потенциально не работала. Круто, скажете вы? А вот это должно было быть покрыто тестами, отвечу я
При каждом пулл-реквесте в указанные ветки (у меня это мастер) прогоняет тесты, билдит примеры (проверяя их тем самым на работоспособность), и заодно прикрутил ещё CodeQL-анализ. Последнее, правда, пока что бесполезное, но выглядит круто, и в случае чего - перепроверяет, что в мастере будет работоспособная штука
Был у меня ещё, к слову, забавный момент. У меня также подключён deepsource, и при одном из PR он заблокировал мерж из-за потенциального бага. Оказывается, я передавал инстанс не по указателю, а по значению, из-за чего важная часть сервера потенциально не работала. Круто, скажете вы? А вот это должно было быть покрыто тестами, отвечу я
Чистота функций
Всё же считаю следует рассказать об этом. Частично буду рассказывать на примере хаскелля
Сама по себе чистая функция - это функция без побочных эффектов. То есть, фактически, это отсутствие каких-либо мутаций объектов. Например, в хаскелле это возведено в абсолют, и (практически) любая функция в нём есть чистая. Даже добавление элемента в список создаёт новый изменённый список
* Практически любая - потому что мир неидеален, и любое взаимодействие с ОС является грязным; подобные функции там обозначаются специальным типом IO
Естественно, не одним лишь хаскеллем едины. Писать чистые функции можно на практически любом (исключая всеразличные экзотические языки, кто их там знает) языке, вопрос лишь в том, что с этим могут происходить нюансы.
Продолжение идеи в посте ниже 👇
Всё же считаю следует рассказать об этом. Частично буду рассказывать на примере хаскелля
Сама по себе чистая функция - это функция без побочных эффектов. То есть, фактически, это отсутствие каких-либо мутаций объектов. Например, в хаскелле это возведено в абсолют, и (практически) любая функция в нём есть чистая. Даже добавление элемента в список создаёт новый изменённый список
* Практически любая - потому что мир неидеален, и любое взаимодействие с ОС является грязным; подобные функции там обозначаются специальным типом IO
Естественно, не одним лишь хаскеллем едины. Писать чистые функции можно на практически любом (исключая всеразличные экзотические языки, кто их там знает) языке, вопрос лишь в том, что с этим могут происходить нюансы.
Продолжение идеи в посте ниже 👇
Допустим, у меня есть подобный конструктор ответов (пикрил 1). В каждый метод (пикрил 2) я передаю структуру по значению - то есть, она копируется. А значит, исходную структуру я не мутирую - это чистые методы.
А вот нюанс (как и написано в комментарии) заключается в заголовках. Это хэшмапа, которая не копируется при передаче по значению, ведь внутри она указывает на конкретные участки памяти. И это может привести к неприятным последствиям в виде передачи заголовков от предыдущего запроса в следующем
Выход в данной ситуации - сделать структуру как nil и не давать пользователю к ней прикасаться. В таком случае, в методе
А вот нюанс (как и написано в комментарии) заключается в заголовках. Это хэшмапа, которая не копируется при передаче по значению, ведь внутри она указывает на конкретные участки памяти. И это может привести к неприятным последствиям в виде передачи заголовков от предыдущего запроса в следующем
Выход в данной ситуации - сделать структуру как nil и не давать пользователю к ней прикасаться. В таком случае, в методе
WithHeader нам приходится проверять на nil (и если хэшмапа оказывается действительно nil, создаём новую с нашем заголовком). Получилось неприятно, однако сохраняется чистота. К слову, со слайсами данный трюк был бы немного попроще🤔> Instructions, registers, and assembler directives are always in UPPER CASE to remind you that assembly programming is a fraught endeavor. (Exception: the g register renaming on ARM.)
Вырезка из доки по go asm. Спасибо
Вырезка из доки по go asm. Спасибо
https://github.com/golang/go/blob/master/doc/go_mem.html#L36-L41
If you must read the rest of this document to understand the behavior of your program, you are being too clever.
Don't be clever.
Ну ладно
If you must read the rest of this document to understand the behavior of your program, you are being too clever.
Don't be clever.
Ну ладно
GitHub
go/go_mem.html at master · golang/go
The Go programming language. Contribute to golang/go development by creating an account on GitHub.
🤔2
Оказывается, существует
Вызов финализатора не гарантирован, ведь программа может завершить свою работу ещё до того, как будет собран необходимый объём мусора
Если указатель на объект занимает 0 байт, то нет гарантий, что финализатор будет вызван. Также нет гарантий, что финализатор будет когда-либо вызван для объектов в package-level, так как они могут быть аллоцированы линкером, а не на хипе
Чтобы убрать финализатор объекта, достаточно установить финализатор в значение nil
runtime.SetFinalizer(x, f), где x - указатель на объект (в случае локальной переменной, при взятии указателя, она будет аллоцирована на хипе), а f - функция, способная принять аргументом x (это может быть как variadic, так и generic функция). Когда garbage collector собирается очистить x, но видит, что для него есть финализатор - сначала вызывается финализатор в отдельной горутине (одна для всех финализаторов), сам же объект будет очищен при следующем вызове gc, если только финализатор завершил свою работуВызов финализатора не гарантирован, ведь программа может завершить свою работу ещё до того, как будет собран необходимый объём мусора
Если указатель на объект занимает 0 байт, то нет гарантий, что финализатор будет вызван. Также нет гарантий, что финализатор будет когда-либо вызван для объектов в package-level, так как они могут быть аллоцированы линкером, а не на хипе
Чтобы убрать финализатор объекта, достаточно установить финализатор в значение nil
🤔1😱1
День открытий. Впервые за 2/3 года, как я изучаю go, узнал об операторе
fallthrough. Эта штука "проваливается" в кейс снизу, не обращая внимание на условие этого самого нижнего кейса. Кстати, в финальном кейсе (просто последний; может быть и не дефолтом вовсе) использование этого оператора запрещено👍1😁1
Simple request/response
Хоть и не про Go, однако все равно интересно (по крайней мере мне).
Из rfc1945 (посвящённому http/1.0), прочитал про такую штуку, как simple request/response. Суть в следующем:
– Simple request - это обязательно GET-запрос, состоящий из соответственно метода и пути запроса. После пути запроса, следует [CR]LF, без каких-либо заголовков
– Simple response - доступен только в случае simple request. Сервер отправляет суто тело ответа, без строки ответа и каких-либо заголовков. Терминатором тела является закрытие подключения, поскольку стандарт требует, чтобы сервер отвечал по http/0.9 протоколу (сам запрос автоматически должен считаться, как http/1.0)
Из нюансов - клиент должен сам детерминировать MIME-тип. Однако не беда, вон, мой сервер тоже не сильно мимами козыряет🌚
По поводу использования на практике - сказать, увы, не смогу. Однако звучит прикольно - поддерживать мы это, конечно же, не будем
Хоть и не про Go, однако все равно интересно (по крайней мере мне).
Из rfc1945 (посвящённому http/1.0), прочитал про такую штуку, как simple request/response. Суть в следующем:
– Simple request - это обязательно GET-запрос, состоящий из соответственно метода и пути запроса. После пути запроса, следует [CR]LF, без каких-либо заголовков
– Simple response - доступен только в случае simple request. Сервер отправляет суто тело ответа, без строки ответа и каких-либо заголовков. Терминатором тела является закрытие подключения, поскольку стандарт требует, чтобы сервер отвечал по http/0.9 протоколу (сам запрос автоматически должен считаться, как http/1.0)
Из нюансов - клиент должен сам детерминировать MIME-тип. Однако не беда, вон, мой сервер тоже не сильно мимами козыряет🌚
По поводу использования на практике - сказать, увы, не смогу. Однако звучит прикольно - поддерживать мы это, конечно же, не будем
Что делать
context.WithValue медленный длиннопоста не будет. Он под капотом рефлексию трогает. Придется в индиге ещё и свои контексты пилить
Кстати, почему key использует женерик
any, а не comparable, раз ему такие и нужны?Сделал для тела запроса структуру, имплементирующую
Правда, из-за некоторых особенностей моей потоковой работы с телом, пришлось чутка закостылить с тем, чтобы сообщать ядру о завершении обработки кусочка тела только при следующем вызове. Но работает стабильно
* Требование сообщать ядру о завершении обработки кусочка тела связана с тем, что этот кусочек тела в виде слайса байт тянется прямиком из tcp сервера, а точнее - буфера для чтения. А значит, если убрать эту синхронизацию, то получаем UB - слайс может быть перезаписан новыми данными
io.Reader. Теперь можно request.Reader(), и получить свой io.Reader. Красиво.Правда, из-за некоторых особенностей моей потоковой работы с телом, пришлось чутка закостылить с тем, чтобы сообщать ядру о завершении обработки кусочка тела только при следующем вызове. Но работает стабильно
* Требование сообщать ядру о завершении обработки кусочка тела связана с тем, что этот кусочек тела в виде слайса байт тянется прямиком из tcp сервера, а точнее - буфера для чтения. А значит, если убрать эту синхронизацию, то получаем UB - слайс может быть перезаписан новыми данными
Итак, до беты остаётся лишь имплементировать динамический роутинг. Есть идея ставить в пути маркер
Почему не регекспы? Ну, они медленные. У меня лично сейчас процесс роутинга укладывается в 80нс, при условии, что у меня получение по ключу из двух хэшмап. Регулярки же укладываются в 700-800нс
{name}, где name - либо пустота, либо имя значения в контексте, которое появится в случае, если путь запроса соответствует шаблону. Поскольку маркер может стоять только между двумя слэшами, нам остаётся лишь пройтись по статичным участкам, и при наступлении маркера - схоронить значение в контекст, где концом значения является слэш (или конец строки)Почему не регекспы? Ну, они медленные. У меня лично сейчас процесс роутинга укладывается в 80нс, при условии, что у меня получение по ключу из двух хэшмап. Регулярки же укладываются в 700-800нс
🔥2
От канала отписался один человек. И он не узнает, насколько быстро мой алгоритм матчит пути по шаблону…
🤡2