Ориентация главы

«Встраивание» — первоклассная точка зрения в дизайне XRT — API каждой главы живут в предположении «быть частью программы-хозяина». Эта глава собирает разбросанные по главам точки интеграции в полное руководство: память (xrtSetAllocator захватывает на входе в main — дальше все выделения библиотеки идут хозяину; блокировка по первому выделению хранит стабильность); ошибки (отображение потокового слота ошибки в систему исключений хозяина — каждое «возвращаемое значение объявляет отказ» есть кандидат-источник исключения хозяина; объекты ошибки переносятся между слоями); обрезка (хозяин выбирает только нужные корневые модули — семантика обрезки главы 131 на стороне хозяина; минимальная интеграция не растёт незаметно); наблюдаемость (почти нулевая по стоимости всегда включённая счётная memory_stats — панель здоровья хозяина читает напрямую); выбор формы (одиночный заголовок/статическая/динамическая — три вида потребителей главы 132 как решение хозяина). Высшая цель встраивания: библиотека — деталь хозяина, не параллельное королевство — память под вашим управлением, ошибки переводите вы, размер определяете вы.

135.1引入

Самая частая форма провала интеграции хозяина — «библиотека хочет быть хозяином»: malloc по собственной инициативе (памятный бюджет хозяина уходит из-под контроля), ошибки только печатаются (система ошибок хозяина не может их поймать), полный объём втащен (встраиваемое не помещается), глобальное состояние решает само (сосуществование многих экземпляров/версий падает). Каждая точка интеграции XRT — прямой ответ на эти провалы — и каждая закреплена тестами (замена распределителя — двухтрековая проверка allocator_tour, отображение ошибок — проверка изоляции слота _threads, обрезка — утверждения профиля размера, stats — базовая линия накладных расходов).

Стоит сперва построить тайминг-картину захвата: первый акт main меняет распределитель (до блокировки) → инициализация инфраструктуры хозяина → включение stats по надобности → в период бизнеса слот ошибки ходит с потоком → на финале снимок stats + Drain всех объектов библиотеки. Неправильный порядок (смена после первого выделения) — ровно сценарий отказа главы 5 — эта глава нанизывает их на таймлайн.

135.2概念

Захват памяти: распределитель и блокировка

1Таймингна входе в main (до любого выделения XRT) -> xrtSetAllocator(&распределитель хозяина)
2После захватавсе выделения библиотеки (включая внутренности пулов/буферов) идут хозяину - бюджет, статистика, arena под управлением хозяина
3Блокировкапосле первого выделения SetAllocator отвергается - сценарии многих TU/модуулей больше не меняют
4Формачетыре входа at-семейства (Alloc/Calloc/Realloc/Dup) + контекст обратного вызова

Частые формы распределителя хозяина: arena (bump-выделение на запрос — весь блок освобождается по концу запроса, прикладное применение главы 7), счётчик бюджета (квотный распределитель — потолок памяти библиотеки принадлежит политике хозяина), прокси-учёт (пересылка системному распределителю + бухгалтерия — панель хозяина читает потребление библиотеки напрямую). Смысл блокировки ещё раз: два модуля библиотеки (или два места кода хозяина) оба пытаются сменить распределитель — первый пришёл, второй отвергнут — стабильность интеграции прежде гибкости.

Отображение ошибок: из слота в систему хозяина

Отчёт об отказе в XRT — «возвращаемое значение + потоковый слот ошибки» (глава 4) — отображение на стороне хозяина задаёт три вопроса: где перехватывать (проверять возвращаемое значение в каждой точке вызова XRT — макрос/обёрточная функция перехватывает единообразно); как переводить (xrtErrorKind → тип исключения/код ошибки хозяина; xrtErrorMessage → человекочитаемое сообщение; цепочка причин → разворачивание в журнал); как переносить (между потоками сперва TakeError — семантика переноса главы 4, исполненная в очереди задач хозяина). Изоляция слота: потоки хозяина держат каждый свой слот — изоляция, проверенная тестами _threads, и есть гарантия конкурентной безопасности хозяина.

