Так. Вот у меня таски лежат в одном пуле. Пул - арены, свободные ячейки в которых лежат в очереди. Получается О(1) вставка и удаление, шикардос. А вот у меня есть net_client, интерфейс для сетевого подключения. В интерфейсе лежит void* env, по сути - указатель на контекст для имплементации, который будет передаваться в методы (лежат как указатели на функции). Есть tcp клиент как имплементация net клиента. А теперь непосредственно проблема: инстанс tcp клиента у меня аллоцируется на хипе. Ну, то есть, это никак не амортизируется, хотя спокойно может. И теперь мне надо думать, как мне так по-умному либо аллокатор прокидывать, либо ещё как-то выкручиваться. Пиздец, мемори менеджмент - сложна.
Ещё и раньше у меня таски лежали просто в динамическом массиве. Ну, обёртка (источник ивентов, пока что - epoll) спавнила новую таску, а я возвращал её индекс, которым и оперировал в дальнейшем. Когда я начал складывать таски в пул, то оказалось проще отдавать указатель. Единственная проблема - индексом таски являлся int64, чтобы отрицательные значения себе загребала обёртка, дабы, например, идентифицировать серверные сокеты, и принимать самостоятельно подключения из них. Если я буду возвращать указатель, то я так сделать не смогу. Самое интуитивное решение - вынести эту логику в ивентлуп, и добавить специальный стейт ACCEPT, как я и сделал.
Но тут я понял, что если взаимодействие с сокетами у меня абстрагировано, то приём подключений - нихуя, а это тоже важно. А добавлять ещё один метод в интерфейс net клиента не хотелось - и без того структурка тяжелая, а тут ещё и метод, который будет использован только на паре сокетов. Поэтому делаем отдельный интерфейс для приёма подключений. Полез имплементировать в tcp. Вижу, надо аллоцировать tcp клиента, чтобы обернуть его в интерфейс. Понимаю - вот тут-то я и в жопе.
Пока идея - склеить интерфейс с имплементацией. Оставить в самом интерфейсе место свободное, куда я могу своё говно положить. Другой вопрос - а сколько именно-то надо? У меня уже сейчас как минимум две реализации (заглушка для тестов, и непосредственно tcp), а потом ещё и tls к этому делу прибавится. Подумываю, нарушить немного несвязанность, и захуярить в интерфейсе union со всеми известными имплементациями вместо void* окружения. Да, должно быть неплохо
Но тут я понял, что если взаимодействие с сокетами у меня абстрагировано, то приём подключений - нихуя, а это тоже важно. А добавлять ещё один метод в интерфейс net клиента не хотелось - и без того структурка тяжелая, а тут ещё и метод, который будет использован только на паре сокетов. Поэтому делаем отдельный интерфейс для приёма подключений. Полез имплементировать в tcp. Вижу, надо аллоцировать tcp клиента, чтобы обернуть его в интерфейс. Понимаю - вот тут-то я и в жопе.
Пока идея - склеить интерфейс с имплементацией. Оставить в самом интерфейсе место свободное, куда я могу своё говно положить. Другой вопрос - а сколько именно-то надо? У меня уже сейчас как минимум две реализации (заглушка для тестов, и непосредственно tcp), а потом ещё и tls к этому делу прибавится. Подумываю, нарушить немного несвязанность, и захуярить в интерфейсе union со всеми известными имплементациями вместо void* окружения. Да, должно быть неплохо
Я докатился по итогу до своего аллокатора. В glibc, конечно, и маллок умный, и юзает под капотом арены, но у меня чуть более специализированный кейс, поэтому могу себе позволить. Всё равно потом забенчмаркаю оба варианта, может, и выкину нахуй
Но тут встала такая проблема: у меня сейчас указатели на все аллоцированные арены лежат в динмассиве. Я могу сделать из арен связанный список, где в начале каждой арены будет лежать указатель на следующую. Но тогда размер арен перестанет быть кратным размеру страницы (как минимум, на х86 - а это 4096 байт), если я буду аллоцировать размер арены + 8 байт (похуй на 32-х разрядные системы, 4 байта непринципиально). Можно просто от обычного размера арены откусить 8 байт под собственные нужды. Но в таком случае, когда мне нужны объекты как раз размером со страницу - например, буфер для чтения из сокета, 4096 байт - тогда у меня ещё и 4088 байт будут "висеть" мёртвым грузом. Эх.
Может, отдельный динмассив где-то там и будет лучшим выбором. Грустно.
Но тут встала такая проблема: у меня сейчас указатели на все аллоцированные арены лежат в динмассиве. Я могу сделать из арен связанный список, где в начале каждой арены будет лежать указатель на следующую. Но тогда размер арен перестанет быть кратным размеру страницы (как минимум, на х86 - а это 4096 байт), если я буду аллоцировать размер арены + 8 байт (похуй на 32-х разрядные системы, 4 байта непринципиально). Можно просто от обычного размера арены откусить 8 байт под собственные нужды. Но в таком случае, когда мне нужны объекты как раз размером со страницу - например, буфер для чтения из сокета, 4096 байт - тогда у меня ещё и 4088 байт будут "висеть" мёртвым грузом. Эх.
Может, отдельный динмассив где-то там и будет лучшим выбором. Грустно.
Что делать
Ещё и раньше у меня таски лежали просто в динамическом массиве. Ну, обёртка (источник ивентов, пока что - epoll) спавнила новую таску, а я возвращал её индекс, которым и оперировал в дальнейшем. Когда я начал складывать таски в пул, то оказалось проще отдавать…
Ага. Хуй. Ну вот я принял новое подключение, а как мне тогда сказать обёртке, чтобы она добавила его в еполл? Сука
Forwarded from dontuto (dontu bruh🥟)
This media is not supported in your browser
VIEW IN TELEGRAM
Что делать
Ага. Хуй. Ну вот я принял новое подключение, а как мне тогда сказать обёртке, чтобы она добавила его в еполл? Сука
По итогу, я осознал, что просто запутался в собственных абстракциях. Добавил новый стейт таски EV_SERVER, который отлавливает конкретно обёртка еполла, и сама принимает подключение. Плюс вынес туда же арены с тасками, склеив таски вместе с собственно клиентами. Получилось стройненько
Что делать
Я докатился по итогу до своего аллокатора. В glibc, конечно, и маллок умный, и юзает под капотом арены, но у меня чуть более специализированный кейс, поэтому могу себе позволить. Всё равно потом забенчмаркаю оба варианта, может, и выкину нахуй Но тут встала…
С аренами решил оставить отдельно список, кстати. Это уже больше мелкие твики, которые буду пробовать тогда, когда я смогу оценить, какие преимущества либо проблемы это создаст. А то решение проблемы в вакууме какое-то
Наконец отдебажил эту хуйню.
В общем, виноваты оказались, как всегда, арены.
Во-первых, я выделял вообще всё пространство арены целиком. Поэтому, когда размер объекта не кратен размеру арены, я выделял под последний элемент частично за пределами аллоцированной зоны. Первый проёб.
Во-вторых, arena_acquire(), чтобы получить указатель, где можно схоронить нужный объект, возвращал результат list_pop() из списка доступных поинтеров. Только вот, я не учёл, что list_pop() возвращает указатель на элемент внутри списка, а не сам элемент. То есть, в списке память хранится, как void*, а я в неё совал void*, так что по итогу память в списке имела тип void**. Вот как раз его и возвращал мне list_pop(). А я возвращал пользователю вместо указателя на свободное место в арене. Так ладно это, возвращал поинтеры с промежутком в 8 байт, равное как раз таки исходному
В общем, охуел я знатно, конечно. Зато теперь вынес ещё и закрытие сокета из ивентлупа в обёртку, что позволит ещё лучше всё это дело в io_uring потом батчить
В общем, виноваты оказались, как всегда, арены.
Во-первых, я выделял вообще всё пространство арены целиком. Поэтому, когда размер объекта не кратен размеру арены, я выделял под последний элемент частично за пределами аллоцированной зоны. Первый проёб.
Во-вторых, arena_acquire(), чтобы получить указатель, где можно схоронить нужный объект, возвращал результат list_pop() из списка доступных поинтеров. Только вот, я не учёл, что list_pop() возвращает указатель на элемент внутри списка, а не сам элемент. То есть, в списке память хранится, как void*, а я в неё совал void*, так что по итогу память в списке имела тип void**. Вот как раз его и возвращал мне list_pop(). А я возвращал пользователю вместо указателя на свободное место в арене. Так ладно это, возвращал поинтеры с промежутком в 8 байт, равное как раз таки исходному
sizeof(void*). Поэтому, у меня таски перекрывали друг дружку. Из-за этого мне поебенило состояние серверной таски. Таску с поебененным состоянием получал ивентлуп. Видит - хуйня какая-то, и логгирует баг. А логгер-то я не имплементировал, у меня там заглушка. Выходит, что я вызывал функцию по рандомному адресу в памяти. Отсюда и sigill. В общем, охуел я знатно, конечно. Зато теперь вынес ещё и закрытие сокета из ивентлупа в обёртку, что позволит ещё лучше всё это дело в io_uring потом батчить
Что делать
Photo
ТУПОЙ СЫН ГОВНА БЛЯТЬ
Когда клиент отключается, я добавляю таску в очередь на дисконнект, чтобы этим занялся враппер (дабы ивентлуп своими грязными ручками не прикасался к тому, что ему не принадлежит).
Соответственно, после этого я пытаюсь заменить таску на новую активную. Если не получается - просто инкрементируй значение inactives (чтобы потом по нему определить, пустая ли очередь активных ивентов, и тогда еполл встанет на ожидание до тех пор, пока новые не появятся)
И Я ЗАБЫЛ ОБОЗНАЧИТЬ САМУ ТАСКУ КАК НЕАКТИВНУЮ БЛЯЯЯЯЯЯЯЯЯТЬ
Из-за чего, соответственно, каждый раз, как я на неё натыкался - пытался из неё что-то прочитать. Каждый раз ловлю EOF. Каждый раз добавляю в очередь на дисконнект. И каждый раз обёртка пыталась её по второму, третьему, etc. кругу её освободить. Ёбаный пиздос
Когда клиент отключается, я добавляю таску в очередь на дисконнект, чтобы этим занялся враппер (дабы ивентлуп своими грязными ручками не прикасался к тому, что ему не принадлежит).
Соответственно, после этого я пытаюсь заменить таску на новую активную. Если не получается - просто инкрементируй значение inactives (чтобы потом по нему определить, пустая ли очередь активных ивентов, и тогда еполл встанет на ожидание до тех пор, пока новые не появятся)
И Я ЗАБЫЛ ОБОЗНАЧИТЬ САМУ ТАСКУ КАК НЕАКТИВНУЮ БЛЯЯЯЯЯЯЯЯЯТЬ
Из-за чего, соответственно, каждый раз, как я на неё натыкался - пытался из неё что-то прочитать. Каждый раз ловлю EOF. Каждый раз добавляю в очередь на дисконнект. И каждый раз обёртка пыталась её по второму, третьему, etc. кругу её освободить. Ёбаный пиздос