描述
当以共享缓存模式运行时,如果无法获取共享缓存中的所需锁或共享缓存中的单独表,则数据库操作可能会失败并发生SQLITE_LOCKED错误。该API可以用于注册当连接当前持有所需的锁放弃它时SQLite将调用的回调。仅当使用定义了SQLITE_ENABLE_UNLOCK_NOTIFY C预处理器符号编译库时,此API才可用。
C / C ++语法
int sqlite3_unlock_notify( sqlite3 *pBlocked, void (*xNotify)(void **apArg, int nArg), void *pNotifyArg ); |
PB语法
FUNCTION sqlite3_unlock_notify ( _ BYVAL pBlocked AS DWORD, _ BYVAL xNotify AS DWORD, _ BYVAL pNotifyArg AS DWORD _ ) AS LONG |
参数
pBlocked
[in]数据库连接句柄。必须是从sqlite3_open,sqlite3_open16或sqlite3_open_v2获取的sqlite3对象指针。 数据库连接不能关闭。
xNotify
[in]指向回调函数的指针。
pNotifyArg
[in]将第二个参数传递给回调函数的数据。
返回值
除非检测到死锁(见下文),sqlite3_unlock_notify总是返回SQLITE_OK。
备注
当数据库连接结束其当前事务时,共享缓存锁将通过提交或回滚来释放。
当连接(称为阻塞连接)无法获取共享高速缓存锁并且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,那么新的回调代替旧的。如果使用NULL指针调用sqlite3_unlock_notify作为其第二个参数,那么任何现有的unlock-notify回调都将被取消。阻塞的连接unlock-notify回调也可以使用sqlite3_close关闭阻止的连接来取消。
unlock-notify回调不可重入。如果应用程序在unlock-notify回调中调用任何sqlite3_xxxAPI函数,则可能会导致崩溃或死锁。
回调调用细节
当一个unlock-notify回调被注册时,该应用程序提供一个单独的void *指针,它被调用时传递给回调。但是,回调函数的签名允许SQLite通过一个void *上下文指针的数组。传递给unlock-notify回调的第一个参数是指向void *指针数组的指针,第二个参数是数组中的条目数。
当阻塞连接事务结束时,可能有多个已经注册为unlock-notify回调的阻塞连接。如果两个或更多个这样的阻塞连接已经指定了相同的回调函数,则不是多次调用回调函数,而是将被阻塞的连接绑定在一起的一组void *上下文指针一起调用到一个数组中。这使应用程序有机会优先考虑与一组未阻止的数据库连接相关的任何操作。
死锁检测
假设在注册解锁通知回调之后,数据库在采取任何进一步的操作(合理的假设)之前等待发出回调,那么使用此API可能导致应用程序死锁。例如,如果连接X正在等待连接Y的事务结束,并且类似的连接Y正在等待连接X的事务,则两个连接都不会继续进行,并且系统可能无限期地保持死锁。
为了避免这种情况,sqlite3_unlock_notify执行死锁检测。如果对sqlite3_unlock_notify的给定调用将使系统处于死锁状态,则返回SQLITE_LOCKED,并且未注册unlock-notify回调。如果连接A在连接B的事务的结束时已经注册了解锁通知回调,则系统被认为处于死锁状态,并且当连接A的事务结束时,连接B本身已经注册了解锁通知回调。也会检测到间接死锁,因此如果连接C在连接C的连接C等待连接A的连接C的事务的连接上连接B注册了解锁通知回调,则系统也被认为是死锁的。允许任何数量的间接级别。
“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。
C ++实现代码
/*
** Register an unlock-notify callback.
**
** This is called after connection "db" has attempted some operation
** but has received an SQLITE_LOCKED error because another connection
** (call it pOther) in the same process was busy using the same shared
** cache. pOther is found by looking at db->pBlockingConnection.
**
** If there is no blocking connection, the callback is invoked immediately,
** before this function returns.
**
** If pOther is already blocked on db, then report SQLITE_LOCKED, to indicate
** a deadlock.
**
** Otherwise, make arrangements to invoke xNotify when pOther drops
** its locks.
**
** Each call to this function overrides any prior callbacks registered
** on the same "db". If xNotify==0 then any prior callbacks are immediately
** cancelled.
*/
SQLITE_API int sqlite3_unlock_notify(
sqlite3 *db,
void (*xNotify)(void **, int),
void *pArg
){
int rc = SQLITE_OK;
sqlite3_mutex_enter(db->mutex);
enterMutex();
if( xNotify==0 ){
removeFromBlockedList(db);
db->pBlockingConnection = 0;
db->pUnlockConnection = 0;
db->xUnlockNotify = 0;
db->pUnlockArg = 0;
}else if( 0==db->pBlockingConnection ){
/* The blocking transaction has been concluded. Or there never was a
** blocking transaction. In either case, invoke the notify callback
** immediately.
*/
xNotify(&pArg, 1);
}else{
sqlite3 *p;
for(p=db->pBlockingConnection; p && p!=db; p=p->pUnlockConnection){}
if( p ){
rc = SQLITE_LOCKED; /* Deadlock detected. */
}else{
db->pUnlockConnection = db->pBlockingConnection;
db->xUnlockNotify = xNotify;
db->pUnlockArg = pArg;
removeFromBlockedList(db);
addToBlockedList(db);
}
}
leaveMutex();
assert( !db->mallocFailed );
sqlite3Error(db, rc, (rc?"database is deadlocked":0));
sqlite3_mutex_leave(db->mutex);
return rc;
}