Обрезка и размер: право выбора хозяина

Хозяин объявляет корневые модули (глава 131) — артефакт хозяина «только контейнеры + строки» не содержит ни одного сетевого символа (утверждение forbid_symbols). Безопасность эволюции: без выбора = Core — будущие новые модули XRT не увеличат незаметно артефакт хозяина (контракт прямо называет цель этого правила). Согласованность многих TU: все TU хозяина используют одинаковое множество модулей (при модульной линковке) — сборочная система определяет в одном месте (target_compile_definitions CMake объявляется однажды).

Наблюдаемость: stats всегда включён в продакшне

Роль memory_stats (представлена в главе 6) на стороне хозяина — источник данных панели здоровья: xrtMemStatsEnable(true) один раз, периодическое чтение Snapshot — malloc_calls (число бизнес-выделений)/pooled (попадания в пул)/direct (напрямую)/backing (число обращений к системе) — четыре чтения образуют минимальный набор метрик здоровья памяти. Почти нулевые накладные расходы — предпосылка постоянного включения (только счётчики, без записей — против высокозатратной полной записи memory_debug); обнаружение утечек: утечка видна как live не возвращается к нулю идёт в пороги тревог долго работающих сервисов.

Решение хозяина о трёх формах

Однозаголовочный (скрипты/прототипы — один include)/статическая библиотека (настоящие проекты — линковка есть заморозка)/динамическая (хозяева плагинов — независимое обновление) — выбор потребителя главы 132 в контексте хозяина повторяется как интеграционное решение: стоимость стыковки со сборочной системой хозяина и форма распространения решают; согласованность поведения (утверждение verify) делает переключение незаметным.

135.3示例

Первая полная программа: панель здоровья с всегда включёнными stats

Программа ниже из examples/memory/stats — образец наблюдения в продакшн-форме:

examples/memory/stats/main.c源码 ↗
























 */

#include <stdio.h>

#include <xrt.h>



