@@ -85,12 +85,6 @@ function tube.new(space, on_task_change, opts)
85
85
86
86
local space_ready_buffer_name = space .name .. " _ready_buffer"
87
87
local space_ready_buffer = box .space [space_ready_buffer_name ]
88
- -- Feature implemented only for memtx engine for now.
89
- -- https://github.com/tarantool/queue/issues/230.
90
- if opts .storage_mode == tube .STORAGE_MODE_READY_BUFFER and opts .engine == ' vinyl' then
91
- error (string.format (' "%s" storage mode cannot be used with vinyl engine' ,
92
- tube .STORAGE_MODE_READY_BUFFER ))
93
- end
94
88
95
89
local ready_space_mode = (opts .storage_mode == tube .STORAGE_MODE_READY_BUFFER )
96
90
if ready_space_mode then
@@ -167,6 +161,10 @@ local function commit()
167
161
box .commit ()
168
162
end
169
163
164
+ local function rollback ()
165
+ box .rollback ()
166
+ end
167
+
170
168
local function empty ()
171
169
end
172
170
@@ -179,14 +177,31 @@ local function begin_if_not_in_txn()
179
177
180
178
if not box .is_in_txn () then
181
179
box .begin (transaction_opts )
182
- return commit
180
+ return commit , rollback
183
181
else
184
- return empty
182
+ return empty , empty
183
+ end
184
+ end
185
+
186
+ -- Try commiting operations until success. This is required for 'vinyl' engine.
187
+ -- In case of a transaction conflict for 'vinyl' we need to retry an entire
188
+ -- transaction.
189
+ local function try_commit_several_times (func , ...)
190
+ local ok = false
191
+ local ret
192
+ while not ok do
193
+ local commit_func , rollback_func = begin_if_not_in_txn ()
194
+ ok , ret = pcall (func , commit_func , ... )
195
+ if ok then
196
+ return ret
197
+ end
198
+ rollback_func ()
199
+ require (' fiber' ).yield ()
185
200
end
186
201
end
187
202
188
203
-- put task in space
189
- function method . put (self , data , opts )
204
+ local function put (self , data , opts , commit_func )
190
205
-- Taking the minimum is an implicit transactions, so it is
191
206
-- always done with 'read-confirmed' mvcc isolation level.
192
207
-- It can lead to errors when trying to make parallel 'take' calls with mvcc enabled.
@@ -197,8 +212,6 @@ function method.put(self, data, opts)
197
212
-- since it will open nested transactions.
198
213
-- See https://github.com/tarantool/queue/issues/207
199
214
-- See https://www.tarantool.io/ru/doc/latest/concepts/atomic/txn_mode_mvcc/
200
- local commit_func = begin_if_not_in_txn ()
201
-
202
215
local max = self .space .index .task_id :max ()
203
216
204
217
local id = max and max [1 ] + 1 or 0
@@ -213,11 +226,18 @@ function method.put(self, data, opts)
213
226
return task
214
227
end
215
228
229
+ -- put task in space
230
+ function method .put (self , data , opts )
231
+ local commit_body = function (commit_func )
232
+ return put (self , data , opts , commit_func )
233
+ end
234
+
235
+ return try_commit_several_times (commit_body )
236
+ end
237
+
216
238
-- Take the first task form the ready_buffer.
217
- local function take_ready (self )
239
+ local function take_ready (self , commit_func )
218
240
while true do
219
- local commit_func = begin_if_not_in_txn ()
220
-
221
241
local task_ready = self .space_ready_buffer .index .task_id :min ()
222
242
if task_ready == nil then
223
243
commit_func ()
@@ -247,45 +267,57 @@ local function take_ready(self)
247
267
end
248
268
end
249
269
250
- local function take (self )
251
- for s , task in self .space .index .status :pairs (state .READY ,
252
- { iterator = ' GE' }) do
253
- if task [2 ] ~= state .READY then
254
- break
255
- end
256
- -- Taking the minimum is an implicit transactions, so it is
257
- -- always done with 'read-confirmed' mvcc isolation level.
258
- -- It can lead to errors when trying to make parallel 'take' calls with mvcc enabled.
259
- -- It is hapenning because 'min' for several takes in parallel will be the same since
260
- -- read confirmed isolation level makes visible all transactions that finished the commit.
261
- -- To fix it we wrap it with box.begin/commit and set right isolation level.
262
- -- Current fix does not resolve that bug in situations when we already are in transaction
263
- -- since it will open nested transactions.
264
- -- See https://github.com/tarantool/queue/issues/207
265
- -- See https://www.tarantool.io/ru/doc/latest/concepts/atomic/txn_mode_mvcc/
266
- local commit_func = begin_if_not_in_txn ()
267
- local taken = self .space .index .utube :min {state .TAKEN , task [3 ]}
268
- local take_complete = false
270
+ local function take_step (self , task , commit_func )
271
+ -- Taking the minimum is an implicit transactions, so it is
272
+ -- always done with 'read-confirmed' mvcc isolation level.
273
+ -- It can lead to errors when trying to make parallel 'take' calls with mvcc enabled.
274
+ -- It is hapenning because 'min' for several takes in parallel will be the same since
275
+ -- read confirmed isolation level makes visible all transactions that finished the commit.
276
+ -- To fix it we wrap it with box.begin/commit and set right isolation level.
277
+ -- Current fix does not resolve that bug in situations when we already are in transaction
278
+ -- since it will open nested transactions.
279
+ -- See https://github.com/tarantool/queue/issues/207
280
+ -- See https://www.tarantool.io/ru/doc/latest/concepts/atomic/txn_mode_mvcc/
281
+ local taken = self .space .index .utube :min {state .TAKEN , task [3 ]}
282
+ local take_complete = false
269
283
270
- if taken == nil or taken [2 ] ~= state .TAKEN then
271
- task = self .space :update (task [1 ], { { ' =' , 2 , state .TAKEN } })
272
- take_complete = true
273
- end
284
+ if taken == nil or taken [2 ] ~= state .TAKEN then
285
+ task = self .space :update (task [1 ], { { ' =' , 2 , state .TAKEN } })
286
+ take_complete = true
287
+ end
274
288
275
- commit_func ()
276
- if take_complete then
277
- self .on_task_change (task , ' take' )
278
- return task
279
- end
289
+ commit_func ()
290
+ if take_complete then
291
+ self .on_task_change (task , ' take' )
292
+ return task
280
293
end
281
294
end
282
295
283
296
-- take task
284
297
function method .take (self )
285
298
if self .ready_space_mode then
286
- return take_ready (self )
299
+ local commit_body = function (commit_func )
300
+ return take_ready (self , commit_func )
301
+ end
302
+
303
+ return try_commit_several_times (commit_body )
304
+ end
305
+
306
+ for _ , task in self .space .index .status :pairs (state .READY ,
307
+ { iterator = ' GE' }) do
308
+ if task [2 ] ~= state .READY then
309
+ break
310
+ end
311
+
312
+ local commit_body = function (commit_func )
313
+ return take_step (self , task , commit_func )
314
+ end
315
+
316
+ local ret = try_commit_several_times (commit_body )
317
+ if ret ~= nil then
318
+ return ret
319
+ end
287
320
end
288
- return take (self )
289
321
end
290
322
291
323
-- touch task
@@ -300,9 +332,7 @@ local function delete_ready(self, id, utube)
300
332
end
301
333
302
334
-- delete task
303
- function method .delete (self , id )
304
- local commit_func = begin_if_not_in_txn ()
305
-
335
+ local function delete (self , id , commit_func )
306
336
local task = self .space :get (id )
307
337
self .space :delete (id )
308
338
if task ~= nil then
@@ -331,10 +361,17 @@ function method.delete(self, id)
331
361
return task
332
362
end
333
363
334
- -- release task
335
- function method .release (self , id , opts )
336
- local commit_func = begin_if_not_in_txn ()
364
+ -- delete task
365
+ function method .delete (self , id )
366
+ local commit_body = function (commit_func )
367
+ return delete (self , id , commit_func )
368
+ end
369
+
370
+ return try_commit_several_times (commit_body )
371
+ end
337
372
373
+ -- release task
374
+ local function release (self , id , opts , commit_func )
338
375
local task = self .space :update (id , {{ ' =' , 2 , state .READY }})
339
376
if task ~= nil then
340
377
if self .ready_space_mode then
@@ -357,10 +394,17 @@ function method.release(self, id, opts)
357
394
return task
358
395
end
359
396
360
- -- bury task
361
- function method .bury (self , id )
362
- local commit_func = begin_if_not_in_txn ()
397
+ -- release task
398
+ function method .release (self , id , opts )
399
+ local commit_body = function (commit_func )
400
+ return release (self , id , opts , commit_func )
401
+ end
363
402
403
+ return try_commit_several_times (commit_body )
404
+ end
405
+
406
+ -- bury task
407
+ local function bury (self , id , commit_func )
364
408
local current_task = self .space :get {id }
365
409
local task = self .space :update (id , {{ ' =' , 2 , state .BURIED }})
366
410
if task ~= nil then
@@ -390,35 +434,54 @@ function method.bury(self, id)
390
434
return task
391
435
end
392
436
393
- -- unbury several tasks
394
- function method .kick (self , count )
395
- for i = 1 , count do
396
- local commit_func = begin_if_not_in_txn ()
437
+ -- bury task
438
+ function method .bury (self , id )
439
+ local commit_body = function (commit_func )
440
+ return bury (self , id , commit_func )
441
+ end
397
442
398
- local task = self .space .index .status :min { state .BURIED }
399
- if task == nil then
400
- return i - 1
401
- end
402
- if task [2 ] ~= state .BURIED then
403
- return i - 1
404
- end
443
+ return try_commit_several_times (commit_body )
444
+ end
405
445
406
- task = self .space :update (task [1 ], {{ ' =' , 2 , state .READY }})
407
- if self .ready_space_mode then
408
- local prev_task = self .space_ready_buffer .index .utube :get {task [3 ]}
409
- if prev_task ~= nil then
410
- if prev_task [1 ] > task [1 ] then
411
- self .space_ready_buffer :delete (prev_task [1 ])
412
- self .space_ready_buffer :insert ({task [1 ], task [2 ]})
413
- end
414
- else
415
- put_ready (self , task [3 ])
446
+ -- unbury several tasks
447
+ local function kick_step (self , id , commit_func )
448
+ local task = self .space .index .status :min { state .BURIED }
449
+ if task == nil then
450
+ return id - 1
451
+ end
452
+ if task [2 ] ~= state .BURIED then
453
+ return id - 1
454
+ end
455
+
456
+ task = self .space :update (task [1 ], {{ ' =' , 2 , state .READY }})
457
+ if self .ready_space_mode then
458
+ local prev_task = self .space_ready_buffer .index .utube :get {task [3 ]}
459
+ if prev_task ~= nil then
460
+ if prev_task [1 ] > task [1 ] then
461
+ self .space_ready_buffer :delete (prev_task [1 ])
462
+ self .space_ready_buffer :insert ({task [1 ], task [2 ]})
416
463
end
464
+ else
465
+ put_ready (self , task [3 ])
417
466
end
467
+ end
418
468
419
- commit_func ()
469
+ commit_func ()
470
+
471
+ self .on_task_change (task , ' kick' )
472
+ end
420
473
421
- self .on_task_change (task , ' kick' )
474
+ -- unbury several tasks
475
+ function method .kick (self , count )
476
+ for i = 1 , count do
477
+ local commit_body = function (commit_func )
478
+ return kick_step (self , i , commit_func )
479
+ end
480
+
481
+ local ret = try_commit_several_times (commit_body )
482
+ if ret ~= nil then
483
+ return ret
484
+ end
422
485
end
423
486
return count
424
487
end
0 commit comments