]> git.ozlabs.org Git - ccan/blobdiff - ccan/tdb/transaction.c
tdb: handle processes dying during transaction commit.
[ccan] / ccan / tdb / transaction.c
index 261428fd9c9e5d0b902266ff3b91f9a24a37b898..11194773647e255f1a7c8743cd69d5c85bfeda59 100644 (file)
@@ -76,7 +76,7 @@
     to reduce this to 3 or even 2 with some more work.
 
   - check for a valid recovery record on open of the tdb, while the
     to reduce this to 3 or even 2 with some more work.
 
   - check for a valid recovery record on open of the tdb, while the
-    global lock is held. Automatically recover from the transaction
+    open lock is held. Automatically recover from the transaction
     recovery area if needed, then continue with the open as
     usual. This allows for smooth crash recovery with no administrator
     intervention.
     recovery area if needed, then continue with the open as
     usual. This allows for smooth crash recovery with no administrator
     intervention.
@@ -135,9 +135,6 @@ struct tdb_transaction {
        bool prepared;
        tdb_off_t magic_offset;
 
        bool prepared;
        tdb_off_t magic_offset;
 
-       /* set when the GLOBAL_LOCK has been taken */
-       bool global_lock_taken;
-
        /* old file size before transaction */
        tdb_len_t old_map_size;
 
        /* old file size before transaction */
        tdb_len_t old_map_size;
 
@@ -411,30 +408,12 @@ static int transaction_expand_file(struct tdb_context *tdb, tdb_off_t size,
        return 0;
 }
 
        return 0;
 }
 
-/*
-  brlock during a transaction - ignore them
-*/
-static int transaction_brlock(struct tdb_context *tdb,
-                             int rw_type, tdb_off_t offset, size_t len,
-                             enum tdb_lock_flags flags)
-{
-       return 0;
-}
-
-static int transaction_brunlock(struct tdb_context *tdb,
-                               int rw_type, tdb_off_t offset, size_t len)
-{
-       return 0;
-}
-
 static const struct tdb_methods transaction_methods = {
        transaction_read,
        transaction_write,
        transaction_next_hash_chain,
        transaction_oob,
        transaction_expand_file,
 static const struct tdb_methods transaction_methods = {
        transaction_read,
        transaction_write,
        transaction_next_hash_chain,
        transaction_oob,
        transaction_expand_file,
-       transaction_brlock,
-       transaction_brunlock
 };
 
 /*
 };
 
 /*
@@ -466,8 +445,8 @@ static int transaction_sync(struct tdb_context *tdb, tdb_off_t offset, tdb_len_t
        return 0;
 }
 
        return 0;
 }
 
-/* ltype is F_WRLCK after prepare. */
-int _tdb_transaction_cancel(struct tdb_context *tdb, int ltype)
+
+static int _tdb_transaction_cancel(struct tdb_context *tdb)
 {
        int i, ret = 0;
 
 {
        int i, ret = 0;
 
@@ -504,33 +483,12 @@ int _tdb_transaction_cancel(struct tdb_context *tdb, int ltype)
                }
        }
 
                }
        }
 
-       if (tdb->transaction->global_lock_taken) {
-               tdb_brunlock(tdb, F_WRLCK, GLOBAL_LOCK, 1);
-               tdb->transaction->global_lock_taken = false;
-       }
-
-       /* remove any global lock created during the transaction */
-       if (tdb->global_lock.count != 0) {
-               tdb_brunlock(tdb, tdb->global_lock.ltype,
-                            FREELIST_TOP, 4*tdb->header.hash_size);
-               tdb->global_lock.count = 0;
-       }
-
-       /* remove any locks created during the transaction */
-       if (tdb->num_locks != 0) {
-               for (i=0;i<tdb->num_lockrecs;i++) {
-                       tdb_brunlock(tdb, tdb->lockrecs[i].ltype,
-                                    FREELIST_TOP+4*tdb->lockrecs[i].list, 1);
-               }
-               tdb->num_locks = 0;
-               tdb->num_lockrecs = 0;
-               SAFE_FREE(tdb->lockrecs);
-       }
+       /* This also removes the OPEN_LOCK, if we have it. */
+       tdb_release_extra_locks(tdb);
 
        /* restore the normal io methods */
        tdb->methods = tdb->transaction->io_methods;
 
 
        /* restore the normal io methods */
        tdb->methods = tdb->transaction->io_methods;
 
