основы управления ресурсами в Apache: APR-пулы (pools)

APR пулы (pools) - это основные строительные блоки в APR (Apache Portable Runtime, подробности на apr.apache.org) и Apache, а также основа для всего управления ресурсами. Они служат для выделения памяти, либо напрямую (на malloc-подобный манер), либо косвенно (как, например, в операциях со строками), и гарантируют полное высвобождение памяти после использования. Также они гарантируют, что для таких ресурсов, как, например, файлы или мьютексы, может быть выделена память, которая также будет всегда полностью освобождена при завершении работы с ресурсом. Также пулы APR могут работать с ресурсами, управляемыми сторонними библиотеками.

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

проблемы управления ресурсами

Основная проблема управления ресурсами, конечно, известна всем программистам. Когда вы выделяете память под ресурс, вы должны гарантированно ее высвободить по завершению работы. Например:
char* buf = malloc(n);
... выделяем память для buf...
... делаем что-либо с buf ...
free(buf);

или
FILE* f = fopen(path, "r") ;
... выделяем память под f...
... читаем из f ....
fclose(f) ;

Несомненно, некорректное высвобождение buf или закрытие f - это ошибка, и в контексте такой длительной программы, как Apache, это может привести к серьезным последствиям вплоть до полного краха системы. Таким образом, мы не должны допустить эти ошибки!

В обычной ситуации, типа вышеприведенной, все очень просто. Но в большинстве более сложных ситуаций с многочисленными ветвлениями и даже при четких областях видимости ресурса, будет точно не известно время его завершения, что приведет к серьезным проблемам при очистке занимаемого места в каждом возможном исходе программы. Таким образом, нам необходим более удобный механизм в управлении ресурсами.

модель конструктора/деструктора

Одним из методов управления ресурсами может служить концепция объекта С++, имеющего конструктор и деструктор. Метод делает деструктор ответственным за очистку всех ресурсов, полученных объектом. Этот подход хорошо работает, предоставляя все динамические ресурсы и возлагая ответственность за их освобождение на объект. Но, как и с простым С-подходом, это требует тщательного внимания к деталям, например там, где ресурс условно получен или разделен между многими разными объектами, есть возможность ошибок.

модель уборки мусора

Более высокоуровневый метод управления ресурсами, типичный для Lisp и Java - это уборка мусора. Данный метод имеет преимущество в том, что решение проблемы перекладывается с программиста на язык, на котором он пишет, таким образом, опасность ошибок программиста просто исчезает. Недостатком такого подхода является дополнительный расход памяти там, где в этом нет необходимости, также это понижает степень контроля программиста над памятью, исчезает возможность контроля времени жизни ресурса. Также данная модель требует, чтобы все компоненты программы - включая сторонние библиотеки - были созданы в тех же системах, что практически невозможно в открытых системах, написанных на С.

APR-пулы

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

Основа концепции состоит в том, что когда вы получите ресурс, вы регистрируете его в пуле. Затем пул становится ответственным за очистку, которая произойдет тогда, когда пул очистится сам. Это означает, что вся проблема уменьшается до разового получения и очищения единственного ресурса: самого пула. А так как пулы Apache управляются самим сервером, то эта сложность удаляется из области прикладного программирования. Программистам же остается только выбирать пул, подходящий под время жизни ресурса.

основы управления памятью

Основное использование пулов происходит при управлении памятью. Вместо
mytype* myvar = malloc(sizeof(mytype)) ;

/* Необходимо убедиться, что myvar вернет память в каждом пути исполнения */
мы используем
mytype* myvar = apr_palloc(pool, sizeof(mytype)) ;

и пул автоматически начинает отвечать за его освобождение, невзирая на то, что может случиться за то время.
Также в APR и Apache память может выделяется внутри других функции. Например, в функциях обработки строк и ведения логов мы можем использовать конструкцию, подобную sprintf без знания размера строки:
char* result = apr_psprintf(pool, fmt, ...) ;

