openlang

В колбэке из C пропадает арена, которую я настроил

C-библиотека вызывает колбэк в моём коде на Odin, и колбэк не видит контекст, который я настроил перед регистрацией. У proc "c" нет неявного контекста, его приходится строить, а runtime.default_context() даёт аллокаторы по умолчанию, а не арену, которая была в context.allocator у регистрирующего кода. Всё, что колбэк выделяет, уходит в кучу и в очистку кадра не попадает.

Сейчас я передаю арену через указатель user_data библиотеки и возвращаю её на место руками.

import "base:runtime"
import "core:c"
import "core:mem"

on_event :: proc "c" (user: rawptr, code: c.int) {
	context = runtime.default_context()
	context.allocator = mem.arena_allocator((^mem.Arena)(user))
	handle_event(int(code))
}

Работает. Указатель на арену должен пережить регистрацию, и это никто не проверяет. И каждый колбэк повторяет эти две строки. Есть ли паттерн аккуратнее, или это просто цена перехода в C?

Разговор продолжается 8

Следуя доводам
  1. Ссылка на ответ

    Указатель user_data одолжен библиотеке, и арена за ним библиотеке не принадлежит. Язык C не говорит, как долго библиотека хранит этот указатель, поэтому это должны сказать заголовок или документация, и обычный ответ - до парного вызова отмены регистрации. Значит, арена должна пережить вызов отмены регистрации, а не просто последний колбэк, который вам довелось увидеть.

    На практике найдите функцию отмены регистрации и прочитайте, что она обещает про колбэк, который уже выполняется в момент её вызова. Если документация молчит, считайте, что она возвращается, не дожидаясь его.

  2. Ссылка на ответ

    Дело в соглашении о вызовах. Процедура Odin по умолчанию получает контекст скрытым аргументом-указателем, а вызывающий из C передаёт только то, что есть в прототипе, так что default_context() строит новый контекст, а не возвращает ваш.

  3. Ссылка на ответ

    До вопроса об аллокаторе есть вопрос о порядке. Библиотека вызывает колбэк только внутри вызова, который делаете вы, вроде функции poll или dispatch, или из собственных потоков? В первом случае колбэк выполняется в вашем потоке в выбранный вами момент, и схема с ареной годится. Во втором меняются все допущения об арене.

  4. Ссылка на ответ

    Тогда поставьте отмену регистрации туда, где владелец арены не сможет её пропустить. Регистрируйте сразу после инициализации арены и на следующей строке пишите defer unregister(handle). Odin выполняет defer в обратном порядке, так что отмена регистрации идёт раньше очистки арены на любом выходе из этой области видимости, включая ранние возвраты. Порядок это закрепляет. С колбэком, который ещё выполняется внутри библиотеки, когда отмена регистрации вернулась, это ничего не делает, а это ровно тот случай, который nullbyte предлагал выяснить.

  5. Ссылка на ответ

    Из собственного потока. В заголовке об этом одна фраза, колбэки выполняются во внутреннем воркере. Так что схема с ареной была неверной, просто это не проявлялось. mem.Arena не рассчитана на одновременное использование, а главный цикл выделяет из той же арены, пока работает колбэк.

    Переделал. Теперь колбэк ничего не выделяет. Он кладёт код события в кольцо фиксированного размера под мьютексом, а цикл кадра в начале каждого кадра разбирает кольцо в главном потоке, где действительны и обычный контекст, и арена. Две строки из первого поста ушли. Размер кольца фиксирован, так что открытый вопрос переехал в то, что делает колбэк, когда оно заполнено.

  6. Ссылка на ответ

    О, это форма, которой я доверяю больше всего. Воркер передаёт маленький факт, а поток-владелец делает настоящую работу в момент, который выбрал сам. Tcl пришёл к тому же правилу для интерпретаторов, другие потоки ставят событие через Tcl_ThreadQueueEvent, а собственный поток интерпретатора выполняет его позже.

    Для полного кольца мягкий вариант - выбрасывать самое новое событие и считать выброшенные, а цикл кадра пусть пишет это число в лог. Блокировать воркер библиотеки, пока кадр не разберёт кольцо, страшнее, потому что библиотека может держать свои блокировки, пока вызывает вас.

  7. Ссылка на ответ

    Кольцо убирает арену из колбэка, а вопрос времени жизни из моего первого ответа теперь относится к кольцу и его мьютексу. Оба доступны через user_data, значит, оба должны оставаться действительными, пока может выполняться хоть один колбэк. Заголовок должен сообщить ровно одно, наступает ли этот момент с возвратом из отмены регистрации. Если он молчит, освободить кольцо безопасно можно только после остановки самой библиотеки.

  8. Ссылка на ответ

    В словах "не может выполняться ни один колбэк" прячутся две отдельные вехи. Первая - отмена регистрации вернулась, и библиотека не начнёт новых вызовов. Вторая - все уже начатые вызовы закончились. Счётчик внутри колбэка сам по себе вторую веху не обеспечит, потому что вызов может быть уже внутри кода диспетчеризации библиотеки, после решения вызвать вас, но ещё до вашей первой строки, где счётчик увеличивается.

    Так что вторую веху должна дать библиотека, в виде ожидающей отмены регистрации или остановки, которая дожидается своего воркера. Если нет ни того ни другого, держать такое маленькое кольцо живым до конца процесса - вполне нормальный ответ.

Другие стороны того же вопроса

  1. Разворот C-указателя через .? - это утверждение

    .? на указателе из C - это утверждение, а не проверка. Оно говорит, что null здесь невозможен. В сборке с проверками безопасн…

    Ответы 5zig
  2. Сброс одного флага через |= сбросил все

    |= на регистре состояния со сбросом записью единицы сбрасывает больше, чем названный бит. Флаг переполнения сбрасывается запи…

    Ответы 5c
  3. Как выглядит проверка канарейки в листинге -O2

    Три маленькие функции на C, gcc -O2 -fstack-protector-strong -S -masm=intel, x86-64 Linux. read_line и read_count получают од…

    Ответы 0assemblyc