-       tdb_brunlock(tdb, ltype, FREELIST_TOP, 0);
        tdb_transaction_unlock(tdb, F_WRLCK);
        SAFE_FREE(tdb->transaction->hash_heads);
        SAFE_FREE(tdb->transaction);
        tdb_transaction_unlock(tdb, F_WRLCK);
        SAFE_FREE(tdb->transaction->hash_heads);
        SAFE_FREE(tdb->transaction);
@@ -564,7 +522,7 @@ int tdb_transaction_start(struct tdb_context *tdb)
                return 0;
        }
 
                return 0;
        }
 
-       if (tdb->num_locks != 0 || tdb->global_lock.count) {
+       if (tdb_have_extra_locks(tdb)) {
                /* the caller must not have any locks when starting a
                   transaction as otherwise we'll be screwed by lack
                   of nested locks in posix */
                /* the caller must not have any locks when starting a
                   transaction as otherwise we'll be screwed by lack
                   of nested locks in posix */
@@ -603,10 +561,9 @@ int tdb_transaction_start(struct tdb_context *tdb)
        
        /* get a read lock from the freelist to the end of file. This
           is upgraded to a write lock during the commit */
        
        /* get a read lock from the freelist to the end of file. This
           is upgraded to a write lock during the commit */
-       if (tdb_brlock(tdb, F_RDLCK, FREELIST_TOP, 0, TDB_LOCK_WAIT) == -1) {
+       if (tdb_allrecord_lock(tdb, F_RDLCK, TDB_LOCK_WAIT, true) == -1) {
                TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_start: failed to get hash locks\n"));
                TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_start: failed to get hash locks\n"));
-               tdb->ecode = TDB_ERR_LOCK;
-               goto fail;
+               goto fail_allrecord_lock;
        }
 
        /* setup a copy of the hash table heads so the hash scan in
        }
 
        /* setup a copy of the hash table heads so the hash scan in
@@ -639,7 +596,8 @@ int tdb_transaction_start(struct tdb_context *tdb)
        return 0;
        
 fail:
        return 0;
        
 fail:
-       tdb_brunlock(tdb, F_RDLCK, FREELIST_TOP, 0);
+       tdb_allrecord_unlock(tdb, F_RDLCK, false);
+fail_allrecord_lock:
        tdb_transaction_unlock(tdb, F_WRLCK);
        SAFE_FREE(tdb->transaction->blocks);
        SAFE_FREE(tdb->transaction->hash_heads);
        tdb_transaction_unlock(tdb, F_WRLCK);
        SAFE_FREE(tdb->transaction->blocks);
        SAFE_FREE(tdb->transaction->hash_heads);
@@ -653,11 +611,8 @@ fail:
 */
 int tdb_transaction_cancel(struct tdb_context *tdb)
 {
 */
 int tdb_transaction_cancel(struct tdb_context *tdb)
 {
-       int ltype = F_RDLCK;
        tdb_trace(tdb, "tdb_transaction_cancel");
        tdb_trace(tdb, "tdb_transaction_cancel");
-       if (tdb->transaction && tdb->transaction->prepared)
-               ltype = F_WRLCK;
-       return _tdb_transaction_cancel(tdb, ltype);
+       return _tdb_transaction_cancel(tdb);
 }
 
 /*
 }
 
 /*
@@ -707,10 +662,16 @@ static int tdb_recovery_allocate(struct tdb_context *tdb,
 
        rec.rec_len = 0;
 
 
        rec.rec_len = 0;
 
-       if (recovery_head != 0 && 
-           methods->tdb_read(tdb, recovery_head, &rec, sizeof(rec), DOCONV()) == -1) {
-               TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_recovery_allocate: failed to read recovery record\n"));
-               return -1;
+       if (recovery_head != 0) {
+               if (methods->tdb_read(tdb, recovery_head, &rec, sizeof(rec), DOCONV()) == -1) {
+                       TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_recovery_allocate: failed to read recovery record\n"));
+                       return -1;
+               }
+               /* ignore invalid recovery regions: can happen in crash */
+               if (rec.magic != TDB_RECOVERY_MAGIC &&
+                   rec.magic != TDB_RECOVERY_INVALID_MAGIC) {
+                       recovery_head = 0;
+               }
        }
 
        *recovery_size = tdb_recovery_size(tdb);
        }
 
        *recovery_size = tdb_recovery_size(tdb);
@@ -917,14 +878,14 @@ static int _tdb_transaction_prepare_commit(struct tdb_context *tdb)
 
        if (tdb->transaction->prepared) {
                tdb->ecode = TDB_ERR_EINVAL;
 
        if (tdb->transaction->prepared) {
                tdb->ecode = TDB_ERR_EINVAL;
-               _tdb_transaction_cancel(tdb, F_WRLCK);
+               _tdb_transaction_cancel(tdb);
                TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_prepare_commit: transaction already prepared\n"));
                return -1;
        }
 
        if (tdb->transaction->transaction_error) {
                tdb->ecode = TDB_ERR_IO;
                TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_prepare_commit: transaction already prepared\n"));
                return -1;
        }
 
        if (tdb->transaction->transaction_error) {
                tdb->ecode = TDB_ERR_IO;
-               _tdb_transaction_cancel(tdb, F_RDLCK);
+               _tdb_transaction_cancel(tdb);
                TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_prepare_commit: transaction error pending\n"));
                return -1;
        }
                TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_prepare_commit: transaction error pending\n"));
                return -1;
        }
@@ -944,37 +905,33 @@ static int _tdb_transaction_prepare_commit(struct tdb_context *tdb)
        
        /* if there are any locks pending then the caller has not
           nested their locks properly, so fail the transaction */
        
        /* if there are any locks pending then the caller has not
           nested their locks properly, so fail the transaction */
-       if (tdb->num_locks || tdb->global_lock.count) {
+       if (tdb_have_extra_locks(tdb)) {
                tdb->ecode = TDB_ERR_LOCK;
                TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_prepare_commit: locks pending on commit\n"));
                tdb->ecode = TDB_ERR_LOCK;
                TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_prepare_commit: locks pending on commit\n"));
-               _tdb_transaction_cancel(tdb, F_RDLCK);
+               _tdb_transaction_cancel(tdb);
                return -1;
        }
 
        /* upgrade the main transaction lock region to a write lock */
                return -1;
        }
 
        /* upgrade the main transaction lock region to a write lock */
-       if (tdb_brlock_upgrade(tdb, FREELIST_TOP, 0) == -1) {
+       if (tdb_allrecord_upgrade(tdb) == -1) {
                TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_prepare_commit: failed to upgrade hash locks\n"));
                TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_prepare_commit: failed to upgrade hash locks\n"));
-               tdb->ecode = TDB_ERR_LOCK;
-               _tdb_transaction_cancel(tdb, F_RDLCK);
+               _tdb_transaction_cancel(tdb);
                return -1;
        }
 
                return -1;
        }
 
-       /* get the global lock - this prevents new users attaching to the database
+       /* get the open lock - this prevents new users attaching to the database
           during the commit */
           during the commit */
-       if (tdb_brlock(tdb, F_WRLCK, GLOBAL_LOCK, 1, TDB_LOCK_WAIT) == -1) {
-               TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_prepare_commit: failed to get global lock\n"));
-               tdb->ecode = TDB_ERR_LOCK;
-               _tdb_transaction_cancel(tdb, F_WRLCK);
+       if (tdb_nest_lock(tdb, OPEN_LOCK, F_WRLCK, TDB_LOCK_WAIT) == -1) {
+               TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_prepare_commit: failed to get open lock\n"));
+               _tdb_transaction_cancel(tdb);
                return -1;
        }
 
                return -1;
        }
 
-       tdb->transaction->global_lock_taken = true;
-
        if (!(tdb->flags & TDB_NOSYNC)) {
                /* write the recovery data to the end of the file */
                if (transaction_setup_recovery(tdb, &tdb->transaction->magic_offset) == -1) {
                        TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_prepare_commit: failed to setup recovery data\n"));
        if (!(tdb->flags & TDB_NOSYNC)) {
                /* write the recovery data to the end of the file */
                if (transaction_setup_recovery(tdb, &tdb->transaction->magic_offset) == -1) {
                        TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_prepare_commit: failed to setup recovery data\n"));
-                       _tdb_transaction_cancel(tdb, F_WRLCK);
+                       _tdb_transaction_cancel(tdb);
                        return -1;
                }
        }
                        return -1;
                }
        }
@@ -988,14 +945,14 @@ static int _tdb_transaction_prepare_commit(struct tdb_context *tdb)
                                             tdb->transaction->old_map_size) == -1) {
                        tdb->ecode = TDB_ERR_IO;
                        TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_prepare_commit: expansion failed\n"));
                                             tdb->transaction->old_map_size) == -1) {
                        tdb->ecode = TDB_ERR_IO;
                        TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_prepare_commit: expansion failed\n"));
-                       _tdb_transaction_cancel(tdb, F_WRLCK);
+                       _tdb_transaction_cancel(tdb);
                        return -1;
                }
                tdb->map_size = tdb->transaction->old_map_size;
                methods->tdb_oob(tdb, tdb->map_size + 1, 1);
        }
 
                        return -1;
                }
                tdb->map_size = tdb->transaction->old_map_size;
                methods->tdb_oob(tdb, tdb->map_size + 1, 1);
        }
 
