sqlite.h


Notificación de desbloqueo

int sqlite3_unlock_notify(
  sqlite3 *pBlocked,                          /* Conexión a la espera */
  void (*xNotify)(void **apArg, int nArg),    /* Retrollamada a invocar */
  void *pNotifyArg                            /* Argumento para pasar a xNotify */
);

Cuando se ejecuta en modo de caché compartida, una operación de base de datos puede fallar con un error SQLITE_LOCKED si los bloqueos requeridos en la caché compartida o en tablas individuales dentro de la caché compartida no pueden ser obtenidos. Ver Modo de caché compartida SQLite para una descripción de bloqueos de caché compartida. Esta función puede ser usada para registrar una retrollamada que SQLite invocará cuando la conexión que actualmente mantiene el bloqueo necesario renuncie a él. Esta función sólo está disponible si la biblioteca fue compilada con el símbolo del preprocesador C SQLITE_ENABLE_UNLOCK_NOTIFY definido.

Los bloqueos de caché compartida son liberados cuando una conexión de base de datos termina su transacción actual, ya sea por que se complete o porque se deshaga.

Cuando una conexión (conocida como la conexión bloqueada) falla al obtener un bloquei de caché compartida se retorna SQLITE_LOCKED, la identidad de la conexión de base de datos (la conexión bloqueante) que ha bloqueado el recurso requerido se almacena internamente. Después de que la aplicación recibe un error SQLITE_LOCKED, puede llamar a sqlite3_unlock_notify() con el manipulador de conexión bloqueada como primer argumento para registrar una retrollamada que será invocada cuando la transacción que actualmente bloquea la conexión concluya. La retrollamada es invocada desde el interior de la llamada sqlite3_step o sqlite3_close que termine la transacción que bloquea la conexión.

Si sqlite3_unlock_notify() es invocada en una aplicación multihilo, existe la posibilidad de que la conexión bloqueante haya concluido su transacción durante el tiempo hasta que se invoca a sqlite3_unlock_notify(). Si eso ocurre, la retrollamada especificada es invocada inmediatamente, desde el interior de la llamada a sqlite3_unlock_notify().

Si la conexión bloqueada está intentando obtener un bloqueo de escritura en una tabla de caché compartida, y más de una conexión diferente tiene actualmente un bloqueo de lectura en la misma tabla, SQLite selecciona una de las otras conexiones arbitrariamente para usar como conexión bloqueante.

Puede haber más de una retrollamada de notificación de desbloqueo registrada para una conexión bloquedada. Si sqlite3_unlock_notify() es invocada cuando la conexión bloqueada todavía tiene una retrollamada de notificación de desbloqueo registrada, la nueva retrollamada reemplaza a la vieja. Si sqlite3_unlock_notify() es invocada con un puntero NULL en su segundo argumento, la retrollamada de notificación existente se cancela. La retrollamada también puede ser cancelada si la conexión bloqueada se cierra usando sqlite3_close().

La retrollamada de notificación de desbloqueo no es reentrante. Si una aplicación invoca a cualquier función del API sqlite3_xxx en el interior de una retrollamada, el resultado puede ser un crash o un punto muerto.

A menos que se detecte un punto muerto (ver abajo), sqlite3_unlock_notify() siempre retorna SQLITE_OK.

Detalles de invocación de retrollamada

Cuando se registra una retrollamada de notificación de desbloqueo, la aplicación proporciona un puntero sencillo *void que es pasado a la retrollamada cuando es invocada. Sin embargo, el prototipo de la función de retrollamada permite a SQLite pasar un array de punteros void*. El primer argumento pasado a la retrollamada es un puntero a un array de punteros void*, y el segundo es el número de entradas en el array.

Ciando una transacción que bloquea una conexión termina, puede haber más de una conexión bloqueada que haya registrado una retrollamada de notificación. Si dos o más de esas conexiones bloqueadas han especificado la misma función de retrollamada, en lugar de invocarla varias veces, es invocada una vez con el conjunto de punteros void* que especifica las conexiones bloquedas agrupadas en una matriz. Esto le da a la aplicación una oportunidad de priorizar cualquier acción relacionada con el conjunto de conexiones de base de datos bloqueadas.

Detección de punto muerto (deadlock)

Asumiendo que después de registrar una retrollamada de notificación de desbloqueo una base de datos espera a que se emita la retrollamada antes de tomar cualquier acción (una suposición razonable), el uso de esta función puede provocar que la aplicación entre en un punto muerto. Por ejemplo, si la conexión X está esperando a que la transacción en la conexión Y termine, y al mismo tiempo la conexión Y está esperando a que la transacción en la conexión B termine, ninguna de las conexiones se producirá y el sistema permanece en un punto muerto indefinidamente.

Para evitar este escenario, sqlite3_unlock_notify() realiza una detección de punto muerto. Si una conexión dada llama a sqlite3_unlock_notify() puede poner el sistema en un estado de punto muerto, se retorna SQLITE_LOCKED y no se registra la retrollamada. Se dice que el sistema está en un estado de punto muerto si la conexión A ha registrado una retrollamada para la conclusión de la transacción de la conexión B, y la conexión B ha registrado una retrollamada para la conclusión de la transacción de la conexión A. Los puntos muertos indirectos también se detectan, por lo que el sustema también se considera en punto muerto si la conexión B ha registrado una retrollamada para la conclusión de la transacción de la conexión C, donde la conexión C está esperando a la conexión A. Está permitido cualquier número de niveles de indirección.

La excepción "DROP TABLE"

Cuando una llamada a sqlite3_step() devuelve SQLITE_LOCKED, casi siembre es apropiado invocar a sqlite3_unlock_notify(). Hay, sin embargo, una excepción. Cuando se ejecuta una sentencia DROP TABLE o DROP INDEX, SQLite verifica si hay ejecuciones concurrentes de sentencias SELECT que pertenecen a la misma conexión. Si las hay, se devuelve SQLITE_LOCKED. En este caso no hay una "conexión bloqueante", de modo que invocar a sqlite3_unlock_notify() hace que la retrollamada sea invocada inmediatamente. Si la aplicación intenta de nuevo la consulta DROP TABLE o DROP INDEX, se puede producir un buble infinito.

Una forma de evitar este problema es verificar el código de error extendido devuelto por la llamada a sqlite3_step(). Si hay una conexión bloqueante, el código de error extendido es SQLITE_LOCKED_SHAREDCACHE. En caso contrario, en el caso especial de "DROP TABLE/INDEX", el código de error extendido es SQLITE_LOCKED.