Improved transaction handling in PulleyBack plugin (poolback)
[tlspool] / pulleyback / api.c
1 /* pulleyback/api.c -- The API to the Pulley backend for the TLS Pool
2  *
3  * From: Rick van Rein <rick@openfortress.nl>
4  */
5
6
7 #include <stdlib.h>
8 #include <string.h>
9 #include <assert.h>
10
11 #include <errno.h>
12
13 #include "api.h"
14
15 #include "poolback.h"
16
17
18 /* Allocate a handler, parse information into it, access the database.
19  */
20 void *pulleyback_open (int argc, char **argv, int varc) {
21         struct pulleyback_tlspool *self;
22         int rv;
23         //
24         // Allocate memory and wipe it clean
25         self = malloc (sizeof (struct pulleyback_tlspool));
26         if (self == NULL) {
27                 errno = ENOMEM;
28                 return NULL;
29         }
30         memset (self, 0, sizeof (struct pulleyback_tlspool));
31         //
32         // Parse arguments, connect to the database
33         rv = parse_arguments (argc, argv, varc, self);
34         if (rv != 0) {
35                 free (self);
36                 errno = EINVAL;
37                 return NULL;
38         }
39         errno = 0;
40         rv = open_database (self);
41         if (rv != 0) {
42                 free (self);
43                 if (errno == 0) {
44                         errno = ENXIO;
45                 }
46                 return NULL;
47         }
48         //
49         // Return the successful result -- mapped to a (void *)
50         return (void *) self;
51 }
52
53 void pulleyback_close (void *pbh) {
54         struct pulleyback_tlspool *self = (struct pulleyback_tlspool *) pbh;
55         if (self == NULL) {
56                 return;
57         }
58         //
59         // Disconnect from the database after implicit rollback
60         if (self->txn_state != TXN_NONE) {
61                 pulleyback_rollback (pbh);
62         }
63         close_database (self);
64         //
65         // Cleanup fields allocated with strdup()
66         if (self->db_env != NULL) {
67                 free (self->db_env);
68                 self->db_env = NULL;
69         }
70         if (self->db_filename != NULL) {
71                 free (self->db_filename);
72                 self->db_filename = NULL;
73         }
74         //
75         // Free the basic data structure
76         free (self);
77         self = NULL;
78 }
79
80 /* Internal method to ensure having a transaction, return 0 on error
81  */
82 static int have_txn (struct pulleyback_tlspool *self) {
83         switch (self->txn_state) {
84         case TXN_NONE:
85                 if (0 != self->env->txn_begin (self->env, NULL, &self->txn, 0)) {
86                         return 0;
87                 }
88                 self->txn_state = TXN_ACTIVE;
89                 return 1;
90         case TXN_ABORT:
91         case TXN_ACTIVE:
92                 return 1;
93         case TXN_SUCCESS:
94                 // You cannot have_txn() after _prepare()
95                 assert ((self->txn_state == TXN_NONE) || (self->txn_state == TXN_ACTIVE));
96                 return 0;
97         }
98 }
99
100 /* Internal method to process a negative "ok" value by switching to TXN_ABORT
101  */
102 static int check_txn (struct pulleyback_tlspool *self, int ok) {
103         if (ok != 1) {
104                 if (self->txn_state == TXN_ACTIVE) {
105                         self->txn_state = TXN_ABORT;
106                 }
107         }
108 }
109
110 int pulleyback_add (void *pbh, uint8_t **forkdata) {
111         struct pulleyback_tlspool *self = (struct pulleyback_tlspool *) pbh;
112         int ok = 1;
113         ok = ok && have_txn (self);
114         ok = ok && (self->txn_state == TXN_ACTIVE);
115         ok = ok && self->update (self, forkdata, 0);
116         check_txn (self, ok);
117         return ok;
118 }
119
120 int pulleyback_del (void *pbh, uint8_t **forkdata) {
121         struct pulleyback_tlspool *self = (struct pulleyback_tlspool *) pbh;
122         int ok = 1;
123         ok = ok && have_txn (self);
124         ok = ok && (self->txn_state == TXN_ACTIVE);
125         ok = ok && self->update (self, forkdata, 1);
126         check_txn (self, ok);
127         return ok;
128 }
129
130
131 int pulleyback_reset (void *pbh) {
132         struct pulleyback_tlspool *self = (struct pulleyback_tlspool *) pbh;
133         int ok = 1;
134         u_int32_t count;
135         ok = ok && have_txn (self);
136         ok = ok && (self->txn_state == TXN_ACTIVE);
137         ok = ok && (0 == self->db->truncate (self->db, self->txn, &count, 0));
138         check_txn (self, ok);
139         return ok;
140 }
141
142
143 /* Transactions are somewhat complex, also because they are implicitly
144  * started when changes are made using _add(), _del() or _reset().
145  * These operations may be conditional in the calling program, and we
146  * want to aliviete the burden of maintaining a state as to whether
147  * a transaction has been started implicitly, so we accept calls to
148  * _prepare(), _rollback() or _commit() when no transaction exists
149  * yet.  We will implement such occurrences equivalently to opening a
150  * new transaction and acting on it immediately (though the logs may
151  * not show it if we can optimise by not actually doing it -- it is
152  * much simpler to skip the _rollback() or _commit() on an empty
153  * transaction.
154  *
155  * We use txn_state to maintain state between _prepare() and its followup
156  * call; the followup may be _prepare(), which is idempotent and will
157  * return the same result; it may be _commit(), but only when the
158  * preceding _prepare() has reported success; or it may be _rollback(),
159  * regardless of the state reported by _prepare().
160  *
161  * Invalid sequences are reported through assert() -- which is not a
162  * bug but a facility!  It helps the plugin user to code transaction
163  * logic correctly.  The implicitness of transactions means that we
164  * cannot capture all logic failures though.
165  */
166
167 int pulleyback_prepare (void *pbh) {
168         struct pulleyback_tlspool *self = (struct pulleyback_tlspool *) pbh;
169         int ok = 1;
170         switch (self->txn_state) {
171         case TXN_NONE:
172                 // We want to return success, so we'd better create an
173                 // empty transaction to permit future _commit() or _rollback()
174                 ok = ok && have_txn (self);
175                 // ...continue into case TXN_ACTIVE...
176         case TXN_ACTIVE:
177                 ok = ok && (0 == self->txn->prepare (self->txn, self->txn_gid));
178                 self->txn_state = ok? TXN_SUCCESS: TXN_ABORT;
179                 break;
180         case TXN_SUCCESS:
181                 // The transaction has already been successfully prepared
182                 ok = ok && 1;
183                 break;
184         case TXN_ABORT:
185                 // The transaction has already failed preparing for commit
186                 ok = 0;
187                 break;
188         }
189         return ok;
190 }
191
192 int pulleyback_commit (void *pbh) {
193         struct pulleyback_tlspool *self = (struct pulleyback_tlspool *) pbh;
194         int ok = 1;
195         switch (self->txn_state) {
196         case TXN_NONE:
197                 // We can safely report success when there is no transaction
198                 ok = 1;
199                 break;
200         case TXN_ACTIVE:
201         case TXN_SUCCESS:
202                 // The transaction is in full progress; attempt to commit it
203                 ok = ok && (0 == self->txn->commit (self->txn, 0));
204                 self->txn = NULL;
205                 self->txn_state = TXN_NONE;
206                 break;
207         case TXN_ABORT:
208                 // Preparation fails, then the call should have been _rollback()
209                 assert (self->txn_state != TXN_ABORT);
210                 ok = ok && 0;
211                 // Since there actually is a transaction, roll it back
212                 ok = ok && (0 == self->txn->abort (self->txn));
213                 self->txn = NULL;
214                 self->txn_state = TXN_NONE;
215                 break;
216         }
217         return ok;
218 }
219
220 void pulleyback_rollback (void *pbh) {
221         struct pulleyback_tlspool *self = (struct pulleyback_tlspool *) pbh;
222         int ok = 1;
223         switch (self->txn_state) {
224         case TXN_NONE:
225                 // In lieu of a transaction, rollback is a trivial matter
226                 ok = ok && 1;
227                 break;
228         case TXN_ABORT:
229         case TXN_SUCCESS:
230                 // Preparation of the transaction has been done,
231                 // so process as we would an active transaction
232         case TXN_ACTIVE:
233                 // When there actually is a transaction, roll it back
234                 ok = ok && (0 == self->txn->abort (self->txn));
235                 self->txn = NULL;
236                 self->txn_state = TXN_NONE;
237                 break;
238         }
239 }
240
241 int pulleyback_collaborate (void *pbh1, void *pbh2) {
242         struct pulleyback_tlspool *data1 = (struct pulleyback_tlspool *) pbh1;
243         struct pulleyback_tlspool *data2 = (struct pulleyback_tlspool *) pbh2;
244         int ok = 1;
245         ok = ok && (0 == strcmp (data1->db_env, data2->db_env));
246         //TODO// May need to copy self->env and reopen self->db in it
247         if (!ok) {
248                 ;  // Do not continue 
249         } else if (data1->txn == NULL) {
250                 if (data2->txn == NULL) {
251                         // Neither has a transaction, so must create it
252                         ok = ok && have_txn (data2);
253                 }
254                 if (ok) {
255                         data1->txn = data2->txn;
256                 }
257         } else if (data2->txn == NULL) {
258                 data2->txn = data1->txn;
259         } else {
260                 ok = (data1->txn == data2->txn);
261         }
262         return ok;
263 }
264