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