tdb2: allow transaction to nest.
authorRusty Russell <rusty@rustcorp.com.au>
Thu, 7 Apr 2011 01:29:45 +0000 (10:59 +0930)
committerRusty Russell <rusty@rustcorp.com.au>
Thu, 7 Apr 2011 01:29:45 +0000 (10:59 +0930)
This is definitely a bad idea in general, but SAMBA uses nested transactions
in many and varied ways (some of them probably reflect real bugs) and it's
far easier to support them inside tdb2 with a flag.

We already have part of the TDB1 infrastructure in place, so this patch
just completes it and fixes one place where I'd messed it up.

ccan/tdb2/open.c
ccan/tdb2/tdb.c
ccan/tdb2/tdb2.h
ccan/tdb2/transaction.c

index 1292fcc1ee32ee59431284527706461696096211..c3feecf73f34acfd08cabbd81570dd1883ddebc4 100644 (file)
@@ -383,7 +383,7 @@ struct tdb_context *tdb_open(const char *name, int tdb_flags,
        }
 
        if (tdb_flags & ~(TDB_INTERNAL | TDB_NOLOCK | TDB_NOMMAP | TDB_CONVERT
-                         | TDB_NOSYNC | TDB_SEQNUM)) {
+                         | TDB_NOSYNC | TDB_SEQNUM | TDB_ALLOW_NESTING)) {
                ecode = tdb_logerr(tdb, TDB_ERR_EINVAL, TDB_LOG_USE_ERROR,
                                   "tdb_open: unknown flags %u", tdb_flags);
                goto fail;
index 18cf2e93a598ea7c3f742bcb3e445a5985686d55..b96c80103688fd299064b7558b556ab956560e6d 100644 (file)
@@ -327,6 +327,9 @@ void tdb_add_flag(struct tdb_context *tdb, unsigned flag)
        case TDB_SEQNUM:
                tdb->flags |= TDB_SEQNUM;
                break;
+       case TDB_ALLOW_NESTING:
+               tdb->flags |= TDB_ALLOW_NESTING;
+               break;
        default:
                tdb->last_error = tdb_logerr(tdb, TDB_ERR_EINVAL,
                                             TDB_LOG_USE_ERROR,
@@ -357,6 +360,9 @@ void tdb_remove_flag(struct tdb_context *tdb, unsigned flag)
        case TDB_SEQNUM:
                tdb->flags &= ~TDB_SEQNUM;
                break;
+       case TDB_ALLOW_NESTING:
+               tdb->flags &= ~TDB_ALLOW_NESTING;
+               break;
        default:
                tdb->last_error = tdb_logerr(tdb, TDB_ERR_EINVAL,
                                             TDB_LOG_USE_ERROR,
index d9194a5eccce54629595966d277cefa0cb7cf8c5..0d2e1c78b023a4c15c1395852d7139dd391457a0 100644 (file)
@@ -80,6 +80,7 @@ struct tdb_context *tdb_open(const char *name, int tdb_flags,
 #define TDB_CONVERT 16 /* convert endian */
 #define TDB_NOSYNC   64 /* don't use synchronous transactions */
 #define TDB_SEQNUM   128 /* maintain a sequence number */
+#define TDB_ALLOW_NESTING   256 /* fake nested transactions */
 
 /**
  * tdb_close - close and free a tdb.
@@ -248,6 +249,15 @@ static inline struct tdb_data tdb_mkdata(const void *p, size_t len)
  * to read the tdb, but not alter it (they will block), nor will they see
  * any changes until tdb_transaction_commit() is called.
  *
+ * Note that if the TDB_ALLOW_NESTING flag is set, a tdb_transaction_start()
+ * within a transaction will succeed, but it's not a real transaction:
+ * (1) An inner transaction which is committed is not actually committed until
+ *     the outer transaction is; if the outer transaction is cancelled, the
+ *     inner ones are discarded.
+ * (2) tdb_transaction_cancel() marks the outer transaction as having an error,
+ *     so the final tdb_transaction_commit() will fail.
+ * (3) the outer transaction will see the results of the inner transaction.
+ *
  * See Also:
  *     tdb_transaction_cancel, tdb_transaction_commit.
  */
@@ -565,7 +575,7 @@ unsigned int tdb_get_flags(struct tdb_context *tdb);
 /**
  * tdb_add_flag - set a flag for a tdb
  * @tdb: the tdb context returned from tdb_open()
- * @flag: one of TDB_NOLOCK, TDB_NOMMAP or TDB_NOSYNC.
+ * @flag: one of TDB_NOLOCK, TDB_NOMMAP, TDB_NOSYNC or TDB_ALLOW_NESTING.
  *
  * You can use this to set a flag on the TDB.  You cannot set these flags
  * on a TDB_INTERNAL tdb.
@@ -575,7 +585,7 @@ void tdb_add_flag(struct tdb_context *tdb, unsigned flag);
 /**
  * tdb_remove_flag - unset a flag for a tdb
  * @tdb: the tdb context returned from tdb_open()
- * @flag: one of TDB_NOLOCK, TDB_NOMMAP or TDB_NOSYNC.
+ * @flag: one of TDB_NOLOCK, TDB_NOMMAP, TDB_NOSYNC or TDB_ALLOW_NESTING.
  *
  * You can use this to clear a flag on the TDB.  You cannot clear flags
  * on a TDB_INTERNAL tdb.
index 68ae234d2f90ccbe4fb993dd27476df7c89afba2..dd2f0e2507d095cb367ca06f865baea26d6ed1a2 100644 (file)
@@ -110,7 +110,7 @@ struct tdb_transaction {
        /* when inside a transaction we need to keep track of any
           nested tdb_transaction_start() calls, as these are allowed,
           but don't create a new transaction */
-       int nesting;
+       unsigned int nesting;
 
        /* set when a prepare has already occurred */
        bool prepared;
@@ -531,11 +531,15 @@ enum TDB_ERROR tdb_transaction_start(struct tdb_context *tdb)
 
        /* cope with nested tdb_transaction_start() calls */
        if (tdb->transaction != NULL) {
-               return tdb->last_error = tdb_logerr(tdb, TDB_ERR_IO,
-                                                   TDB_LOG_USE_ERROR,
-                                                   "tdb_transaction_start:"
-                                                   " already inside"
-                                                   " transaction");
+               if (!(tdb->flags & TDB_ALLOW_NESTING)) {
+                       return tdb->last_error
+                               = tdb_logerr(tdb, TDB_ERR_IO,
+                                            TDB_LOG_USE_ERROR,
+                                            "tdb_transaction_start:"
+                                            " already inside transaction");
+               }
+               tdb->transaction->nesting++;
+               return 0;
        }
 
        if (tdb_has_hash_locks(tdb)) {
@@ -907,7 +911,6 @@ static enum TDB_ERROR _tdb_transaction_prepare_commit(struct tdb_context *tdb)
 
 
        if (tdb->transaction->nesting != 0) {
-               tdb->transaction->nesting--;
                return TDB_SUCCESS;
        }