int main(void)
{
	xmemstats tStats;
	ptr pSmall;
	ptr pLarge;

	/* 开关可查询:先确认当前状态,再打开并清零。 */
	printf("before=%d\n", xrtMemStatsEnabled() ? 1 : 0);
	xrtMemStatsEnable(true);
	printf("after=%d\n", xrtMemStatsEnabled() ? 1 : 0);
	xrtMemStatsReset();

	/* 一次小块(走池)+ 一次大块(直通)。 */
	pSmall = xrtMalloc(48);
	pLarge = xrtMalloc(4096);
	if ( (pSmall == NULL) || (pLarge == NULL) ) {
		xrtFree(pSmall);
		xrtFree(pLarge);
		return 1;
	}
	xrtFree(pSmall);
	xrtFree(pLarge);

	/* 读取窗口内计数(字段语义见文件头读数解释)。 */
	xrtMemStatsGet(&tStats);
	printf("malloc_calls=%llu pooled=%llu direct=%llu backing=%llu\n",
		(unsigned long long)tStats.MallocCalls,
		(unsigned long long)tStats.PooledAllocCalls,
		(unsigned long long)tStats.DirectAllocCalls,
		(unsigned long long)tStats.BackingAllocCalls);

	/* 用完关闭,回到零开销模式。 */
	xrtMemStatsEnable(false);
	return 0;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single -include xrt.h impl.c examples/memory/stats/main.c -lws2_32 -liphlpapibefore=0 after=1malloc_calls=2 pooled=1 direct=1 backing=3

Что только что произошло. (1) xrtMemStatsEnable(true) + Reset — опрос переключателя (before/after), затем обнуление: дисциплина наблюдения сперва убедиться, потом действовать. (2) После двух бизнес-выделений (малый блок 48Б + большой 4096Б) чтение снимка: pooled=1 (малый попал в пул — ноль системных вызовов), direct=1 (большой сверх порога прошёл напрямую), backing=3 (по-настоящему на стороне системы 3 — прямой 1 + внутренний прогрев пула 2) — сверка бизнес-взгляда с системным и есть сырьё планирования ёмкости. (3) Периодическое «снимок + чтение» панелью хозяина — всё мониторинг здоровья памяти библиотеки — постоянное включение стоит почти ничего (инкременты счётчиков).

Вторая полная программа: полная проверка захвата распределителя

Вторая программа из examples/core/allocator_tour — приёмка захвата памяти хозяином:

examples/core/allocator_tour/main.c源码 ↗



















 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <xrt.h>

/* 计数分配器:仍路由系统堆,便于验证替换生效。 */
typedef struct examplecount {
	size_t iAllocs;
	size_t iFrees;
} examplecount;

static ptr exampleAlloc(ptr pContext, size_t iSize)
{
	++((examplecount*)pContext)->iAllocs;
	return (ptr)malloc(iSize);
}

static ptr exampleRealloc(ptr pContext, ptr pMemory, size_t iSize)
{
	(void)pContext;
	return (ptr)realloc(pMemory, iSize);
}

static void exampleFree(ptr pContext, ptr pMemory)
{
	++((examplecount*)pContext)->iFrees;
	free(pMemory);
}

int main(void)
{
	xallocator Default;
	xallocator Custom;
	examplecount Count;
	uint8* pBlock;
	uint8* pDup;
	int iResult = 1;

	/* ---- 必须在任何 XRT 分配前完成替换。 ---- */
	xrtGetAllocator(&Default);
	memset(&Count, 0, sizeof(Count));
	Custom.Context = (ptr)&Count;
	Custom.Alloc = exampleAlloc;
	Custom.Realloc = exampleRealloc;
	Custom.Free = exampleFree;
	if ( !xrtSetAllocator(&Custom) ) {
		goto Cleanup;
	}

	/* ---- At 族五接口:位置参数不影响堆行为。 ---- */
	pBlock = (uint8*)xrtMallocAt(4u, __FILE__, __LINE__);
	if ( (pBlock == NULL) ||
		((pDup = (uint8*)xrtMemDupAt("hi", 2u, __FILE__,
			__LINE__)) == NULL) ||
		(memcmp(pDup, "hi", 2u) != 0) ||
		((pBlock = (uint8*)xrtReallocAt(pBlock, 16u, __FILE__,
			__LINE__)) == NULL) ||
		(xrtCallocAt(2u, 4u, __FILE__, __LINE__) ==
			NULL) ) {
		goto Cleanup;
	}
	/* Calloc 清零验证单独做——保留指针用于释放。 */
	{
		uint8* pZero = (uint8*)xrtCallocAt(2u, 4u, __FILE__,
			__LINE__);

		if ( (pZero == NULL) || (pZero[0] != 0u) ||
			(pZero[7] != 0u) ) {
			goto Cleanup;
		}
		xrtFreeAt(pZero, __FILE__, __LINE__);
	}
	xrtFreeAt(pDup, __FILE__, __LINE__);
	xrtFreeAt(pBlock, __FILE__, __LINE__);
	printf("allocator: swap -> at-family alloc/calloc/dup/realloc ok\n");

	
	 * 且 GetAllocator 仍报告已安装的计数分配器。 ---- */
	
	 * 分配才经过自定义 Alloc;计数只需 >= 1 即证明替换生效。 */
	if ( xrtSetAllocator(&Default) ||
		(Count.iAllocs < 1u) ) {
		goto Cleanup;
	}
	{
		xallocator Current;

		xrtGetAllocator(&Current);
		if ( (Current.Alloc != Custom.Alloc) ||
			(Current.Free != Custom.Free) ) {
			goto Cleanup;
		}
	}
	printf("allocator: locked after first alloc -> swap rejected ok\n");
	iResult = 0;

Cleanup:
	return iResult;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single -include xrt.h impl.c examples/core/allocator_tour/main.c -lws2_32 -liphlpapiallocator: swap -> at-family alloc/calloc/dup/realloc okallocator: locked after first alloc -> swap rejected ok

Что только что произошло. (1) После замены проверены все четыре входа at-семейства (Alloc/Calloc/Dup/Realloc) — полная грань, которую обязан реализовать распределитель хозяина (не только Alloc — семантику Realloc часто забывают). (2) Проверка блокировки второй строкой: второй SetAllocator отвергнут — тайминг-контракт интеграции хозяина (обязан предшествовать первому выделению) из ошибки превращён в проверенный факт. (3) Эти два образца вместе — «Hello World+» интеграции хозяина: захват (этот) + наблюдение (предыдущий) — суверенитет памяти и видимость прибывают одновременно.

Чек-лист хозяина: порядок приёмки интеграции

Сверните главу в чек-лист перед запуском — отмечайте пункты в порядке зависимостей: (1) распределитель захвачен в первой строке main (все четыре входа at-семейства реализованы, семантика блокировки известна); (2) обрезка модулей объявлена (список корневых модулей в сборочной системе, запрещённые символы профиля размера проверены); (3) слой отображения ошибок на месте (таблица перевода Kind, цепочка причин в журнале, путь TakeError между потоками); (4) наблюдение включено постоянно (stats enable + периодические снимки + пороги тревог по четырём чтениям); (5) форма диагностики определена (в проде применена деградация до EXCLUDE, отладочные сборки переключаемы); (6) форма артефакта выбрана (одиночный заголовок/статическая/динамическая — стыковка со сборочной системой хозяина проверена); (7) путь финала отрепетирован (все объекты библиотеки Drained, финальный снимок stats, слоты ошибки очищены). Все семь отмечены — XRT вливается в хозяина как «деталь»: память видна, ошибки переводимы, размер управляем, поведение проверяемо — полное состояние встраивания не «компилируется», а «суверенитет ясен».

135.4契约

  • Тайминг захвата: SetAllocator до любого выделения XRT (уровень первой строки main); блокировка после первого выделения; все четыре входа at-семейства реализованы.
  • Суверенитет памяти: после захвата все выделения библиотеки (включая внутренности пулов) идут хозяину — бюджет/статистика/arena принадлежат хозяину.
  • Отображение ошибок: проверка возвращаемого значения в точках вызова → перевод Kind → показ Message → цепочка причин в журнал; между потоками сперва TakeError.
  • Изоляция слота: потоки хозяина держат каждый свой слот ошибки — конкурентная безопасность закреплена тестами _threads.
  • Суверенитет обрезки: хозяин объявляет корневые модули; без выбора = Core без роста; множества многих TU согласованы (одно определение в сборочной системе).
  • Наблюдение всегда: stats только считает, почти нулевая стоимость; четыре чтения (calls/pooled/direct/backing) — минимальный набор здоровья.
  • Согласованность форм: поведение однозаголовочного/статической/динамической утверждается verify — переключение хозяина бесплатно.
  • Диагностика съёмная: семантика исключения MEMORY_DEBUG (глава 131) — полная продакшн-функциональность минус диагностика.

Типичные ошибки

Ловушка 1: смена распределителя после инициализации библиотеки:Ловушка 1: смена распределителя после инициализации библиотеки

Симптом: SetAllocator возвращает отказ — внутренние структуры библиотеки (пулы/первые кэши) уже построены распределителем по умолчанию.

Причина: контракт блокировки. Первый акт main хозяина — смена; любой порядок «сперва что-то инициализировать» может пересечь черту.

错误写法
int main(void) {
	init_something_using_xrt();   /* внутри уже выделялось */
	xrtSetAllocator(&MyAlloc);    /* отвергнуто: первое выделение произошло */
}
正确写法
int main(void) {
	xrtSetAllocator(&MyAlloc);    /* первый акт */
	init_something_using_xrt();   /* всё через распределитель хозяина */
}
Ловушка 2: обёртки хозяина глотают детали ошибки:Ловушка 2: обёртки хозяина глотают детали ошибки

Симптом: сторона хозяина видит только «отказ» — категория/домен/цепочка причин потеряны — стратегия повторов может лишь гадать.

Причина: ленивый слой отображения (передаёт только bool). Kind — машинно-читаемое основание решений (глава 4) — TIMEOUT повторить, AGAIN подождать, ARGUMENT завести баг — проглотить его значит разжаловать структурированную ошибку в булево.

错误写法
bool my_json_parse(str s, xvalue** out) {
	if ( !xrtJsonParse(...) ) { return false; }   /* все детали потеряны */
}
正确写法
bool my_json_parse(str s, xvalue** out, my_error* e) {
	if ( !xrtJsonParse(...) ) {
		fill_host_error(e, xrtTakeError());  /* Kind/домен/цепочка переведены полностью */
		return false;
	}
}
Ловушка 3: stats и debug перепутаны (включены оба):Ловушка 3: stats и debug перепутаны (включены оба)

Симптом: продакшн работает с включённым memory_debug (каждое выделение записывается подробно) — производительность падает, память удваивается.

Причина: роли разные: stats считает (всегда), debug пишет детали (кратко при разборе). Продакшн-умолчание комбинации: stats включён + debug исключён (XRT_EXCLUDE_MEMORY_DEBUG — комбинированная семантика главы 131).

错误写法
#define XRT_MODULE_ALL   /* с memory_debug - полная запись деталей в проде */
正确写法
#define XRT_MODULE_ALL
#define XRT_EXCLUDE_MEMORY_DEBUG   /* прод: stats остаётся, детали убираются */
/* отладочная сборка добавит XRT_MODULE_MEMORY_DEBUG явно */

Практические задания

Базовое: интеграция с arena на запрос

Arena хозяина (bump-выделение + освобождение всего блока по концу запроса) как распределитель XRT: внутри одного запроса контейнерно-строковый бизнес. Критерии приёмки: ноль системных выделений внутри библиотеки (backing=0 или только большой блок arena); одно освобождение по концу запроса; stats сходится.

Продвинутое: слой отображения ошибок

Написать функции отображения хозяина (Kind → коды ошибок хозяина, Message + цепочка причин → структуры журнала) + обернуть три частых вызова. Критерии приёмки: шесть значений Kind каждое отображается верно; межпоточная задача несёт ошибку (путь TakeError) без потерь.

Вызов: многотенантное встраивание

Два «тенанта» (независимые бизнес-модули) в одном процессе, каждый со своей обрезкой, деля один экземпляр XRT: единый распределитель (бюджеты по тенантам — контекст различает), ошибки изолированы по потокам, stats читается по тенантам. Критерии приёмки: OOM тенанта A не влияет на B; ошибки не пересекают слоты; объём есть объединение замыканий обоих тенантов.

Итоги

ТемаКратко
Высшая цельбиблиотека — деталь хозяина — суверенитет памяти/ошибок/размера у хозяина
Тайминг захватаSetAllocator до любого выделения XRT; все четыре входа at-семейства
Блокировкапосле первого выделения не меняется — стабильность интеграции прежде
Отображение ошибокточки возвращаемого значения → перевод Kind → цепочка в журнал; TakeError между потоками
Изоляция слотапотоки держат каждый свой — гарантия конкурентности хозяина, закреплённая _threads
Суверенитет обрезкиобъявить корневые модули; без выбора = Core; эволюция не растит незаметно
stats всегдапочти нулевая стоимость, четыре чтения: calls/pooled/direct/backing
Диагностика съёмнаяALL+EXCLUDE_MEMORY_DEBUG — полная прод-функциональность минус детали
Решение о формеоднозаголовочный/статическая/динамическая — поведение согласовано, переключение бесплатно

📖 Справочники API в этой главе:core (25 записей)memory_stats (5 записей)error (34 записей)

Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки