1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2010 Couchbase, Inc
4  *
5  *   Licensed under the Apache License, Version 2.0 (the "License");
6  *   you may not use this file except in compliance with the License.
7  *   You may obtain a copy of the License at
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *   Unless required by applicable law or agreed to in writing, software
12  *   distributed under the License is distributed on an "AS IS" BASIS,
13  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *   See the License for the specific language governing permissions and
15  *   limitations under the License.
16  */
17 
18 #include <stdlib.h>
19 #include <string.h>
20 #include <stdint.h>
21 
22 #include "libforestdb/forestdb.h"
23 #include "fdb_internal.h"
24 #include "internal_types.h"
25 #include "filemgr.h"
26 #include "common.h"
27 #include "list.h"
28 #include "wal.h"
29 #include "memleak.h"
30 
31 // Global static variables
32 static atomic_uint64_t transaction_id(0); // unique & monotonically increasing
33 
34 LIBFDB_API
fdb_begin_transaction(fdb_file_handle *fhandle, fdb_isolation_level_t isolation_level)35 fdb_status fdb_begin_transaction(fdb_file_handle *fhandle,
36                                  fdb_isolation_level_t isolation_level)
37 {
38     if (!fhandle || !fhandle->root) {
39         return FDB_RESULT_INVALID_HANDLE;
40     }
41 
42     file_status_t fstatus;
43     fdb_kvs_handle *handle = fhandle->root;
44     struct filemgr *file;
45 
46     if (handle->txn) {
47         // transaction already exists
48         return FDB_RESULT_TRANSACTION_FAIL;
49     }
50     if (handle->kvs) {
51         if (handle->kvs->type == KVS_SUB) {
52             // deny transaction on sub handle
53             return FDB_RESULT_INVALID_HANDLE;
54         }
55     }
56 
57     if (!atomic_cas_uint8_t(&handle->handle_busy, 0, 1)) {
58         return FDB_RESULT_HANDLE_BUSY;
59     }
60 
61     do { // repeat until file status is not REMOVED_PENDING
62         fdb_check_file_reopen(handle, NULL);
63         filemgr_mutex_lock(handle->file);
64         fdb_sync_db_header(handle);
65 
66         if (filemgr_is_rollback_on(handle->file)) {
67             // deny beginning transaction during rollback
68             filemgr_mutex_unlock(handle->file);
69             atomic_cas_uint8_t(&handle->handle_busy, 1, 0);
70             return FDB_RESULT_FAIL_BY_ROLLBACK;
71         }
72 
73         file = handle->file;
74         fstatus = filemgr_get_file_status(file);
75         if (fstatus == FILE_REMOVED_PENDING) {
76             // we must not create transaction on this file
77             // file status was changed by other thread .. start over
78             filemgr_mutex_unlock(file);
79         }
80     } while (fstatus == FILE_REMOVED_PENDING);
81 
82     handle->txn = (fdb_txn*)malloc(sizeof(fdb_txn));
83     handle->txn->wrapper = (struct wal_txn_wrapper *)
84                            malloc(sizeof(struct wal_txn_wrapper));
85     handle->txn->wrapper->txn = handle->txn;
86     handle->txn->handle = handle;
87     handle->txn->txn_id = ++transaction_id;
88 
89     if (filemgr_get_file_status(handle->file) != FILE_COMPACT_OLD) {
90         // keep previous header's BID
91         handle->txn->prev_hdr_bid = handle->last_hdr_bid;
92     } else {
93         // if file status is COMPACT_OLD,
94         // then this transaction will work on new file, and
95         // there is no previous header until the compaction is done.
96         handle->txn->prev_hdr_bid = BLK_NOT_FOUND;
97     }
98     handle->txn->prev_revnum = handle->cur_header_revnum;
99     handle->txn->items = (struct list *)malloc(sizeof(struct list));
100     handle->txn->isolation = isolation_level;
101     list_init(handle->txn->items);
102     wal_add_transaction(file, handle->txn);
103 
104     filemgr_mutex_unlock(file);
105 
106     atomic_cas_uint8_t(&handle->handle_busy, 1, 0);
107     return FDB_RESULT_SUCCESS;
108 }
109 
110 LIBFDB_API
fdb_abort_transaction(fdb_file_handle *fhandle)111 fdb_status fdb_abort_transaction(fdb_file_handle *fhandle)
112 {
113     if (!fhandle) {
114         return FDB_RESULT_INVALID_HANDLE;
115     }
116 
117     return _fdb_abort_transaction(fhandle->root);
118 }
119 
_fdb_abort_transaction(fdb_kvs_handle *handle)120 fdb_status _fdb_abort_transaction(fdb_kvs_handle *handle)
121 {
122     if (!handle) {
123         return FDB_RESULT_INVALID_HANDLE;
124     }
125 
126     file_status_t fstatus;
127     struct filemgr *file;
128 
129     if (handle->txn == NULL) {
130         // there is no transaction started
131         return FDB_RESULT_TRANSACTION_FAIL;
132     }
133     if (handle->kvs) {
134         if (handle->kvs->type == KVS_SUB) {
135             // deny transaction on sub handle
136             return FDB_RESULT_INVALID_HANDLE;
137         }
138     }
139 
140     if (!atomic_cas_uint8_t(&handle->handle_busy, 0, 1)) {
141         return FDB_RESULT_HANDLE_BUSY;
142     }
143 
144     do { // repeat until file status is not REMOVED_PENDING
145         fdb_check_file_reopen(handle, NULL);
146 
147         file = handle->file;
148         filemgr_mutex_lock(file);
149         fdb_sync_db_header(handle);
150 
151         fstatus = filemgr_get_file_status(file);
152         if (fstatus == FILE_REMOVED_PENDING) {
153             // we must not abort transaction on this file
154             // file status was changed by other thread .. start over
155             filemgr_mutex_unlock(file);
156         }
157     } while (fstatus == FILE_REMOVED_PENDING);
158 
159     wal_discard(file, handle->txn);
160     wal_remove_transaction(file, handle->txn);
161 
162     free(handle->txn->items);
163     free(handle->txn->wrapper);
164     free(handle->txn);
165     handle->txn = NULL;
166 
167     filemgr_mutex_unlock(file);
168 
169     atomic_cas_uint8_t(&handle->handle_busy, 1, 0);
170     return FDB_RESULT_SUCCESS;
171 }
172 
173 LIBFDB_API
fdb_end_transaction(fdb_file_handle *fhandle, fdb_commit_opt_t opt)174 fdb_status fdb_end_transaction(fdb_file_handle *fhandle,
175                                fdb_commit_opt_t opt)
176 {
177     if (!fhandle || !fhandle->root) {
178         return FDB_RESULT_INVALID_HANDLE;
179     }
180 
181     file_status_t fstatus;
182     fdb_kvs_handle *handle = fhandle->root;
183     struct filemgr *file;
184 
185     if (handle->txn == NULL) {
186         // there is no transaction started
187         return FDB_RESULT_TRANSACTION_FAIL;
188     }
189     if (handle->kvs) {
190         if (handle->kvs->type == KVS_SUB) {
191             // deny transaction on sub handle
192             return FDB_RESULT_INVALID_HANDLE;
193         }
194     }
195 
196     fdb_status fs = FDB_RESULT_SUCCESS;
197     if (list_begin(handle->txn->items)) {
198         fs = _fdb_commit(handle, opt,
199                        !(handle->config.durability_opt & FDB_DRB_ASYNC));
200     }
201 
202     if (fs == FDB_RESULT_SUCCESS) {
203 
204         do { // repeat until file status is not REMOVED_PENDING
205             fdb_check_file_reopen(handle, NULL);
206 
207             file = handle->file;
208             filemgr_mutex_lock(file);
209             fdb_sync_db_header(handle);
210 
211             fstatus = filemgr_get_file_status(file);
212             if (fstatus == FILE_REMOVED_PENDING) {
213                 // we must not commit transaction on this file
214                 // file status was changed by other thread .. start over
215                 filemgr_mutex_unlock(file);
216             }
217         } while (fstatus == FILE_REMOVED_PENDING);
218 
219         wal_remove_transaction(file, handle->txn);
220 
221         free(handle->txn->items);
222         free(handle->txn->wrapper);
223         free(handle->txn);
224         handle->txn = NULL;
225 
226         filemgr_mutex_unlock(file);
227     }
228 
229     return fs;
230 }
231