Что делать
Сука шизоид что ему не нравится, какие нахуй скобки
Ага. Понятно. У нас же
struct epoll_event выглядит следующим образом. Вот в data я сую свою task. А data у нас - юнион. Вот оно и просило, чтобы я более явно это всё дело инициализировал. Ну и хуйняКстати, между делом наткнулся на какой-то старый баг гсс. В 2014 они не умели в zero-initializer вида
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53119
struct myStruct my_var = { 0 };. А ещё, чтобы собрать С компилятор, нужны плюсы🗿https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53119
Я переписал с нуля ивентлуп. Получилось проще, красивее и удобнее, но проблемы остались всё те же - опять растут арены, опять список с кэшем ивентов, который не должен превышать 16 вхождений, бесконечно разрастается. Ну, то есть, старые баги вернулись, только теперь ситуация сука ещё хуже. Зато код красивее, да.
🏆1
Арены я починил, я просто забывал при ошибке из сокета возвращать указатель на буфер. А вот ошибка с вечно раздувающимся массивом кэша ивентов, которую я ещё в старой имплементации отдебажить так и не смог, осталась. Кажется, сейчас будет интересно
Но, кстати, я нашёл какое-то сообщение в рассылке линукса на тему edge-triggered режима еполла. Там упоминалось в том числе и то, что практически все всё равно используют кэш ивентов. Потому что, как известно, edge-triggered режим обозначает как раз то, что будут приходить оповещения о сокетах только тогда, когда их состояние меняется. В данном случае, состояний два - сокет полностью вычитан/буфер записи забит, и тут в нём появляется что-то новое/появляется достаточно места в буфере для записи. То есть, получив условно единожды ивент о том, что такой-то сокет доступен на чтение, до тех пор, пока ты не дочитаешь до ошибки EAGAIN (данных больше не осталось, read buffer is exhausted) - снова тебе этот ивент не прилетит.
Собственно, держать где-то рядышком структуру данных, где все доступные для какой-либо из операций сокеты, и называется кэшем ивентов. А сказано было, что только так обычно и достигают highly efficient I/O. Значит, я на правильном пути
Но, кстати, я нашёл какое-то сообщение в рассылке линукса на тему edge-triggered режима еполла. Там упоминалось в том числе и то, что практически все всё равно используют кэш ивентов. Потому что, как известно, edge-triggered режим обозначает как раз то, что будут приходить оповещения о сокетах только тогда, когда их состояние меняется. В данном случае, состояний два - сокет полностью вычитан/буфер записи забит, и тут в нём появляется что-то новое/появляется достаточно места в буфере для записи. То есть, получив условно единожды ивент о том, что такой-то сокет доступен на чтение, до тех пор, пока ты не дочитаешь до ошибки EAGAIN (данных больше не осталось, read buffer is exhausted) - снова тебе этот ивент не прилетит.
Собственно, держать где-то рядышком структуру данных, где все доступные для какой-либо из операций сокеты, и называется кэшем ивентов. А сказано было, что только так обычно и достигают highly efficient I/O. Значит, я на правильном пути
А ведь мне ещё ой сколько работы по оптимизациям лупа предстоит) Как минимум, думаю, можно будет сделать небольшой "шедулер" - если ивентов достаточно мало, мы можем себе позволить несколько раз пройтись по кэшу ивентов, перед тем, как снова обратиться к еполлу за новыми.
Ещё, кстати, заметка о том, как я сейчас работаю с ним. Я обычно дёргаю
Ещё, возможно, мне стоит ограничить размер кэша, чтобы не было такого, что я трачу где-то секунду на то, чтобы по нему только пройтись и обработать, и всё это время другие клиенты будут ждать. Ну, то есть, они и так будут ждать, просто с ограниченным размером кэша, распределение будет немного более честным, и позволит дать хоть немного воздуха большему количеству подключений
epoll_wait() с таймаутом в 0 миллисекунд, чтобы получить все новые мгновенно. Это нужно для того, чтобы я не уходил в ожидание новых ивентов в то время, как у меня сидят ждут обработки таски в кэше. Но если кэш пустой, то, дабы не растрачивать попусту циклы, я выставляю таймаут в -1, чтобы проснуться только тогда, когда новые ивенты возникнут. Собственно, минимизация количества вызовов epoll_wait() будет моей первой задачей в попытках оптимизировать что-то, потому что, помимо него, я делаю только сисколлы для чтения и записи, что я уже сильно-то не оптимизирую (io_uring временно отложим). Ещё, возможно, мне стоит ограничить размер кэша, чтобы не было такого, что я трачу где-то секунду на то, чтобы по нему только пройтись и обработать, и всё это время другие клиенты будут ждать. Ну, то есть, они и так будут ждать, просто с ограниченным размером кэша, распределение будет немного более честным, и позволит дать хоть немного воздуха большему количеству подключений
Что делать
Арены я починил, я просто забывал при ошибке из сокета возвращать указатель на буфер. А вот ошибка с вечно раздувающимся массивом кэша ивентов, которую я ещё в старой имплементации отдебажить так и не смог, осталась. Кажется, сейчас будет интересно Но, кстати…
Но у меня есть стойкое ощущение, что я могу получить новый ивент по сокету даже без того, чтобы дочитаться до EAGAIN. иначе я не могу объяснить, почему у меня постоянно новые ивенты появляются, при том, что я не успеваю получить ошибку
Что делать
Но у меня есть стойкое ощущение, что я могу получить новый ивент по сокету даже без того, чтобы дочитаться до EAGAIN. иначе я не могу объяснить, почему у меня постоянно новые ивенты появляются, при том, что я не успеваю получить ошибку
Если так, то я в жопе. А выглядит всё пока именно так
Что делать
Если так, то я в жопе. А выглядит всё пока именно так
Первые EAGAIN всплывают только после того, как я получаю дубликатный ивент. Пиздец.
Хотя, на самом деле, у меня есть два варианта. Первое - у меня поломанный еполл (учитывая, что у меня твикнутое ядро от cachyos, то допущение валидно). Второе - чтобы не дрочить epoll_ctl с EPOLL_CTL_MOD каждый раз, когда я сменяю состояние таски с READ на WRITE и наоборот, я выставил изначально интересы
Ну так вот. Подозреваю, что мне просто ивент прилетает дважды, один раз - по поводу чтения, второй раз - по поводу записи. Тогда у меня в кэше оказывается две таски - точнее, два указателя на одну и ту же таску. Таким образом, обрабатывая таски в кэше, я обрабатываю одну и ту же как минимум дважды. Первый раз получаю данные, второй раз получаю EAGAIN. Ещё хорошо, если других ивентов нет - тогда я просто сую вместо дубликатной таски нулл. Поскольку я словил EAGAIN, при втором проходе получаю снова эту же таску. И дубликат, который сейчас равен нуллу, я заменяю на "новый" ивент. То есть, совершенно тем же, что и было там до этого. Цикл повторяется.
А если добавить ещё парочку подключений, которые тоже активно обмениваются сообщениями - ситуация совсем уже распиздос. Тут и с одним-то подключением кэш будет расти, хоть и медленно, а с 16 одновременными подключениями он улетает в небеса. А я откровенно хуй знает, что с этим делать...
Хотя, на самом деле, у меня есть два варианта. Первое - у меня поломанный еполл (учитывая, что у меня твикнутое ядро от cachyos, то допущение валидно). Второе - чтобы не дрочить epoll_ctl с EPOLL_CTL_MOD каждый раз, когда я сменяю состояние таски с READ на WRITE и наоборот, я выставил изначально интересы
EPOLLIN | EPOLLOUT | EPOLLRDHUP (ну, ладно, последнее - как костыль, чтобы такие ивенты просто скипать, это еполл сам по себе такую хуйню творит - см. линк на СО от 1 августа)Ну так вот. Подозреваю, что мне просто ивент прилетает дважды, один раз - по поводу чтения, второй раз - по поводу записи. Тогда у меня в кэше оказывается две таски - точнее, два указателя на одну и ту же таску. Таким образом, обрабатывая таски в кэше, я обрабатываю одну и ту же как минимум дважды. Первый раз получаю данные, второй раз получаю EAGAIN. Ещё хорошо, если других ивентов нет - тогда я просто сую вместо дубликатной таски нулл. Поскольку я словил EAGAIN, при втором проходе получаю снова эту же таску. И дубликат, который сейчас равен нуллу, я заменяю на "новый" ивент. То есть, совершенно тем же, что и было там до этого. Цикл повторяется.
А если добавить ещё парочку подключений, которые тоже активно обмениваются сообщениями - ситуация совсем уже распиздос. Тут и с одним-то подключением кэш будет расти, хоть и медленно, а с 16 одновременными подключениями он улетает в небеса. А я откровенно хуй знает, что с этим делать...
Наверное, стоит всё же перестать носом воротить, и начать смотреть, как остальные кэш ивентов оборудовали. Потому что я, по всей видимости, охуею разбираться, почему еполл - настолько ущербный и поломанный. Нет, ну реально, он отправляет EPOLLRDHUP ивенты даже тогда, когда ты их и не просил вовсе! Так ладно это. Он это делает избирательно! Не всегда! Он, сука, НЕКОНСИСТЕНТНЫЙ!!!
Надеюсь, что это я его просто не осилил, но это всё ещё какой-то пиздес.
Надеюсь, что это я его просто не осилил, но это всё ещё какой-то пиздес.
https://idea.popcount.org/2017-03-20-epoll-is-fundamentally-broken-22/
продолжение охуенной серии статей. Здесь, конечно, не описывается моя проблема, а только то, что, несмотря на заявления в мане, удалять сокет из еполла перед close() необходимо. А теперь осталось понять, КАКИМ ОБРАЗОМ МНЕ ДУБЛИРУЮЩИЕСЯ ИВЕНТЫ ПРИЛЕТАЮТ
продолжение охуенной серии статей. Здесь, конечно, не описывается моя проблема, а только то, что, несмотря на заявления в мане, удалять сокет из еполла перед close() необходимо. А теперь осталось понять, КАКИМ ОБРАЗОМ МНЕ ДУБЛИРУЮЩИЕСЯ ИВЕНТЫ ПРИЛЕТАЮТ
idea.popcount.org
Epoll is fundamentally broken 2/2 —
Idea of the day
Idea of the day
Forwarded from Hacker News
Hacker News
Linear Algebra Done Wrong (2004) Article, Comments
Заебись. Вводим транспозицию, чтобы место на бумаге сэкономить
Так атаки с амплификацией трафика - это просто отправка IP пакетов с полем source address жертвы? А разговоров-то было...
Я наконец допёр (с помощью умного поляка), почему при инстанциации сокета мы не указываем условно
Ещё у AF_UNIX есть интересная штука - SOCK_SEQPACKET, которая по своим свойствам чётко между udp и tcp. То есть, оно даёт те же гарантии доставки и сохранения очередности, что и tcp, но сигнализирует об окончании пакета (в оригинале они это называют record) явно, как и udp. При том, что такая удобная и очень дружелюбная к программисту штука есть, в целом-то, только для AF_UNIX, в AF_INET/6 аналогичного транспортного протокола нет. Хоть его и можно имитировать на уровне tcp, что, отнюдь, всё ещё приводит к костылям. Возможно, на этом этапе уже действительно проще вынести это как часть собственного протокола над tcp (который у тебя есть примерно всегда).
Кому интересно, вот текст, описывающий всё это, только языком более компетентного человека: https://urchin.earth.li/~twic/Sequenced_Packets_Over_Ordinary_TCP.html
socket(AF_INET, SOCK_TCP), а SOCK_STREAM, SOCK_DGRAM. Сетевой стэк следует той философии, что ты выбираешь не столько протокол, сколько требуемую семантику от подключения. Соответственно, семантика SOCK_STREAM может быть реализована и по другому семейству, и не по TCP. Всё тот же AF_UNIX к примеру.Ещё у AF_UNIX есть интересная штука - SOCK_SEQPACKET, которая по своим свойствам чётко между udp и tcp. То есть, оно даёт те же гарантии доставки и сохранения очередности, что и tcp, но сигнализирует об окончании пакета (в оригинале они это называют record) явно, как и udp. При том, что такая удобная и очень дружелюбная к программисту штука есть, в целом-то, только для AF_UNIX, в AF_INET/6 аналогичного транспортного протокола нет. Хоть его и можно имитировать на уровне tcp, что, отнюдь, всё ещё приводит к костылям. Возможно, на этом этапе уже действительно проще вынести это как часть собственного протокола над tcp (который у тебя есть примерно всегда).
Кому интересно, вот текст, описывающий всё это, только языком более компетентного человека: https://urchin.earth.li/~twic/Sequenced_Packets_Over_Ordinary_TCP.html
❤1