]> mj.ucw.cz Git - libucw.git/blob - ucw/trans.c
a8de27cdcc5f7ea82c499e56c82eb87d38717009
[libucw.git] / ucw / trans.c
1 /*
2  *      The UCW Library -- Transactions
3  *
4  *      (c) 2008--2011 Martin Mares <mj@ucw.cz>
5  *
6  *      This software may be freely distributed and used according to the terms
7  *      of the GNU Lesser General Public License.
8  */
9
10 #undef LOCAL_DEBUG
11
12 #include "ucw/lib.h"
13 #include "ucw/trans.h"
14 #include "ucw/respool.h"
15 #include "ucw/mempool.h"
16
17 #include <stdio.h>
18
19 void
20 trans_init(void)
21 {
22   struct ucwlib_context *c = ucwlib_thread_context();
23   if (!c->trans_pool)
24     c->trans_pool = mp_new(1024);
25 }
26
27 void
28 trans_cleanup(void)
29 {
30   struct ucwlib_context *c = ucwlib_thread_context();
31   if (c->trans_pool)
32     {
33       mp_delete(c->trans_pool);
34       c->trans_pool = NULL;
35     }
36   c->current_trans = NULL;
37 }
38
39 struct trans *
40 trans_open(void)
41 {
42   trans_init();
43   struct ucwlib_context *c = ucwlib_thread_context();
44   struct mempool *mp = c->trans_pool;
45
46   struct mempool_state *mst = mp_push(mp);
47   struct trans *t = mp_alloc(mp, sizeof(*t));
48   t->trans_pool_state = mst;
49
50   struct respool *rp = rp_new("trans", mp);
51   t->rpool = rp;
52   t->prev_rpool = rp_switch(rp);
53
54   t->thrown_exc = NULL;
55   t->prev_trans = c->current_trans;
56   c->current_trans = t;
57   DBG("Opened transaction %p", t);
58   return t;
59 }
60
61 struct trans *
62 trans_get_current(void)
63 {
64   return ucwlib_thread_context() -> current_trans;
65 }
66
67 struct mempool *
68 trans_get_pool(void)
69 {
70   return ucwlib_thread_context() -> trans_pool;
71 }
72
73 static void
74 trans_pop(struct trans *t, struct ucwlib_context *c)
75 {
76   DBG("... popping trans %p", t);
77   rp_switch(t->prev_rpool);
78   c->current_trans = t->prev_trans;
79 }
80
81 static void
82 trans_drop(struct trans *t, struct ucwlib_context *c)
83 {
84   DBG("... dropping trans %p", t);
85   mp_restore(c->trans_pool, t->trans_pool_state);
86 }
87
88 void
89 trans_commit(void)
90 {
91   struct ucwlib_context *c = ucwlib_thread_context();
92   struct trans *t = c->current_trans;
93   DBG("Committing transaction %p", t);
94   ASSERT(t);
95   ASSERT(!t->thrown_exc);
96   rp_detach(t->rpool);
97   trans_pop(t, c);
98   trans_drop(t, c);
99 }
100
101 void
102 trans_rollback(void)
103 {
104   struct ucwlib_context *c = ucwlib_thread_context();
105   struct trans *t = c->current_trans;
106   DBG("Rolling back transaction %p", t);
107   ASSERT(t);
108   ASSERT(!t->thrown_exc);
109   rp_delete(t->rpool);
110   trans_pop(t, c);
111   trans_drop(t, c);
112 }
113
114 void
115 trans_fold(void)
116 {
117   struct ucwlib_context *c = ucwlib_thread_context();
118   struct trans *t = c->current_trans;
119   DBG("Folding transaction %p", t);
120   ASSERT(t);
121   ASSERT(!t->thrown_exc);
122   trans_pop(t, c);
123   ASSERT(c->current_trans);     // Ensure that the parent exists
124   res_subpool(t->rpool);
125   t->rpool = NULL;              // To stop people from using the trans
126 }
127
128 void
129 trans_caught(struct exception *x)
130 {
131   // Exception has been finally caught. Roll back the current transaction,
132   // including all sub-transactions that have been folded to it during
133   // propagation.
134   DBG("... exception %p caught", x);
135   struct trans *t = trans_get_current();
136   ASSERT(t->thrown_exc == x);
137   t->thrown_exc = NULL;
138   trans_rollback();
139 }
140
141 void
142 trans_dump(void)
143 {
144   struct trans *t = trans_get_current();
145   if (!t)
146     {
147       puts("No transaction open.");
148       return;
149     }
150
151   while (t)
152     {
153       printf("Transaction %p:\n", t);
154       struct exception *x = t->thrown_exc;
155       if (x)
156         printf("    Exception %s (%s) in flight\n", x->id, x->msg);
157       rp_dump(t->rpool, 4);
158       t = t->prev_trans;
159     }
160 }
161
162 void
163 trans_throw_exc(struct exception *x)
164 {
165   struct ucwlib_context *c = ucwlib_thread_context();
166   struct trans *t = c->current_trans;
167   DBG("Throwing exception %s (%s) in trans %p", x->id, x->msg, t);
168   if (!t)
169     die("%s", x->msg);
170
171   // If we are already handling an exception (i.e., throw from a catch handler),
172   // fold the current transaction into its parent.
173   if (t->thrown_exc)
174     {
175       if (!t->prev_trans)
176         die("%s", x->msg);
177       t->thrown_exc = NULL;
178       trans_fold();
179       t = c->current_trans;
180       DBG("... recursive throw propagated to parent trans %p", t);
181     }
182
183   // And jump overboard.
184   t->thrown_exc = x;
185   longjmp(t->jmp, 1);
186 }
187
188 void
189 trans_throw(const char *id, void *object, const char *fmt, ...)
190 {
191   va_list args;
192   va_start(args, fmt);
193   trans_vthrow(id, object, fmt, args);
194 }
195
196 void
197 trans_vthrow(const char *id, void *object, const char *fmt, va_list args)
198 {
199   struct mempool *mp = trans_get_pool();
200   if (!mp)
201     vdie(fmt, args);
202   struct exception *x = mp_alloc(mp, sizeof(*x));
203   x->id = id;
204   x->object = object;
205   x->msg = mp_vprintf(mp, fmt, args);
206   trans_throw_exc(x);
207 }
208
209 struct exception *
210 trans_current_exc(void)
211 {
212   return trans_get_current() -> thrown_exc;
213 }
214
215 #ifdef TEST
216
217 static void trc_free(struct resource *r)
218 {
219   printf("Freeing object #%d\n", (int)(intptr_t) r->priv);
220 }
221
222 static struct res_class trc = {
223   .name = "test",
224   .free = trc_free,
225 };
226
227 int main(void)
228 {
229   TRANS_TRY
230     {
231       res_new(&trc, (void *)(intptr_t) 1);
232       res_malloc(64, NULL);
233       TRANS_TRY
234         {
235           res_new(&trc, (void *)(intptr_t) 2);
236           res_malloc(128, NULL);
237           trans_throw("ucw.test", "inn", "Universe failure: %d+%d=%d", 1, 2, 4);
238         }
239       TRANS_CATCH(x)
240         {
241           printf("Inner catch: %s\n", x->msg);
242           trans_dump();
243           trans_throw("ucw.test2", "out", "Error: %s", x->msg);
244         }
245       TRANS_END;
246     }
247   TRANS_CATCH(x)
248     {
249       printf("Outer catch: %s\n", x->msg);
250       trans_dump();
251     }
252   TRANS_END;
253   trans_throw("ucw.test3", "glob", "Global error. Reboot globe.");
254 #if 0
255   trans_open();
256   res_malloc(64, NULL);
257   trans_dump();
258   trans_commit();
259 #endif
260   trans_cleanup();
261   return 0;
262 }
263
264 #endif