-       /* Keep the global lock until the actual commit */
+       /* Keep the open lock until the actual commit */
 
        return 0;
 }
 
        return 0;
 }
@@ -1040,7 +997,7 @@ int tdb_transaction_commit(struct tdb_context *tdb)
 
        /* check for a null transaction */
        if (tdb->transaction->blocks == NULL) {
 
        /* check for a null transaction */
        if (tdb->transaction->blocks == NULL) {
-               _tdb_transaction_cancel(tdb, F_RDLCK);
+               _tdb_transaction_cancel(tdb);
                return 0;
        }
 
                return 0;
        }
 
@@ -1076,7 +1033,7 @@ int tdb_transaction_commit(struct tdb_context *tdb)
                        tdb->methods = methods;
                        tdb_transaction_recover(tdb); 
 
                        tdb->methods = methods;
                        tdb_transaction_recover(tdb); 
 
-                       _tdb_transaction_cancel(tdb, F_WRLCK);
+                       _tdb_transaction_cancel(tdb);
 
                        TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_commit: write failed\n"));
                        return -1;
 
                        TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_commit: write failed\n"));
                        return -1;
@@ -1111,7 +1068,7 @@ int tdb_transaction_commit(struct tdb_context *tdb)
 
        /* use a transaction cancel to free memory and remove the
           transaction locks */
 
        /* use a transaction cancel to free memory and remove the
           transaction locks */
