-
Notifications
You must be signed in to change notification settings - Fork 72
Thread Safety
LiteCore has very little explicit thread-safety built into its API layer. Instead it expects you to follow the general rule:
A C4Database object, and objects derived from it, shall not be called concurrently.
"Concurrently" applies to all the objects at once. For instance, calling into two C4Documents on two threads simultaneously would not be legal if they were associated with the same C4Database.
LiteCore objects don't have to be called on specific threads: you can call any object on any thread as long as you follow the rule above. So you can implement thread-safety by associating your own mutex with each C4Database, and locking the mutex while calling any object associated with that database.
This scope refers to in-memory objects, not files. It's perfectly legal to open two C4Databases on the same physical database file, and call them simultaneously (with a few exceptions detailed below.) In fact, the function c4db_openAgain exists to make this easy to do.
The general rule is conservative, and it is actually legal to call some of the functions concurrently. The rest of this document details this, by grouping the API into categories.
These functions can be called at any time, as long as their parameters remain valid during the call (i.e. another thread isn't freeing the data at the same time):
- Library info
c4_getBuildInfoc4_getObjectCountc4_dumpInstances
- C4Error API
c4error_makec4error_getMessageCc4error_mayBeTransientc4error_mayBeNetworkDependent
- C4Slice API
c4SliceEqualc4Slice_free
- C4Log API
-
c4log_* (entire API)
-
- Databases
c4db_openc4db_copyc4db_retain-
c4db_free(as long as it was balanced by a priorc4db_retain; it's really "release")
- Collections
c4db_getDefaultCollectionc4coll_isValidc4coll_getSpecc4coll_getDatabasec4coll_retainc4coll_release
- Documents
c4raw_freec4rev_getGenerationc4doc_isOldMetaPropertyc4doc_hasOldMetaPropertiesc4doc_encodeStrippingOldMetaProperties
- Database observers
c4dbobs_getChangesc4dbobs_freec4docobs_free
- Query observers
c4queryobs_create
- Replication
c4repl_isValidDatabaseNamec4repl_parseURLc4repl_getStatusc4repl_getResponseHeadersc4repl_stopc4repl_free
These functions can only be called if no other C4Database objects exist on the same file. Otherwise they'll return an error (not crash!)
c4db_deletec4db_deleteAtPathc4db_rekey
These functions follow the general rule: only one thread at a time can call any of these functions with a specific C4Database reference.
- Database operations
c4db_openAgainc4db_copyc4db_closec4db_compact
- Database properties
c4db_getPathc4db_getConfigc4db_getDocumentCountc4db_getLastSequencec4db_nextDocExpirationc4db_getMaxRevTreeDepthc4db_setMaxRevTreeDepthc4db_getUUIDs-
c4db_sharedFleeceEncoder(the code that uses the encoder needs to be database-exclusive too)
- Collection Actions
c4db_hasCollectionc4db_getCollectionc4db_createCollectionc4db_deleteCollectionc4db_collectionNamesc4coll_enumerateChangesc4coll_enumerateAllDocs
- Scope Actions
c4db_scopeNames
- Transactions (see note below)
c4db_beginTransactionc4db_endTransactionc4db_isInTransaction
- Document I/O
c4raw_getc4raw_putc4doc_getc4doc_getBySequencec4doc_putc4doc_create-
c4doc_update(Also document-exclusive) -
c4doc_save(Also document-exclusive) c4doc_setExpirationc4doc_getExpirationc4db_purgeDocc4coll_getDocumentCountc4coll_getLastSequencec4coll_getDocc4coll_getDocBySequencec4coll_putDocc4coll_moveDocc4coll_purgeDocc4coll_setDocExpirationc4coll_getDocExpiration
- Shared keys (Because FLSharedKeys isn't thread-safe)
c4db_getFLSharedKeysc4db_createFleeceEncoderc4db_sharedFleeceEncoderc4db_encodeJSONc4db_initFLDictKeyc4doc_bodyAsJSONc4doc_dictIsBlobc4doc_dictContainsBlobs
- Document expiration
c4db_enumerateExpiredc4exp_nextc4exp_getDocIDc4exp_purgeExpiredc4exp_closec4exp_freec4coll_purgeExpiredDocsc4coll_nextDocExpiration
- Database observers
c4dbobs_createc4dbobs_createFromCollectionc4docobs_createc4docobs_createWithCollection
- Queries
c4query_newc4query_releasec4query_explainc4query_runc4query_fullTextMatchedc4query_freec4queryenum_refreshc4queryenum_closec4queryenum_release-
c4queryobs_enable(true)(c4queryobs_create,c4queryobs_freeandc4queryobs_enable(false)are all thread-safe) c4coll_createIndexc4coll_deleteIndexc4coll_getIndexesInfo
- Replication
c4repl_newc4repl_newWithSocketc4db_getCookiesc4db_setCookiec4db_clearCookies-
c4socket_registerFactory(Can only be called once)
Transactions have their own concurrency behavior:
A transaction is associated with a C4Database instance, not with a thread.
If two threads call c4db_beginTransaction one after the other on the same database, this is legal but creates a single transaction. The second call does not block until the first thread ends its transaction; instead it just increments the transaction's ref-count. Any writes performed by either C4Database instance will go through, and be committed when the second call to c4db_endTransaction is made.
A related effect is that there is no read isolation between threads. If one thread opens a transaction and makes changes, another thread reading via the same C4Database instance will see those changes, even before the transaction is committed.
The moral of the story is that if your threads need their own transactions, they should open separate connections. Alternatively, you could create your own per-database transaction mutex that a thread acquires before beginning a transaction and releases after ending it.
The functions below operate on a C4Document instance in memory without accessing the database; these can be called on different documents of the same database at once, as long as each C4Document isn't called concurrently.
Direct access to C4Document's public fields obviously has the same restrictions, although concurrent reads are OK.
-
c4doc_selectRevision(see note) c4doc_selectCurrentRevision-
c4doc_loadRevisionBody(see note) c4doc_detachRevisionBodyc4doc_hasRevisionBodyc4doc_selectParentRevisionc4doc_selectNextRevision-
c4doc_selectNextLeafRevision(see note) c4doc_selectFirstPossibleAncestorOfc4doc_selectNextPossibleAncestorOfc4doc_selectCommonAncestorRevisionc4doc_removeRevisionBodyc4doc_purgeRevisionc4doc_resolveConflict-
c4doc_save(Also database-exclusive) -
c4doc_update(Also database-exclusive)
Note: The API allows for non-current document revisions to be stored separately in the database, which would make c4doc_loadRevisionBody database-exclusive since it might call into the database. The same goes for c4doc_selectRevision and c4doc_selectNextLeafRevision, when the withBody parameter is true. However, the current implementation never stores revision bodies externally, so in practice these functions are not database-exclusive.
These functions only access a C4Query's data, not the database, so their only restriction is that they can't be called on the same C4Query instance at the same time.
c4query_columnCountc4query_nameOfColumn
The functions that pass network events to a custom C4Socket should not be called concurrently on the same C4Socket.
c4socket_gotHTTPResponsec4socket_openedc4socket_closeRequestedc4socket_closedc4socket_completedWritec4socket_received