APR также предоставляет высокий уровень абстракции пула памяти, например, в блоках (bucket), используемых для передача данных вниз по цепочке фильтров.

общее управление памятью

APR предоставляет собственные функции для управления памятью и некоторыми другими основными ресурсами, такими, как файлы, сокеты и мьютексы. Но особой потребности в их использовании нет. Альтернативой этому подходу является использование родных функций для выделения памяти и последующей явной регистрации ресурса в пуле:
mytype* myvar = malloc(sizeof(mytype)) ;
apr_pool_cleanup_register(pool, myvar, free, apr_pool_cleanup_null) ;

или
FILE* f = fopen(filename, "r") ;
apr_pool_cleanup_register(pool, f, fclose, apr_pool_cleanup_null) ;

что будет передавать ответственность за освобождение памяти пулу. Но использование родных функции может быть менее переносимым, чем использование функций APR.
Данный метод обобщается на ресурсы Apache и APR. Вот, например, подключение к базе данных и гарантированное отключение от нее после использования:
MYSQL* sql = NULL ;
sql = mysql_init(sql) ;
if ( sql == NULL ) { возврат; }
apr_pool_cleanup_register(pool, sql, mysql_close, apr_pool_cleanup_null) ;
sql= mysql_real_connect(sql, host, user, pass, dbname, port, sock, 0);
if ( sql == NULL ) { возврат; }

Заметьте, что APR предлагает в целом лучший метод управления подключением к базе данных.
В качестве второго примера рассмотрим XML-обработку:
xmlDocPtr doc = xmlReadFile(filename);
apr_pool_cleanup_register(pool, doc, xmlFreeDoc, apr_pool_cleanup_null) ;

/* теперь начинаем работать с doc, что может потребовать выделения дополнительной памяти для XML-библиотеки, но она все равно будет очищена */. Другой пример. Код очистки, интегрированный в С-деструктор: Предположим, у нас есть:
class myclass {
public:
virtual ~myclass() { очистка ; }
// ....
} ;

Мы определяем С-обертку:
void myclassCleanup(void* ptr) { delete (myclass*)ptr ; }

и регистрируем ее с пулом, при выделении памяти под myclass:
myclass* myobj = new myclass(...) ;
apr_pool_cleanup_register(pool, (void*)myobj, myclassCleanup, apr_pool_cleanup_null) ;

Теперь у нас есть возможность управления ресурсами С++ в Apache и больше нет нужды удалять myobj.

неявная и явная очистка

Теперь, допустим, мы хотим освободить наши явно заданные ресурсы до завершения запроса - например, потому что мы делаем что-либо расходующее много памяти, но при этом имеем объекты, которые можем освободить. Мы можем делать все в соответствии с правилами области видимости и использовать только очистку с помощью пулов, избегая части ошибок. C того момента, как мы зарегистрировали очистку, она запускается, отвечая за высвобождения неиспользуемой памяти.

Функция пула apr_pool_cleanup_kill обеспечивает решение этой задачи. Для явного освобождения памяти мы должны открепить очистку от пула, что и делается функцией apr_pool_cleanup_kill. Или мы можем поступить элегантнее. Здесь представлен С++ класс, управляемый на основе пулов:
class poolclass {
private:
apr_pool_t* pool ;
public:
poolclass(apr_pool_t* p) : pool(p) {
apr_pool_cleanup_register(pool, (void*)this,
myclassCleanup, apr_pool_cleanup_null) ;
}
virtual ~poolclass() {
apr_pool_cleanup_kill(pool, (void*)this, myclassCleanup) ;
}
} ;

Если вы используете С++ в Apache (или APR), вы можете наследовать любые классы из класса poolclass. Большинство APR-функций делают что-то подобное этому, используя регистрацию и открепление всякий раз при выделении или освобождении ресурсов.

время жизни ресурса

