Skip to content

Commit a76eb6e

Browse files
EvgenyMekhanikKorablev77
authored andcommitted
txn: implement timeout for transactions
Client code errors or manual mistakes can create transactions that are never closed. Such transaction will work as a memory leak. Implement timeout for transactions after which they are rolled back. Part of #6177 @TarantoolBot document Title: ability to set timeout for transactions was implemented Previously transactions are never closed until commit or rollback. Timeout for transactions was implemented after which they are rolled back. For these purpose, in `box.begin` the optional table parameter was added. For example if user want to start transaction with timeout 3s, he should use `box.begin({timeout = 3})`. Also was implement new configuration option `box.cfg.txn_timeout` which determines timeout for transactions, for which the timeout was not explicitly set. By default this option is set to infinity (TIMEOUT_INFINITY = 365 * 100 * 86400). Also in C API was added new function to set timeout for transaction - 'box_txn_set_timeout'.
1 parent 9c5d3ed commit a76eb6e

File tree

16 files changed

+470
-2
lines changed

16 files changed

+470
-2
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
## feature/core
2+
3+
* Implemented a timeout for transactions after
4+
which they are rolled back (gh-6177).
5+
Implemented new C API function 'box_txn_set_timeout'
6+
to set timeout for transaction.

extra/exports

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ box_txn_id
100100
box_txn_rollback
101101
box_txn_rollback_to_savepoint
102102
box_txn_savepoint
103+
box_txn_set_timeout
103104
box_update
104105
box_upsert
105106
clock_monotonic

src/box/box.cc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ struct rmean *rmean_box;
9191

9292
double on_shutdown_trigger_timeout = 3.0;
9393

94+
double txn_timeout_default;
95+
9496
struct rlist box_on_shutdown_trigger_list =
9597
RLIST_HEAD_INITIALIZER(box_on_shutdown_trigger_list);
9698

@@ -1141,6 +1143,18 @@ box_check_iproto_options(void)
11411143
return 0;
11421144
}
11431145

1146+
static double
1147+
box_check_txn_timeout(void)
1148+
{
1149+
double timeout = cfg_getd_default("txn_timeout", TIMEOUT_INFINITY);
1150+
if (timeout <= 0) {
1151+
diag_set(ClientError, ER_CFG, "txn_timeout",
1152+
"the value must be greather than 0");
1153+
return -1;
1154+
}
1155+
return timeout;
1156+
}
1157+
11441158
void
11451159
box_check_config(void)
11461160
{
@@ -1183,6 +1197,8 @@ box_check_config(void)
11831197
diag_raise();
11841198
if (box_check_sql_cache_size(cfg_geti("sql_cache_size")) != 0)
11851199
diag_raise();
1200+
if (box_check_txn_timeout() < 0)
1201+
diag_raise();
11861202
}
11871203

11881204
int
@@ -2036,6 +2052,16 @@ box_set_crash(void)
20362052
return 0;
20372053
}
20382054

2055+
int
2056+
box_set_txn_timeout(void)
2057+
{
2058+
double timeout = box_check_txn_timeout();
2059+
if (timeout < 0)
2060+
return -1;
2061+
txn_timeout_default = timeout;
2062+
return 0;
2063+
}
2064+
20392065
/* }}} configuration bindings */
20402066

20412067
/**

src/box/box.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ extern double on_shutdown_trigger_timeout;
7070
/** Invoked on box shutdown. */
7171
extern struct rlist box_on_shutdown_trigger_list;
7272

