sqlcipher/doc/wal-lock.md

3.6 KiB

Wal-Mode Blocking Locks

On some Unix-like systems, SQLite may be configured to use POSIX blocking locks by:

  • building the library with SQLITE_ENABLE_SETLK_TIMEOUT defined, and
  • configuring a timeout in ms using the sqlite3_busy_timeout() API.

Blocking locks may be advantageous as (a) waiting database clients do not need to continuously poll the database lock, and (b) using blocking locks facilitates transfer of OS priority between processes when a high priority process is blocked by a lower priority one.

Only read/write clients use blocking locks. Clients that have read-only access to the *-shm file nevery use blocking locks.

Threads or processes that access a single database at a time never deadlock as a result of blocking database locks. But it is of course possible for threads that lock multiple databases simultaneously to do so. In most cases the OS will detect the deadlock and return an error.

Wal Recovery

Wal database "recovery" is a process required when the number of connected database clients changes from zero to one. In this case, a client is considered to connect to the database when it first reads data from it. Before recovery commences, an exclusive WRITER lock is taken.

Without blocking locks, if two clients attempt recovery simultaneously, one fails to obtain the WRITER lock and either invokes the busy-handler callback or returns SQLITE_BUSY to the user. With blocking locks configured, the second client blocks on the WRITER lock.

Database Readers

Usually, read-only are not blocked by any other database clients, so they have no need of blocking locks.

If a read-only transaction is being opened on a snapshot, the CHECKPOINTER lock is required briefly as part of opening the transaction (to check that a checkpointer is not currently overwriting the snapshot being opened). A blocking lock is used to obtain the CHECKPOINTER lock in this case. A snapshot opener may therefore block on and transfer priority to a checkpointer in some cases.

Database Writers

A database writer must obtain the exclusive WRITER lock. It uses a blocking lock to do so if any of the following are true:

  • the transaction is an implicit one consisting of a single DML or DDL statement, or
  • the transaction is opened using BEGIN IMMEDIATE or BEGIN EXCLUSIVE, or
  • the first SQL statement executed following the BEGIN command is a DML or DDL statement (not a read-only statement like a SELECT).

In other words, in all cases except when an open read-transaction is upgraded to a write-transaction. In that case a non-blocking lock is used.

Database Checkpointers

Database checkpointers takes the following locks, in order:

  • The exclusive CHECKPOINTER lock.
  • The exclusive WRITER lock (FULL, RESTART and TRUNCATE only).
  • Exclusive lock on read-mark slots 1-N. These are immediately released after being taken.
  • Exclusive lock on read-mark 0.
  • Exclusive lock on read-mark slots 1-N again. These are immediately released after being taken (RESTART and TRUNCATE only).

All of the above use blocking locks.

Summary

With blocking locks configured, the only cases in which clients should see an SQLITE_BUSY error are:

  • if the OS does not grant a blocking lock before the configured timeout expires, and
  • when an open read-transaction is upgraded to a write-transaction.

In all other cases the blocking locks implementation should prevent clients from having to handle SQLITE_BUSY errors and facilitate appropriate transfer of priorities between competing clients.

Clients that lock multiple databases simultaneously must be wary of deadlock.