Когда мы выделяем ресурс в пуле, мы гарантируем их очистку в некоторый момент времени. Но когда? Мы должны удостовериться, что очистка произойдет в нужное время.
К счастью, Apache упрощает эту задачу для нас, предоставляя различные пулы для различных типов ресурсов. Эти пулы связаны со структурами сервера и имеют время жизни соответственно этим структурам. Вот четыре основных пула, всегда доступных в Apache:
- пул запроса, связан с временем жизни НТТР запроса;
- пул процесса, связан с временем жизни процесса сервера;
- пул соединения, связан с временем жизни ТСР-соединения;
- пул конфигурирования.

Первые три пула ассоциируются с соответствующими структурами Apache и доступны как request->pool, connection->pool и process->pool
соответственно. Четвертый, process->pconf, также ассоциируется с процессом, но отличается от пула процесса, в частности тем, что он очищается всякий раз, когда Apache перечитывает свою конфигурацию.
Пул процесса подходит под долгоживущий ресурс, например тот, который инициализируется при запуске сервера, или для ресурса, используемого для обработки составных запросов. Пул запроса подходит для временного ресурса и используется для обработки одного запроса.

Третий основной пул - это пул соединения, который имеет время жизни соединения, созданного одним или несколькими запросами. Это полезно для временного ресурса, который не может быть ассоциирован с запросом: особенно в фильтрах уровня соединения, где структура request_rec (структура запроса) не определена.

В дополнение к этому, создается специальный пул для других целей, например для конфигурирования и ведения логов.

использование пулов в Apache: обработка запроса

Вся обработка запросов имеет форму:
int my_func(request_rec* r) {
/* реализация обработки запроса */
}

Эта форма дает пул запроса r->pool в ваше распоряжение. Как обсуждалось выше, пул запроса подходит для большинства действий, возникающих при обработке. Именно его вы передаете Apache и APR-функциям, которые нуждаются в пуле.

Пул процесса доступен как r->server->process->pool для операций, которые нуждаются в выделенном долгоживущем ресурсе; например, кэшированный ресурс может быть вычислен один раз и впоследствии может использоваться неоднократно в других запросах. Пул соединения доступен как r- >connection->pool.

использование пулов в Apache: инициализация и конфигурирование

Внутренняя организация инициализации Apache является очень сложной. Но чем более связанными становятся модули , тем легче становится инициализация: вам остается только установить ваши настройки. Apache все это упрощает: большинство хуков (hooks) имеют прототипы, которые передают пул как первый аргумент.

Конфигурация обработчиков:
static const char* my_cfg(cmd_parms* cmd, void* cfg, /* args */ )

Использование пула конфигурации cmd->pool дает вам конфигурацию с временем жизни директивы.

Pre-и Post-конфигурация. Эти хуки передают несколько пулов, что не совсем обычно:
static int my_pre_config(apr_pool_t* pool, apr_pool_t* plog,apr_pool_t* ptemp).

Для большинства задач используется первый пул, но если ваши функции используют пулы для временного ресурса внутри себя, то используйте ptemp.

использование пулов в Apache: другие случаи

Большинство модулей Apache используют инициализацию и обрабатывают запросы - мы это уже обсудили. Но есть два других случая:

Функции соединения.Хуки Pre_connection и process_connection уровня соединения передают conn_rec как первый аргумент по аналогии с функциями запроса. Хук Create_connection уровня соединения-инициализации передает пул как первый аргумент: любые модули могут использовать его для установки соединения.
Функции фильтра.Функции фильтра получают ap_filter_t как первый аргумент. Эта структура вмещает оба пула: и request_rec, и conn_rec, невзирая на тип фильтр. Фильтрам уровня запроса (который описывается как AP_FTYPE_RESOURCE или AP_FTYPE_CONTENT) оптимально использовать пул запроса. Фильтры уровня соединения получают неверный указатель в f->r и должны использовать пул соединения.



Ник Кью (Nick Kew), перевод ApacheDev.ru.¶


Сетевые решения. Статья была опубликована в номере 04 за 2005 год в рубрике программирование

©1999-2024 Сетевые решения