struct ucwlib_context *c = ucwlib_thread_context();
if (!c->trans_pool)
c->trans_pool = mp_new(1024);
+ if (!c->exc_pool)
+ c->exc_pool = mp_new(1024);
}
void
if (c->trans_pool)
{
mp_delete(c->trans_pool);
+ mp_delete(c->exc_pool);
c->trans_pool = NULL;
+ c->exc_pool = NULL;
}
c->current_trans = NULL;
}
return ucwlib_thread_context() -> current_trans;
}
+struct mempool *
+trans_get_pool(void)
+{
+ return ucwlib_thread_context() -> trans_pool;
+}
+
+struct mempool *
+trans_get_exc_pool(void)
+{
+ return ucwlib_thread_context() -> exc_pool;
+}
+
static void
-trans_close(struct trans *t)
+trans_pop(struct trans *t, struct ucwlib_context *c)
{
- struct ucwlib_context *c = ucwlib_thread_context();
+ 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 trans *t = trans_get_current();
- DBG("Commiting transaction %p", t);
+ struct ucwlib_context *c = ucwlib_thread_context();
+ struct trans *t = c->current_trans;
+ DBG("Committing transaction %p", t);
ASSERT(t);
+ ASSERT(!c->current_exc);
rp_detach(t->rpool);
- trans_close(t);
+ trans_pop(t, c);
+ trans_drop(t, c);
+}
+
+static void
+trans_rollback_exc(struct ucwlib_context *c)
+{
+ // In case we were processing an exception, roll back all transactions
+ // through which the exception has propagated.
+ struct exception *x = c->current_exc;
+ struct trans *t = x->trans;
+ while (t != c->current_trans)
+ {
+ DBG("Rolling back transaction %p after exception", t);
+ struct trans *tprev = t->prev_trans;
+ rp_delete(t->rpool);
+ trans_drop(t, c);
+ t = tprev;
+ }
+
+ c->current_exc = NULL;
+ mp_flush(c->exc_pool);
}
void
trans_rollback(void)
{
- struct trans *t = trans_get_current();
- DBG("Rolling back transaction %p", t);
+ struct ucwlib_context *c = ucwlib_thread_context();
+ if (c->current_exc)
+ {
+ trans_rollback_exc(c);
+ return;
+ }
+
+ struct trans *t = c->current_trans;
ASSERT(t);
+ DBG("Rolling back transaction %p", t);
rp_delete(t->rpool);
- trans_close(t);
+ trans_pop(t, c);
+ trans_drop(t, c);
}
void
trans_dump(void)
{
- struct trans *t = trans_get_current();
+ struct ucwlib_context *c = ucwlib_thread_context();
+ struct exception *x = c->current_exc;
+ struct trans *t = c->current_trans;
+
+ if (x)
+ {
+ printf("Exception %s (%s) in flight\n", x->id, x->msg);
+ struct trans *tx = x->trans;
+ while (tx != t)
+ {
+ printf("Recovering transaction %p:\n", tx);
+ rp_dump(tx->rpool);
+ tx = tx->prev_trans;
+ }
+ }
+
if (!t)
{
puts("No transaction open.");
}
}
+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);
+
+ // Remember which transaction have the exceptions started to propagate from
+ if (c->current_exc)
+ x->trans = c->current_exc->trans;
+ else
+ x->trans = t;
+
+ // Pop the current transaction off the transaction stack, but do not roll it
+ // back, it will be done when the exception stops propagating.
+ trans_pop(t, c);
+
+ // And jump overboard.
+ c->current_exc = x;
+ longjmp(t->jmp, 1);
+}
+
+void
+trans_throw(const char *id, void *object, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ struct mempool *mp = trans_get_pool();
+ struct exception *x = mp_alloc(mp, sizeof(*x));
+ x->id = id;
+ x->object = object;
+ x->msg = mp_vprintf(mp, fmt, args);
+ trans_throw_exc(x);
+}
+
+struct exception *
+trans_current_exc(void)
+{
+ return ucwlib_thread_context() -> current_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_throw("ucw.test2", "out", "Error: %s", x->msg);
+ }
+ TRANS_END;
+ }
+ TRANS_CATCH(x)
+ {
+ printf("Outer catch: %s\n", x->msg);
+ }
+ 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;
}
void trans_rollback(void);
void trans_dump(void);
+struct mempool *trans_get_pool(void);
+struct mempool *trans_get_exc_pool(void);
+
/* Exceptions */
+struct exception {
+ const char *id; // Hierarchic identifier of the exception
+ const char *msg; // Error message to present to the user
+ void *object; // Object on which the exception happened
+ struct trans *trans; // Transaction in which it happened (set by trans_throw*)
+ // More data specific for the particular `id' can follow
+};
+
+void trans_throw_exc(struct exception *x) NONRET;
+void trans_throw(const char *id, void *object, const char *fmt, ...) FORMAT_CHECK(printf,3,4) NONRET;
+
+struct exception *trans_current_exc(void);
+
+#define TRANS_TRY do { \
+ struct trans *_t = trans_open(); \
+ if (!setjmp(_t->jmp)) \
+ {
+
+#define TRANS_CATCH(x) \
+ trans_commit(); \
+ } \
+ else \
+ { \
+ struct exception *x UNUSED = trans_current_exc();
+
+#define TRANS_END \
+ trans_rollback(); \
+ } \
+ } while(0)
+
#endif