From 3176746705acac964df7656d553d26128295782f Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Sun, 17 Apr 2011 21:00:33 +0200 Subject: [PATCH] Trans: more documentation --- ucw/doc/trans.txt | 164 ++++++++++++++++++++++++---------------------- ucw/trans.h | 39 ++++++----- 2 files changed, 109 insertions(+), 94 deletions(-) diff --git a/ucw/doc/trans.txt b/ucw/doc/trans.txt index 5e5ec71e..d2c10df3 100644 --- a/ucw/doc/trans.txt +++ b/ucw/doc/trans.txt @@ -32,48 +32,50 @@ operating on resources work on both active and in-active pools. Transactions: ucw/trans.h ------------------------- -Assorted notes: - -- A transaction is tied to a thread which has created it. A transaction - can create a sub-transaction, so every thread keeps a stack of running - transactions in its per-thread data. Calling @trans_init() is optional, - but @trans_cleanup() should be used before a thread exits in order to - free resources used by transaction system. - -- Every transaction keeps a resource pool containing resources, which - have been created during the transaction. Whenever the transaction is - running, this pool is set as current. You are allowed to switch to - a different pool, but please do so carefully. -+ - When a transaction ends, the pool is destroyed and the previous active - pool is popped off the transaction stack. The fate of the resources - inside the pool depends on the operation used to end the transaction: - * commit -- all resources are detached from the pool - * rollback -- all resources are freed - * fold -- instead of destroying the pool, it is added as a subpool - to the parent transaction (which must exist) - -- Each transaction also includes a memory pool, from which all temporary - structures (including all resources created by the transaction) are - allocated. Feel free to allocate your temporary data from this pool, too; - they will be freed when the transaction is committed or rolled back. - When the transaction ends with a fold, this pool gets included inside - the parent transaction's pool. -+ - (To be true, there is actually a shared transaction pool per thread - and the transaction logic uses @mp_push() and @mp_pop() to keep a stack - of per-transaction data.) - -- Transactions are usually used together with exceptions (which are similar - to how exceptions work in other languages, but they differ in subtle details, - so please read carefully). When a failure condition of some kind is detected, - an exception is raised. It involves creating an exception object and jumping - out of the transaction by a `longjmp()`. The exception object (`struct exception`) - contains an identification of the error and possibly additional data. -+ - Usually, creation of an transaction and handling of exceptions is done - using helper macros (it is not strictly necessary, but highly recommended): -+ +Upon the resource pools, a transactional mechanism is built. A transaction +consists of a piece of code and a resource pool for temporary objects created +by the code. Whenever the transaction is running, this pool is set as current. +You are allowed to switch to a different pool, but please do so carefully. + +When a transaction ends, the pool is destroyed and the previous active +pool is popped off the transaction stack. The fate of the resources +inside the pool depends on the operation used to end the transaction: + +* *commit* -- all resources are detached from the pool +* *rollback* -- all resources are freed +* *fold* -- instead of destroying the pool, it is added as a subpool + to the parent transaction (which must exist) + +A transaction is tied to a thread which has created it. A transaction +can create a sub-transaction, so every thread keeps a stack of running +transactions in its per-thread data. Calling @trans_init() is optional, +but @trans_cleanup() should be used before a thread exits in order to +free resources used by transaction system. + +Each transaction also includes a memory pool, from which all temporary +structures (including all resources created by the transaction) are +allocated. Feel free to allocate your temporary data from this pool, too; +they will be freed when the transaction is committed or rolled back. +When the transaction ends with a fold, this pool gets included inside +the parent transaction's pool. + +(More precisely, there is actually a shared transaction pool per thread +and the transaction logic uses @mp_push() and @mp_pop() to keep a stack +of per-transaction data.) + +=== Exceptions === + +Transactions are commonly used together with exceptions (which are similar +to how exceptions work in other languages, but they differ in subtle details, +so please read carefully). When a failure condition of some kind is detected, +an exception is *raised* ("*thrown*" is also sometimes used). It involves +creating an exception object and jumping out of the transaction by +a `longjmp()`. The exception object (`struct exception`) contains an +identification of the error and possibly additional data. + +Usually, creation of an transaction and handling of exceptions is done +using *helper macros* (it is not strictly necessary, but highly recommended): + TRANS_TRY { // Code that runs inside the transaction. @@ -84,40 +86,44 @@ Assorted notes: } TRANS_END; - The code inside the transaction ends with an implicit @trans_commit(). - If you want to end the transaction in a different way, you can do so, - but you need to use a `break` statement to skip the implicit commit. - - The exception handling code gets a local variable `x` pointing to the - exception object. When the exception is handled (for example, an error - message is logged), @trans_caught() is called automatically, which rolls - back the transaction and frees all its resources. Again, you can use the - `break` statement to skip this. - - Alternatively, when you are in a nested transaction, you can throw a different - exception or re-throw the original one. This raises an exception in the - context of the parent transaction. In this case, the child transaction is - not rolled back, but its pools are folded as sub-pools of the parent transaction - and kept until @trans_caught() is called finally. - -* When an exception is thrown outside a transaction, it is converted to - a plain @die(). - -* Memory management and lifetime of various objects and pools deserve special - attention, as usually when non-local jumps are taking place. When an exception - is raised, the exception structure is allocated from the memory pool of the - current transaction. When the exception is propagated through the stack of - transactions, no transaction is ever rolled back -- all of them are folded - and their pools remain accessible until @trans_caught() is called at the end. - Therefore exceptions can carry pointers to the objects which have failed - without a risk of the object becoming invalid. However, you need to avoid - pointing to on-stack data like local variables of functions, because these - are of course destroyed during the `longjmp()`. - -FIXME: Interaction between exceptions, pools and other libucw modules. - -FIXME: Unit tests - -FIXME: Resourcification of more libucw objects - -FIXME: Do we want to allow res_alloc() when no pool is active. +The code inside the transaction ends with an implicit @trans_commit(). +If you want to end the transaction in a different way, you can do so, +but you need to use a `break` statement to skip the implicit commit. + +The exception handling code gets a local variable `x` pointing to the +exception object. When the exception is handled (for example, an error +message is logged), @trans_caught() is called automatically, which rolls +back the transaction and frees all its resources. Again, you can use the +`break` statement to skip this. + +Alternatively, when you are in a *nested transaction*, you can throw a different +exception or re-throw the original one. This raises an exception in the +context of the parent transaction. In this case, the child transaction is +not rolled back, but its pools are folded as sub-pools of the parent transaction +and kept until @trans_caught() is called finally. + +When an exception is thrown *outside a transaction*, it is converted to +a plain @die(). + +*Memory management* and lifetime of various objects and pools deserve special +attention, as usually when non-local jumps are taking place. When an exception +is raised, the exception structure is allocated from the memory pool of the +current transaction. When the exception is propagated through the stack of +transactions, no transaction is ever rolled back -- all of them are folded +and their pools remain accessible until @trans_caught() is called at the end. +Therefore exceptions can carry pointers to the objects which have failed +without a risk of the object becoming invalid. However, you need to avoid +pointing to on-stack data like local variables of functions, because these +are of course destroyed during the `longjmp()`. + +=== Functions and structures === + +!!ucw/trans.h + +== FIXME's == + +- Interaction between exceptions, pools and other libucw modules. +- Unit tests +- Resourcification of more libucw objects. +- Do we want to allow res_alloc() when no pool is active? +- Structure of exception identifiers: rethink and document diff --git a/ucw/trans.h b/ucw/trans.h index 608581fa..15a08fd1 100644 --- a/ucw/trans.h +++ b/ucw/trans.h @@ -14,8 +14,7 @@ #include -/* Transactions */ - +/** A structure describing a transaction. All fields are for internal use only. **/ struct trans { struct trans *prev_trans; struct mempool_state *trans_pool_state; @@ -25,33 +24,43 @@ struct trans { jmp_buf jmp; }; -void trans_init(void); // Called automatically on trans_open() if needed -void trans_cleanup(void); // Free memory occupied by the transaction system pools +void trans_init(void); /** Initializes the transaction system for the current thread. Called automatically as needed. **/ +void trans_cleanup(void); /** Frees memory occupied by the transaction system pools for the current thread. **/ -struct trans *trans_open(void); -struct trans *trans_get_current(void); -void trans_commit(void); -void trans_rollback(void); -void trans_fold(void); -void trans_dump(void); +struct trans *trans_open(void); /** Creates a new transaction. Used inside `TRANS_TRY`. **/ +struct trans *trans_get_current(void); /** Get a pointer to the currently running transaction, or NULL if there is none. **/ +void trans_commit(void); /** Commits the current transaction. **/ +void trans_rollback(void); /** Rolls back the current transaction. **/ +void trans_fold(void); /** Folds the current transaction to its parent. **/ +void trans_dump(void); /** Prints out a debugging dump of the transaction stack to stdout. **/ struct mempool *trans_get_pool(void); -/* Exceptions */ - +/** + * Data associated with an exception. Usually, this structure is created + * by calling @trans_throw(), but if you want to pass more data, you can + * create your own exception and throw it using @trans_throw_exc(). + **/ struct exception { - const char *id; // Hierarchic identifier of the exception + const char *id; // Hierarchical identifier of the exception const char *msg; // Error message to present to the user void *object; // Object on which the exception happened // More data specific for the particular `id' can follow }; -void trans_throw_exc(struct exception *x) NONRET; +/** Creates an exception and throws it. The error message can contain `printf`-like formatting. **/ void trans_throw(const char *id, void *object, const char *fmt, ...) FORMAT_CHECK(printf,3,4) NONRET; + +/** A `va_list` variant of @trans_throw(). **/ void trans_vthrow(const char *id, void *object, const char *fmt, va_list args) NONRET; + +/** Throw an already constructed exception (or re-throw an exception you have caught). **/ +void trans_throw_exc(struct exception *x) NONRET; + +/** Declare the current exception caught and roll back the current transaction. Called from `TRANS_END`. **/ void trans_caught(void); -struct exception *trans_current_exc(void); +struct exception *trans_current_exc(void); /** Return the exception in flight, or NULL if there is none. **/ #define TRANS_TRY do { \ struct trans *_t = trans_open(); \ -- 2.39.2