73+
/**
74+
* Timeout during which the transaction must complete,
75+
* otherwise it will be rolled back.
76+
*/
77+
extern double txn_timeout_default;
78+
7379
/*
7480
* Initialize box library
7581
* @throws C++ exception
@@ -264,6 +270,7 @@ void box_set_replication_skip_conflict(void);
264270
void box_set_replication_anon(void);
265271
void box_set_net_msg_max(void);
266272
int box_set_crash(void);
273+
int box_set_txn_timeout(void);
267274

268275
int
269276
box_set_prepared_stmt_cache_size(void);

src/box/errcode.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,8 @@ struct errcode_record {
283283
/*228 */_(ER_SYNC_QUEUE_FOREIGN, "The synchronous transaction queue belongs to other instance with id %u")\
284284
/*226 */_(ER_UNABLE_TO_PROCESS_IN_STREAM, "Unable to process %s request in stream") \
285285
/*227 */_(ER_UNABLE_TO_PROCESS_OUT_OF_STREAM, "Unable to process %s request out of stream") \
286+
/*228 */_(ER_TRANSACTION_TIMEOUT, "Transaction has been aborted by timeout") \
287+
/*229 */_(ER_ACTIVE_TIMER, "Operation is not permitted if timer is already running") \
286288

287289
/*
288290
* !IMPORTANT! Please follow instructions at start of the file

src/box/lua/cfg.cc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,14 @@ lbox_cfg_set_crash(struct lua_State *L)
396396
return 0;
397397
}
398398

399+
static int
400+
lbox_cfg_set_txn_timeout(struct lua_State *L)
401+
{
402+
if (box_set_txn_timeout() != 0)
403+
luaT_error(L);
404+
return 0;
405+
}
406+
399407
void
400408
box_lua_cfg_init(struct lua_State *L)
401409
{
@@ -435,6 +443,7 @@ box_lua_cfg_init(struct lua_State *L)
435443
{"cfg_set_net_msg_max", lbox_cfg_set_net_msg_max},
436444
{"cfg_set_sql_cache_size", lbox_set_prepared_stmt_cache_size},
437445
{"cfg_set_crash", lbox_cfg_set_crash},
446+
{"cfg_set_txn_timeout", lbox_cfg_set_txn_timeout},
438447
{NULL, NULL}
439448
};
440449

src/box/lua/load_cfg.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ local default_cfg = {
109109
feedback_interval = 3600,
110110
net_msg_max = 768,
111111
sql_cache_size = 5 * 1024 * 1024,
112+
txn_timeout = 365 * 100 * 86400,
112113
}
113114

114115
-- cfg variables which are covered by modules
@@ -215,6 +216,7 @@ local template_cfg = {
215216
feedback_interval = ifdef_feedback('number'),
216217
net_msg_max = 'number',
217218
sql_cache_size = 'number',
219+
txn_timeout = 'number',
218220
}
219221

220222
local function normalize_uri(port)
@@ -335,6 +337,7 @@ local dynamic_cfg = {
335337
replicaset_uuid = check_replicaset_uuid,
336338
net_msg_max = private.cfg_set_net_msg_max,
337339
sql_cache_size = private.cfg_set_sql_cache_size,
340+
txn_timeout = private.cfg_set_txn_timeout,
338341
}
339342

340343
-- dynamically settable options, which should be reverted in case

src/box/lua/schema.lua

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ ffi.cdef[[
7171
box_txn_id();
7272
int
7373
box_txn_begin();
74+
int
75+
box_txn_set_timeout(double timeout);
7476
/** \endcond public */
7577
/** \cond public */
7678
int
@@ -328,10 +330,22 @@ local function feedback_save_event(event)
328330
end
329331
end
330332

331-
box.begin = function()
333+
box.begin = function(options)
334+
local timeout
335+
if options then
336+
check_param(options, 'options', 'table')
337+
timeout = options.timeout
338+
if timeout and (type(timeout) ~= "number" or timeout <= 0) then
339+
box.error(box.error.ILLEGAL_PARAMS,
340+
"timeout must be a number greater than 0")
341+
end
342+
end
332343
if builtin.box_txn_begin() == -1 then
333344
box.error()
334345
end
346+
if timeout then
347+
assert(builtin.box_txn_set_timeout(timeout) == 0)
348+
end
335349
end
336350

337351
box.is_in_txn = builtin.box_txn

src/box/txn.c

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include "xrow.h"
3939
#include "errinj.h"
4040
#include "iproto_constants.h"
41+
#include "box.h"
4142

4243
double too_long_threshold;
4344