-       _tdb_transaction_cancel(tdb, F_WRLCK);
+       _tdb_transaction_cancel(tdb);
 
        if (need_repack) {
                return tdb_repack(tdb);
 
        if (need_repack) {
                return tdb_repack(tdb);
@@ -1123,7 +1080,7 @@ int tdb_transaction_commit(struct tdb_context *tdb)
 
 /*
   recover from an aborted transaction. Must be called with exclusive
 
 /*
   recover from an aborted transaction. Must be called with exclusive
-  database write access already established (including the global
+  database write access already established (including the open
   lock to prevent new processes attaching)
 */
 int tdb_transaction_recover(struct tdb_context *tdb)
   lock to prevent new processes attaching)
 */
 int tdb_transaction_recover(struct tdb_context *tdb)
@@ -1225,16 +1182,6 @@ int tdb_transaction_recover(struct tdb_context *tdb)
                return -1;                      
        }
        
                return -1;                      
        }
        
-       /* reduce the file size to the old size */
-       tdb_munmap(tdb);
-       if (ftruncate(tdb->fd, recovery_eof) != 0) {
-               TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_recover: failed to reduce to recovery size\n"));
-               tdb->ecode = TDB_ERR_IO;
-               return -1;                      
-       }
-       tdb->map_size = recovery_eof;
-       tdb_mmap(tdb);
-
        if (transaction_sync(tdb, 0, recovery_eof) == -1) {
                TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_recover: failed to sync2 recovery\n"));
                tdb->ecode = TDB_ERR_IO;
        if (transaction_sync(tdb, 0, recovery_eof) == -1) {
                TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_recover: failed to sync2 recovery\n"));
                tdb->ecode = TDB_ERR_IO;
@@ -1247,3 +1194,28 @@ int tdb_transaction_recover(struct tdb_context *tdb)
        /* all done */
        return 0;
 }
        /* all done */
        return 0;
 }
+
+/* Any I/O failures we say "needs recovery". */
+bool tdb_needs_recovery(struct tdb_context *tdb)
+{
+       tdb_off_t recovery_head;
+       struct tdb_record rec;
+
+       /* find the recovery area */
+       if (tdb_ofs_read(tdb, TDB_RECOVERY_HEAD, &recovery_head) == -1) {
+               return true;
+       }
+
+       if (recovery_head == 0) {
+               /* we have never allocated a recovery record */
+               return false;
+       }
+
+       /* read the recovery record */
+       if (tdb->methods->tdb_read(tdb, recovery_head, &rec,
+                                  sizeof(rec), DOCONV()) == -1) {
+               return true;
+       }
+
+       return (rec.magic == TDB_RECOVERY_MAGIC);
+}