You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
278 lines
5.7 KiB
278 lines
5.7 KiB
/*
|
|
* The UCW Library -- Transactions
|
|
*
|
|
* (c) 2008--2011 Martin Mares <mj@ucw.cz>
|
|
* (c) 2021 Pavel Charvat <pchar@ucw.cz>
|
|
*
|
|
* This software may be freely distributed and used according to the terms
|
|
* of the GNU Lesser General Public License.
|
|
*/
|
|
|
|
#undef LOCAL_DEBUG
|
|
|
|
#include <ucw/lib.h>
|
|
#include <ucw/trans.h>
|
|
#include <ucw/resource.h>
|
|
#include <ucw/mempool.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
void
|
|
trans_init(void)
|
|
{
|
|
struct ucwlib_context *c = ucwlib_thread_context();
|
|
if (!c->trans_pool)
|
|
c->trans_pool = mp_new(1024);
|
|
}
|
|
|
|
void
|
|
trans_cleanup(void)
|
|
{
|
|
struct ucwlib_context *c = ucwlib_thread_context();
|
|
if (c->trans_pool)
|
|
{
|
|
mp_delete(c->trans_pool);
|
|
c->trans_pool = NULL;
|
|
}
|
|
c->current_trans = NULL;
|
|
}
|
|
|
|
struct trans *
|
|
trans_open(void)
|
|
{
|
|
trans_init();
|
|
struct ucwlib_context *c = ucwlib_thread_context();
|
|
struct mempool *mp = c->trans_pool;
|
|
|
|
struct mempool_state *mst = mp_push(mp);
|
|
struct trans *t = mp_alloc(mp, sizeof(*t));
|
|
t->trans_pool_state = mst;
|
|
|
|
struct respool *rp = rp_new("trans", mp);
|
|
t->rpool = rp;
|
|
t->prev_rpool = rp_switch(rp);
|
|
|
|
t->thrown_exc = NULL;
|
|
t->prev_trans = c->current_trans;
|
|
c->current_trans = t;
|
|
DBG("Opened transaction %p", t);
|
|
return t;
|
|
}
|
|
|
|
struct trans *
|
|
trans_get_current(void)
|
|
{
|
|
return ucwlib_thread_context() -> current_trans;
|
|
}
|
|
|
|
struct mempool *
|
|
trans_get_pool(void)
|
|
{
|
|
return ucwlib_thread_context() -> trans_pool;
|
|
}
|
|
|
|
static void
|
|
trans_pop(struct trans *t, struct ucwlib_context *c)
|
|
{
|
|
DBG("... popping trans %p", t);
|
|
rp_switch(t->prev_rpool);
|
|
c->current_trans = t->prev_trans;
|
|
}
|
|
|
|
static void
|
|
trans_drop(struct trans *t, struct ucwlib_context *c)
|
|
{
|
|
DBG("... dropping trans %p", t);
|
|
mp_restore(c->trans_pool, t->trans_pool_state);
|
|
}
|
|
|
|
void
|
|
trans_commit(void)
|
|
{
|
|
struct ucwlib_context *c = ucwlib_thread_context();
|
|
struct trans *t = c->current_trans;
|
|
DBG("Committing transaction %p", t);
|
|
ASSERT(t);
|
|
ASSERT(!t->thrown_exc);
|
|
rp_commit(t->rpool);
|
|
trans_pop(t, c);
|
|
trans_drop(t, c);
|
|
}
|
|
|
|
void
|
|
trans_rollback(void)
|
|
{
|
|
struct ucwlib_context *c = ucwlib_thread_context();
|
|
struct trans *t = c->current_trans;
|
|
DBG("Rolling back transaction %p", t);
|
|
ASSERT(t);
|
|
ASSERT(!t->thrown_exc);
|
|
rp_delete(t->rpool);
|
|
trans_pop(t, c);
|
|
trans_drop(t, c);
|
|
}
|
|
|
|
void
|
|
trans_fold(void)
|
|
{
|
|
struct ucwlib_context *c = ucwlib_thread_context();
|
|
struct trans *t = c->current_trans;
|
|
DBG("Folding transaction %p", t);
|
|
ASSERT(t);
|
|
ASSERT(!t->thrown_exc);
|
|
trans_pop(t, c);
|
|
ASSERT(c->current_trans); // Ensure that the parent exists
|
|
res_subpool(t->rpool);
|
|
t->rpool = NULL; // To stop people from using the trans
|
|
}
|
|
|
|
void
|
|
trans_caught(void)
|
|
{
|
|
// Exception has been finally caught. Roll back the current transaction,
|
|
// including all sub-transactions that have been folded to it during
|
|
// propagation.
|
|
struct trans *t = trans_get_current();
|
|
struct exception *x = t->thrown_exc;
|
|
ASSERT(x);
|
|
DBG("... exception %p caught", x);
|
|
t->thrown_exc = NULL;
|
|
trans_rollback();
|
|
}
|
|
|
|
void
|
|
trans_dump(void)
|
|
{
|
|
struct trans *t = trans_get_current();
|
|
if (!t)
|
|
{
|
|
puts("No transaction open.");
|
|
return;
|
|
}
|
|
|
|
while (t)
|
|
{
|
|
printf("Transaction %p:\n", t);
|
|
struct exception *x = t->thrown_exc;
|
|
if (x)
|
|
printf(" Exception %s (%s) in flight\n", x->id, x->msg);
|
|
rp_dump(t->rpool, 4);
|
|
t = t->prev_trans;
|
|
}
|
|
}
|
|
|
|
void
|
|
trans_throw_exc(struct exception *x)
|
|
{
|
|
struct ucwlib_context *c = ucwlib_thread_context();
|
|
struct trans *t = c->current_trans;
|
|
DBG("Throwing exception %s (%s) in trans %p", x->id, x->msg, t);
|
|
if (!t)
|
|
die("%s", x->msg);
|
|
|
|
// If we are already handling an exception (i.e., throw from a catch handler),
|
|
// fold the current transaction into its parent.
|
|
while (t->thrown_exc)
|
|
{
|
|
if (!t->prev_trans)
|
|
die("%s", x->msg);
|
|
t->thrown_exc = NULL;
|
|
trans_fold();
|
|
t = c->current_trans;
|
|
DBG("... recursive throw propagated to parent trans %p", t);
|
|
}
|
|
|
|
// And jump overboard.
|
|
t->thrown_exc = x;
|
|
longjmp(t->jmp, 1);
|
|
}
|
|
|
|
void
|
|
trans_throw(const char *id, void *object, const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
trans_vthrow(id, object, fmt, args);
|
|
}
|
|
|
|
void
|
|
trans_vthrow(const char *id, void *object, const char *fmt, va_list args)
|
|
{
|
|
struct mempool *mp = trans_get_pool();
|
|
if (!mp)
|
|
vdie(fmt, args);
|
|
struct exception *x = mp_alloc(mp, sizeof(*x));
|
|
x->id = mp_strdup(mp, id);
|
|
x->object = object;
|
|
x->msg = mp_vprintf(mp, fmt, args);
|
|
trans_throw_exc(x);
|
|
}
|
|
|
|
struct exception *
|
|
trans_current_exc(void)
|
|
{
|
|
return trans_get_current() -> thrown_exc;
|
|
}
|
|
|
|
#ifdef TEST
|
|
|
|
static void trc_free(struct resource *r)
|
|
{
|
|
printf("Freeing object #%d\n", (int)(intptr_t) r->priv);
|
|
}
|
|
|
|
static struct res_class trc = {
|
|
.name = "test",
|
|
.free = trc_free,
|
|
};
|
|
|
|
int main(void)
|
|
{
|
|
TRANS_TRY
|
|
{
|
|
res_new(&trc, (void *)(intptr_t) 1);
|
|
res_malloc(64, NULL);
|
|
TRANS_TRY
|
|
{
|
|
res_new(&trc, (void *)(intptr_t) 2);
|
|
res_malloc(128, NULL);
|
|
trans_throw("ucw.test", "inn", "Universe failure: %d+%d=%d", 1, 2, 4);
|
|
}
|
|
TRANS_CATCH(x)
|
|
{
|
|
printf("Inner catch: %s\n", x->msg);
|
|
trans_dump();
|
|
TRANS_TRY
|
|
{
|
|
res_malloc(256, NULL);
|
|
trans_throw("ucw.test.nested", "nest", "Something is wrong recursively");
|
|
}
|
|
TRANS_CATCH(y)
|
|
{
|
|
printf("Yet another layer: %s\n", y->msg);
|
|
trans_dump();
|
|
// trans_throw_exc(y);
|
|
}
|
|
TRANS_END;
|
|
trans_throw("ucw.test2", "out", "Error: %s", x->msg);
|
|
}
|
|
TRANS_END;
|
|
}
|
|
TRANS_CATCH(x)
|
|
{
|
|
printf("Outer catch: %s\n", x->msg);
|
|
trans_dump();
|
|
}
|
|
TRANS_END;
|
|
trans_throw("ucw.test3", "glob", "Global error. Reboot globe.");
|
|
#if 0
|
|
trans_open();
|
|
res_malloc(64, NULL);
|
|
trans_dump();
|
|
trans_commit();
|
|
#endif
|
|
trans_cleanup();
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|