@@ -60,20 +61,31 @@ txn_flags_to_error_code(struct txn *txn)
6061
return ER_TRANSACTION_CONFLICT;
6162
else if (txn_has_flag(txn, TXN_IS_ABORTED_BY_YIELD))
6263
return ER_TRANSACTION_YIELD;
64+
else if (txn_has_flag(txn, TXN_IS_ABORTED_BY_TIMEOUT))
65+
return ER_TRANSACTION_TIMEOUT;
6366
return ER_UNKNOWN;
6467
}
6568

6669
static inline int
6770
txn_check_can_continue(struct txn *txn)
6871
{
69-
enum txn_flag flags = TXN_IS_CONFLICTED | TXN_IS_ABORTED_BY_YIELD;
72+
enum txn_flag flags =
73+
TXN_IS_CONFLICTED | TXN_IS_ABORTED_BY_YIELD |
74+
TXN_IS_ABORTED_BY_TIMEOUT;
7075
if (txn_has_any_of_flags(txn, flags)) {
7176
diag_set(ClientError, txn_flags_to_error_code(txn));
7277
return -1;
7378
}
7479
return 0;
7580
}
7681

82+
static inline void
83+
txn_set_timeout(struct txn *txn, double timeout)
84+
{
85+
assert(timeout > 0);
86+
txn->timeout = timeout;
87+
}
88+
7789
static int
7890
txn_add_redo(struct txn *txn, struct txn_stmt *stmt, struct request *request)
7991
{
@@ -237,6 +249,8 @@ txn_new(void)
237249
inline static void
238250
txn_free(struct txn *txn)
239251
{
252+
if (txn->rollback_timer != NULL)
253+
ev_timer_stop(loop(), txn->rollback_timer);
240254
memtx_tx_clean_txn(txn);
241255
struct tx_read_tracker *tracker, *tmp;
242256
rlist_foreach_entry_safe(tracker, &txn->read_set,
@@ -327,6 +341,8 @@ txn_begin(void)
327341
rlist_create(&txn->savepoints);
328342
memtx_tx_register_tx(txn);
329343
txn->fiber = NULL;
344+
txn->timeout = TIMEOUT_INFINITY;
345+
txn->rollback_timer = NULL;
330346
fiber_set_txn(fiber(), txn);
331347
trigger_create(&txn->fiber_on_yield, txn_on_yield, NULL, NULL);
332348
trigger_add(&fiber()->on_yield, &txn->fiber_on_yield);
@@ -721,6 +737,11 @@ txn_prepare(struct txn *txn)
721737

722738
if (txn_check_can_continue(txn) != 0)
723739
return -1;
740+
741+
if (txn->rollback_timer != NULL) {
742+
ev_timer_stop(loop(), txn->rollback_timer);
743+
txn->rollback_timer = NULL;
744+
}
724745
/*
725746
* If transaction has been started in SQL, deferred
726747
* foreign key constraints must not be violated.
@@ -1059,6 +1080,7 @@ box_txn_begin(void)
10591080
}
10601081
if (txn_begin() == NULL)
10611082
return -1;
1083+
txn_set_timeout(in_txn(), txn_timeout_default);
10621084
return 0;
10631085
}
10641086

@@ -1118,6 +1140,27 @@ box_txn_alloc(size_t size)
11181140
alignof(union natural_align));
11191141
}
11201142

1143+
int
1144+
box_txn_set_timeout(double timeout)
1145+
{
1146+
if (timeout <= 0) {
1147+
diag_set(ClientError, ER_ILLEGAL_PARAMS,
1148+
"timeout must be a number greater than 0");
1149+
return -1;
1150+
}
1151+
struct txn *txn = in_txn();
1152+
if (txn == NULL) {
1153+
diag_set(ClientError, ER_NO_TRANSACTION);
1154+
return -1;
1155+
}
1156+
if (txn->rollback_timer != NULL) {
1157+
diag_set(ClientError, ER_ACTIVE_TIMER);
1158+
return -1;
1159+
}
1160+
txn_set_timeout(txn, timeout);
1161+
return 0;
1162+
}
1163+
11211164
struct txn_savepoint *
11221165
txn_savepoint_new(struct txn *txn, const char *name)
11231166
{
@@ -1238,6 +1281,16 @@ txn_on_stop(struct trigger *trigger, void *event)
12381281
return 0;
12391282
}
12401283

1284+
static void
1285+
txn_on_timeout(ev_loop *loop, ev_timer *watcher, int revents)
1286+
{
1287+
(void) loop;
1288+
(void) revents;
1289+
struct txn *txn = (struct txn *)watcher->data;
1290+
txn_rollback_to_svp(txn, NULL);
1291+
txn_set_flags(txn, TXN_IS_ABORTED_BY_TIMEOUT);
1292+
}
1293+
12411294
/**
12421295
* Memtx yield-in-transaction trigger callback.
12431296
*
@@ -1267,6 +1320,19 @@ txn_on_yield(struct trigger *trigger, void *event)
12671320
txn_rollback_to_svp(txn, NULL);
12681321
txn_set_flags(txn, TXN_IS_ABORTED_BY_YIELD);
12691322
say_warn("Transaction has been aborted by a fiber yield");
1323+
return 0;
1324+
}
1325+
if (txn->rollback_timer == NULL && txn->timeout != TIMEOUT_INFINITY) {
1326+
int size;
1327+
txn->rollback_timer = region_alloc_object(&txn->region,
1328+
struct ev_timer,
1329+
&size);
1330+
if (txn->rollback_timer == NULL)
1331+
panic("Out of memory on creation of rollback timer");
1332+
ev_timer_init(txn->rollback_timer, txn_on_timeout,
1333+
txn->timeout, 0);
1334+
txn->rollback_timer->data = txn;
1335+
ev_timer_start(loop(), txn->rollback_timer);
12701336
}
12711337
return 0;
12721338
}

src/box/txn.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ enum txn_flag {
9696
* committed due to conflict.
9797
*/
9898
TXN_IS_CONFLICTED = 0x80,
99+
/*
100+
* Transaction has been aborted by timeout so should be
101+
* rolled back at commit.
102+
*/
103+
TXN_IS_ABORTED_BY_TIMEOUT = 0x100,
99104
};
100105

101106
enum {
@@ -433,6 +438,13 @@ struct txn {
433438
struct rlist in_all_txs;
434439
/** True in case transaction provides any DDL change. */
435440
bool is_schema_changed;
441+
/** Timeout for transaction, or TIMEOUT_INFINITY if not set. */
442+
double timeout;
443+
/**
444+
* Timer that is alarmed if the transaction did not have time
445+
* to complete within the timeout specified when it was created.
446+
*/
447+
struct ev_timer *rollback_timer;
436448
};
437449

438450
static inline bool
@@ -831,6 +843,18 @@ box_txn_rollback(void);
831843
API_EXPORT void *
832844
box_txn_alloc(size_t size);
833845

846+
/**
847+
* Set @a timeout for transaction, when it expires, transaction
848+
* will be rolled back.
849+
*
850+
* @retval 0 if success
851+
* @retval -1 if timeout is less than or equal to 0, there is
852+
* no current transaction or rollback timer for
853+
* current transaction is already started.
854+
*/
855+
API_EXPORT int
856+
box_txn_set_timeout(double timeout);
857+
834858
/** \endcond public */
835859

836860
typedef struct txn_savepoint box_txn_savepoint_t;

test/app-tap/init_script.result

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ slab_alloc_granularity:8
4444
sql_cache_size:5242880
4545
strip_core:true
4646
too_long_threshold:0.5
47+
txn_timeout:3153600000
4748
vinyl_bloom_fpr:0.05
4849
vinyl_cache:134217728
4950
vinyl_dir:.

test/box/admin.result

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ cfg_filter(box.cfg)
109109
- true
110110
- - too_long_threshold
111111
- 0.5
112+
- - txn_timeout
113+
- 3153600000
112114
- - vinyl_bloom_fpr
113115
- 0.05
114116
- - vinyl_cache

0 commit comments

Comments
 (0)