Разблокировка уведомлений
int sqlite3_unlock_notify( sqlite3 *pBlocked, /* Waiting connection */ void (*xNotify)(void **apArg, int nArg), /* Callback function to invoke */ void *pNotifyArg /* Argument to pass to xNotify */ );
При работе в режиме общей кэш-памяти операция с базой данных может завершиться ошибкой SQLITE_LOCKED, если не удаётся получить необходимые блокировки на общей кэш-памяти или отдельных таблицах внутри общей кэш-памяти. Смотрите Режим общей кэш-памяти SQLite для описания блокировки общей кэш-памяти. Данный API может использоваться для регистрации обратного вызова, который SQLite вызовет, когда соединение, в настоящее время удерживающее необходимую блокировку, освободит её. Этот API доступен только в том случае, если библиотека была скомпилирована с определённым препроцессором C SQLITE_ENABLE_UNLOCK_NOTIFY.
См. также: Использование функции уведомления о разблокировке SQLite.
Блокировки общей кэш-памяти освобождаются, когда соединение с базой данных завершает текущую транзакцию, либо путём её подтверждения, либо путём её отката.
Когда соединение (известное как заблокированное соединение) не может получить блокировку общей кэш-памяти и вызывающему лицу возвращается SQLITE_LOCKED, идентификатор соединения базы данных (блокирующее соединение), которое заблокировало необходимый ресурс, сохраняется внутри. После того, как приложение получит ошибку SQLITE_LOCKED, оно может вызвать метод sqlite3_unlock_notify() с дескриптором заблокированного соединения в качестве первого аргумента, чтобы зарегистрировать обратный вызов, который будет вызван, когда текущая транзакция блокирующего соединения будет завершена. Обратный вызов вызывается из sqlite3_step или sqlite3_close вызова, который завершает транзакцию блокирующего соединения.
Если sqlite3_unlock_notify() вызывается в многопоточном приложении, существует вероятность того, что блокирующее соединение уже завершит свою транзакцию к моменту вызова sqlite3_unlock_notify(). Если это произойдёт, то указанный обратный вызов вызывается немедленно, изнутри вызова sqlite3_unlock_notify().
Если заблокированное соединение пытается получить запись-блокировку в таблице общей кэш-памяти, и более одного другого соединения в настоящее время удерживают чтение-блокировку в той же таблице, то SQLite произвольно выбирает одно из других соединений в качестве блокирующего соединения.
Может быть зарегистрирован только один обратный вызов unlock-notify для одного заблокированного соединения. Если sqlite3_unlock_notify() вызывается, когда у заблокированного соединения уже есть зарегистрированный обратный вызов unlock-notify, то новый обратный вызов заменяет старый. Если sqlite3_unlock_notify() вызывается с указателем NULL в качестве второго аргумента, то любой существующий обратный вызов unlock-notify отменяется. Обратный вызов unlock-notify заблокированного соединения также может быть отменён путём закрытия заблокированного соединения с помощью sqlite3_close().
Обратный вызов unlock-notify не является рекурсивным. Если приложение вызывает какие-либо функции API sqlite3_xxx из внутри обратного вызова unlock-notify, в результате может произойти сбой или тупиковая ситуация.
Если не обнаруживается тупиковая ситуация (см. ниже), sqlite3_unlock_notify() всегда возвращает SQLITE_OK.
Подробности вызова обратного вызова
Когда регистрируется обратный вызов unlock-notify, приложение предоставляет единственный указатель void*, который передаётся обратному вызову при его вызове. Однако, сигнатура функции обратного вызова позволяет SQLite передать массив указателей void* контекста. Первый аргумент, передаваемый обратному вызову unlock-notify, является указателем на массив указателей void*, а второй - количество элементов в массиве.
Когда транзакция блокирующего соединения завершается, может быть более одного заблокированного соединения, которое зарегистрировалось для обратного вызова unlock-notify. Если два или более таких заблокированных соединения указали на ту же функцию обратного вызова, то вместо многократного вызова функции обратного вызова, она вызывается один раз со множеством указателей void* контекста, указанных заблокированными соединениями, объединёнными в массив. Это даёт приложению возможность приоритизировать любые действия, связанные с набором разблокированных соединений базы данных.
Обнаружение тупиковой ситуации
Предполагая, что после регистрации на обратный вызов unlock-notify база данных ожидает, пока обратный вызов не будет выпущен, прежде чем предпринимать какие-либо дальнейшие действия (разумное предположение), то использование данного API может привести к тупиковой ситуации в приложении. Например, если соединение X ожидает завершения транзакции соединения Y, и аналогично соединение Y ожидает завершения транзакции соединения X, то ни одно соединение не продолжит работу, и система может оставаться в тупике бесконечно.
Чтобы избежать такой ситуации, sqlite3_unlock_notify() выполняет проверку на тупиковую ситуацию. Если данный вызов sqlite3_unlock_notify() поместит систему в тупиковую ситуацию, то возвращается SQLITE_LOCKED, и обратный вызов unlock-notify не регистрируется. Система считается находящейся в тупиковой ситуации, если соединение A зарегистрировалось на обратный вызов unlock-notify при завершении транзакции соединения B, и соединение B само зарегистрировалось на обратный вызов unlock-notify при завершении транзакции соединения A. Обнаружению также поддаются косвенные тупиковые ситуации, поэтому система также считается находящейся в тупиковой ситуации, если соединение B зарегистрировалось на обратный вызов unlock-notify при завершении транзакции соединения C, где соединение C ожидает соединения A. Разрешается любое количество уровней косвенности.
Исключение "DROP TABLE"
Когда вызов sqlite3_step() возвращает SQLITE_LOCKED, почти всегда уместно вызвать sqlite3_unlock_notify(). Однако есть одно исключение. При выполнении операторов "DROP TABLE" или "DROP INDEX", SQLite проверяет, есть ли какие-либо выполняющиеся операторы SELECT, относящиеся к тому же соединению. Если они есть, возвращается SQLITE_LOCKED. В этом случае нет "блокирующего соединения", поэтому вызов sqlite3_unlock_notify() приводит к немедленному вызову обратного вызова unlock-notify. Если приложение затем повторно пытается выполнить оператор "DROP TABLE" или "DROP INDEX", в результате может возникнуть бесконечный цикл.
Один из способов решения этой проблемы - проверка расширенного кода ошибки, возвращаемого вызовом sqlite3_step(). Если существует блокирующее соединение, то расширенный код ошибки устанавливается в SQLITE_LOCKED_SHAREDCACHE. В противном случае, в особом случае "DROP TABLE/INDEX", расширенный код ошибки просто равен SQLITE_LOCKED.
См. также списки Объектов, Констант и Функций.
SQLite is in the Public Domain.
https://sqlite.org/c3ref/unlock_notify.html