Merge lp:~laurynas-biveinis/percona-server/multiple-xa-ses-5.6 into lp:percona-server/5.6

Proposed by Laurynas Biveinis
Status: Merged
Approved by: Stewart Smith
Approved revision: 520
Merged at revision: 524
Proposed branch: lp:~laurynas-biveinis/percona-server/multiple-xa-ses-5.6
Merge into: lp:percona-server/5.6
Diff against target: 519 lines (+287/-38)
6 files modified
Percona-Server/sql/handler.cc (+1/-1)
Percona-Server/sql/log.cc (+79/-35)
Percona-Server/sql/log.h (+4/-1)
Percona-Server/sql/mysqld.cc (+1/-1)
Percona-Server/unittest/gunit/CMakeLists.txt (+1/-0)
Percona-Server/unittest/gunit/tc_log_mmap-t.cc (+201/-0)
To merge this branch: bzr merge lp:~laurynas-biveinis/percona-server/multiple-xa-ses-5.6
Reviewer Review Type Date Requested Status
Stewart Smith (community) Approve
George Ormond Lorch III g2 Pending
Review via email: mp+199470@code.launchpad.net

This proposal supersedes a proposal from 2013-11-29.

Description of the change

2nd MP:

Took MariaDB copyrights to log.cc, rebased on the current trunk.
http://jenkins.percona.com/job/percona-server-5.6-param/459/

1st MP:

Fixes for bug 1255549 and bug 1255551, making it possible to have 2 XA storage engines installed and running.

jenkins.percona.com/job/percona-server-5.6-param/423/

Fix bug 1255549 (Crash on startup when XA support functions activated
by a second engine) / http://bugs.mysql.com/bug.php?id=47134.

The fix is a combination of:

- disabling of code in ha_recover() that asserts if more than one XA
  SE is installed;

- MariaDB revisions 2502.102.122, 2502.102.167 (minus PBXT testcase),
  2502.565.20, 2502.102.215 (minus PBXT testcase and a bit that was
  later reverted as MDEV-3850) in lp:maria/5.5. They address:

  - TC_LOG_MMAP::open() performed incorrect pointer arithmetics to set
    the end pointer for each page by adding tc_log_page_size
    (byte-sized) number of XIDs (ulonglong-sized).
  - TC_LOG_MMAP::PAGE::ptr field was never initialized.
  - TC_LOG_MMAP::pool reads and writes in
    TC_LOG_MMAP::get_active_from_pool() failed to be protected by
    TC_LOG_MMAP::LOCK_pool if !syncing.
  - If TC_LOG_MMAP::get_active_from_pool() sees that the first page in
    the pool has no waiters, it returns it as an active page, and
    fails to check whether there is any free space on it.
  - active->free and active->size were accessed without active->lock
    protection in TC_LOG_MMAP::get_active_from_pool().
  - tc_log_cur_pages_used was not protected from race conditions in
    bumping it.
  - The TC_LOG_MMAP::log_xid() return value check was inverted in
    TC_LOG_MMAP::commit().
  - active == NULL in TC_LOG_MMAP::log_xid() would result in the new
    active page lock locked twice.
  - LOCK_active was released too early in TC_LOG_MMAP::log_xid(),
    resulting in TC_LOG_MMAP::active access race conditions.
  - There was a lock order violation between LOCK_sync and page locks
    in TC_LOG_MMAP::log_xid().
  - LOCK_active was held needlessly, and page field accesses were not
    protected by a page lock in TC_LOG_MMAP::log_xid() if
    TC_LOG_MMAP::syncing != NULL.
  - TC_LOG_MMAP::sync() requested to sync only 1 byte from the page
    being synced.
  - TC_LOG_MMAP::sync() broadcasted syncing->cond too early.
  - TC_LOG_MMAP::sync() could dereference NULL active.
  - TC_LOG_MMAP::unlog() performed page writes without the page lock
    locked.
  - Do not assert but return error in the case the opened log was
    shorter than three pages long in TC_LOG_MMAP::open().
  - If TC_LOG_MMAP::get_active_from_pool() chooses the last pool page
    to be made active, it was not unlinked from the pool.

- backport of the tc_log_mmap unit test from MySQL 5.7.2 with a
  supporting TC_LOG_MMAP::size() method.

Fix bug 1255551 (Tc_log_page_size should be unflushable or server
crashes if 2 XA SEs installed) / upstream
http://bugs.mysql.com/bug.php?id=70854.

The issue is FLUSH STATUS resetting tc_log_page_size to zero and a
later TC_LOG_MMAP::unlog() attempt to divide by it.

Fixed by making it unflushable.

To post a comment you must log in.
Revision history for this message
George Ormond Lorch III (gl-az) : Posted in a previous version of this proposal
review: Approve (g2)
Revision history for this message
Stewart Smith (stewart) wrote : Posted in a previous version of this proposal

A few small notes:
- We should bring in the (C) headers from MariaDB for files where the code came from MariaDB.
- I'd love it if we had the test case from http://bugs.mysql.com/bug.php?id=70854 (modifying EXAMPLE engine... or copy&pasting it and having an engine for such test cases)

otherwise i think it's okay

review: Needs Fixing
Revision history for this message
Laurynas Biveinis (laurynas-biveinis) wrote : Posted in a previous version of this proposal

> A few small notes:
> - We should bring in the (C) headers from MariaDB for files where the code
> came from MariaDB.

Ack

> - I'd love it if we had the test case from
> http://bugs.mysql.com/bug.php?id=70854 (modifying EXAMPLE engine... or
> copy&pasting it and having an engine for such test cases)

I considered making EXAMPLE a XA engine but in the end decided not to. Neither MySQL nor MariaDB has done it despite the change being simple and, at least in MySQL, the developer who fixed #47134 did this change in order to perform manual testing but mentions in the commit that "I think it won't be correct to modify EXAMPLE".

Maybe it's because making EXAMPLE a XA engine forces TC_LOG_MMAP overhead if binlog is disabled even if the engine is unused. Now TC_LOG_DUMMY is used in that case.

Maybe adding some CMake glue so that EXAMPLE can be compiled into two engines, EXAMPLE and EXAMPLE_XA, from the same sources would be a better option? But IMHO this falls outside the scope of this MP and is more of a Wishlist thing.

What do you think?

Revision history for this message
Stewart Smith (stewart) wrote :

Good point on the overhead of TC_LOG_MMAP overhead. I agree we should have a separate engine, we can easily create (dynamically loaded) engines simply for testing purposes, that isn't a problem at all.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Percona-Server/sql/handler.cc'
2--- Percona-Server/sql/handler.cc 2013-12-16 12:54:12 +0000
3+++ Percona-Server/sql/handler.cc 2013-12-18 14:41:08 +0000
4@@ -1920,7 +1920,7 @@
5 if (info.commit_list)
6 sql_print_information("Starting crash recovery...");
7
8-#ifndef WILL_BE_DELETED_LATER
9+#if 0
10 /*
11 for now, only InnoDB supports 2pc. It means we can always safely
12 rollback all pending transactions, without risking inconsistent data
13
14=== modified file 'Percona-Server/sql/log.cc'
15--- Percona-Server/sql/log.cc 2013-12-16 08:45:31 +0000
16+++ Percona-Server/sql/log.cc 2013-12-18 14:41:08 +0000
17@@ -1,4 +1,5 @@
18 /* Copyright (c) 2000, 2013 Oracle and/or its affiliates. All rights reserved.
19+ Copyright (c) 2009, 2013, Monty Program Ab
20 Copyright (C) 2012 Percona Inc.
21
22 This program is free software; you can redistribute it and/or modify
23@@ -2639,7 +2640,8 @@
24 inited=2;
25
26 npages=(uint)file_length/tc_log_page_size;
27- DBUG_ASSERT(npages >= 3); // to guarantee non-empty pool
28+ if (npages < 3) // to guarantee non-empty pool
29+ goto err;
30 if (!(pages=(PAGE *)my_malloc(npages*sizeof(PAGE), MYF(MY_WME|MY_ZEROFILL))))
31 goto err;
32 inited=3;
33@@ -2650,9 +2652,10 @@
34 pg->state=PS_POOL;
35 mysql_mutex_init(key_PAGE_lock, &pg->lock, MY_MUTEX_INIT_FAST);
36 mysql_cond_init(key_PAGE_cond, &pg->cond, 0);
37- pg->start=(my_xid *)(data + i*tc_log_page_size);
38- pg->end=(my_xid *)(pg->start + tc_log_page_size);
39+ pg->ptr= pg->start=(my_xid *)(data + i*tc_log_page_size);
40 pg->size=pg->free=tc_log_page_size/sizeof(my_xid);
41+ pg->end=pg->start + pg->size;
42+
43 }
44 pages[0].size=pages[0].free=
45 (tc_log_page_size-TC_LOG_HEADER_SIZE)/sizeof(my_xid);
46@@ -2678,8 +2681,9 @@
47
48 syncing= 0;
49 active=pages;
50+ DBUG_ASSERT(npages >= 2);
51 pool=pages+1;
52- pool_last=pages+npages-1;
53+ pool_last_ptr= &((pages+npages-1)->next);
54
55 return 0;
56
57@@ -2688,6 +2692,17 @@
58 return 1;
59 }
60
61+
62+/**
63+ Get the total amount of potentially usable slots for XIDs in TC log.
64+*/
65+
66+uint TC_LOG_MMAP::size() const
67+{
68+ return (tc_log_page_size-TC_LOG_HEADER_SIZE)/sizeof(my_xid) +
69+ (npages - 1) * (tc_log_page_size/sizeof(my_xid));
70+}
71+
72 /**
73 there is no active page, let's got one from the pool.
74
75@@ -2705,13 +2720,12 @@
76 PAGE **p, **best_p=0;
77 int best_free;
78
79- if (syncing)
80- mysql_mutex_lock(&LOCK_pool);
81+ mysql_mutex_lock(&LOCK_pool);
82
83 do
84 {
85 best_p= p= &pool;
86- if ((*p)->waiters == 0) // can the first page be used ?
87+ if ((*p)->waiters == 0 && (*p)->free > 0) // can the first page be used ?
88 break; // yes - take it.
89
90 best_free=0; // no - trying second strategy
91@@ -2726,20 +2740,21 @@
92 }
93 while ((*best_p == 0 || best_free == 0) && overflow());
94
95+ mysql_mutex_assert_owner(&LOCK_active);
96 active=*best_p;
97+
98+ /* Unlink the page from the pool. */
99+ if (!(*best_p)->next)
100+ pool_last_ptr= best_p;
101+ *best_p=(*best_p)->next;
102+ mysql_mutex_unlock(&LOCK_pool);
103+
104+ mysql_mutex_lock(&active->lock);
105 if (active->free == active->size) // we've chosen an empty page
106 {
107- tc_log_cur_pages_used++;
108+ thread_safe_increment(tc_log_cur_pages_used, &LOCK_status);
109 set_if_bigger(tc_log_max_pages_used, tc_log_cur_pages_used);
110 }
111-
112- if ((*best_p)->next) // unlink the page from the pool
113- *best_p=(*best_p)->next;
114- else
115- pool_last=*best_p;
116-
117- if (syncing)
118- mysql_mutex_unlock(&LOCK_pool);
119 }
120
121 /**
122@@ -2772,7 +2787,7 @@
123 my_xid xid= thd->transaction.xid_state.xid.get_my_xid();
124
125 if (all && xid)
126- if ((cookie= log_xid(thd, xid)))
127+ if (!(cookie= log_xid(thd, xid)))
128 DBUG_RETURN(RESULT_ABORTED); // Failed to log the transaction
129
130 if (ha_commit_low(thd, all))
131@@ -2834,9 +2849,17 @@
132 /* no active page ? take one from the pool */
133 if (active == 0)
134 get_active_from_pool();
135+ else
136+ mysql_mutex_lock(&active->lock);
137
138 p=active;
139- mysql_mutex_lock(&p->lock);
140+
141+ /*
142+ p->free is always > 0 here because to decrease it one needs
143+ to take p->lock and before it one needs to take LOCK_active.
144+ But checked that active->free > 0 under LOCK_active and
145+ haven't release it ever since
146+ */
147
148 /* searching for an empty slot */
149 while (*p->ptr)
150@@ -2851,37 +2874,49 @@
151 p->free--;
152 p->state= PS_DIRTY;
153
154- /* to sync or not to sync - this is the question */
155- mysql_mutex_unlock(&LOCK_active);
156+ mysql_mutex_unlock(&p->lock);
157+
158 mysql_mutex_lock(&LOCK_sync);
159- mysql_mutex_unlock(&p->lock);
160-
161 if (syncing)
162 { // somebody's syncing. let's wait
163+ mysql_mutex_unlock(&LOCK_active);
164+ mysql_mutex_lock(&p->lock);
165 p->waiters++;
166 /*
167 note - it must be while (), not do ... while () here
168 as p->state may be not PS_DIRTY when we come here
169 */
170 while (p->state == PS_DIRTY && syncing)
171+ {
172+ mysql_mutex_unlock(&p->lock);
173 mysql_cond_wait(&p->cond, &LOCK_sync);
174+ mysql_mutex_lock(&p->lock);
175+ }
176 p->waiters--;
177 err= p->state == PS_ERROR;
178 if (p->state != PS_DIRTY) // page was synced
179 {
180+ mysql_mutex_unlock(&LOCK_sync);
181 if (p->waiters == 0)
182 mysql_cond_signal(&COND_pool); // in case somebody's waiting
183- mysql_mutex_unlock(&LOCK_sync);
184+ mysql_mutex_unlock(&p->lock);
185 goto done; // we're done
186 }
187- } // page was not synced! do it now
188- DBUG_ASSERT(active == p && syncing == 0);
189- mysql_mutex_lock(&LOCK_active);
190- syncing=p; // place is vacant - take it
191+ DBUG_ASSERT(!syncing);
192+ mysql_mutex_unlock(&p->lock);
193+ syncing = p;
194+ mysql_mutex_unlock(&LOCK_sync);
195+
196+ mysql_mutex_lock(&LOCK_active);
197+ }
198+ else
199+ {
200+ syncing=p; // place is vacant - take it
201+ mysql_mutex_unlock(&LOCK_sync);
202+ }
203 active=0; // page is not active anymore
204 mysql_cond_broadcast(&COND_active); // in case somebody's waiting
205 mysql_mutex_unlock(&LOCK_active);
206- mysql_mutex_unlock(&LOCK_sync);
207 err= sync();
208
209 done:
210@@ -2898,22 +2933,31 @@
211 sit down and relax - this can take a while...
212 note - no locks are held at this point
213 */
214- err= my_msync(fd, syncing->start, 1, MS_SYNC);
215+ err= my_msync(fd, syncing->start, syncing->size * sizeof(my_xid), MS_SYNC);
216
217 /* page is synced. let's move it to the pool */
218 mysql_mutex_lock(&LOCK_pool);
219- pool_last->next=syncing;
220- pool_last=syncing;
221+ (*pool_last_ptr)= syncing;
222+ pool_last_ptr= &(syncing->next);
223 syncing->next=0;
224 syncing->state= err ? PS_ERROR : PS_POOL;
225- mysql_cond_broadcast(&syncing->cond); // signal "sync done"
226 mysql_cond_signal(&COND_pool); // in case somebody's waiting
227 mysql_mutex_unlock(&LOCK_pool);
228
229 /* marking 'syncing' slot free */
230 mysql_mutex_lock(&LOCK_sync);
231+ mysql_cond_broadcast(&syncing->cond); // signal "sync done"
232 syncing=0;
233- mysql_cond_signal(&active->cond); // wake up a new syncer
234+ /*
235+ we check the "active" pointer without LOCK_active. Still, it's safe -
236+ "active" can change from NULL to not NULL any time, but it
237+ will take LOCK_sync before waiting on active->cond. That is, it can never
238+ miss a signal.
239+ And "active" can change to NULL only by the syncing thread
240+ (the thread that will send a signal below)
241+ */
242+ if (active)
243+ mysql_cond_signal(&active->cond); // wake up a new syncer
244 mysql_mutex_unlock(&LOCK_sync);
245 return err;
246 }
247@@ -2930,9 +2974,9 @@
248
249 DBUG_ASSERT(*x == xid);
250 DBUG_ASSERT(x >= p->start && x < p->end);
251+
252+ mysql_mutex_lock(&p->lock);
253 *x=0;
254-
255- mysql_mutex_lock(&p->lock);
256 p->free++;
257 DBUG_ASSERT(p->free <= p->size);
258 set_if_smaller(p->ptr, x);
259
260=== modified file 'Percona-Server/sql/log.h'
261--- Percona-Server/sql/log.h 2013-06-20 15:16:00 +0000
262+++ Percona-Server/sql/log.h 2013-12-18 14:41:08 +0000
263@@ -149,7 +149,7 @@
264 my_off_t file_length;
265 uint npages, inited;
266 uchar *data;
267- struct st_page *pages, *syncing, *active, *pool, *pool_last;
268+ struct st_page *pages, *syncing, *active, *pool, **pool_last_ptr;
269 /*
270 note that, e.g. LOCK_active is only used to protect
271 'active' pointer, to protect the content of the active page
272@@ -167,6 +167,7 @@
273 int rollback(THD *thd, bool all) { return ha_rollback_low(thd, all); }
274 int prepare(THD *thd, bool all) { return ha_prepare_low(thd, all); }
275 int recover();
276+ uint size() const;
277
278 private:
279 int log_xid(THD *thd, my_xid xid);
280@@ -174,6 +175,8 @@
281 void get_active_from_pool();
282 int sync();
283 int overflow();
284+
285+ friend class TCLogMMapTest;
286 };
287 #else
288 #define TC_LOG_MMAP TC_LOG_DUMMY
289
290=== modified file 'Percona-Server/sql/mysqld.cc'
291--- Percona-Server/sql/mysqld.cc 2013-12-16 08:45:31 +0000
292+++ Percona-Server/sql/mysqld.cc 2013-12-18 14:41:08 +0000
293@@ -8217,7 +8217,7 @@
294 {"Table_open_cache_overflows",(char*) offsetof(STATUS_VAR, table_open_cache_overflows), SHOW_LONGLONG_STATUS},
295 #ifdef HAVE_MMAP
296 {"Tc_log_max_pages_used", (char*) &tc_log_max_pages_used, SHOW_LONG},
297- {"Tc_log_page_size", (char*) &tc_log_page_size, SHOW_LONG},
298+ {"Tc_log_page_size", (char*) &tc_log_page_size, SHOW_LONG_NOFLUSH},
299 {"Tc_log_page_waits", (char*) &tc_log_page_waits, SHOW_LONG},
300 #endif
301 #ifdef HAVE_POOL_OF_THREADS
302
303=== modified file 'Percona-Server/unittest/gunit/CMakeLists.txt'
304--- Percona-Server/unittest/gunit/CMakeLists.txt 2013-12-05 17:23:10 +0000
305+++ Percona-Server/unittest/gunit/CMakeLists.txt 2013-12-18 14:41:08 +0000
306@@ -270,6 +270,7 @@
307 segfault
308 sql_table
309 table_cache
310+ tc_log_mmap
311 )
312
313 ## Merging tests into fewer executables saves *a lot* of
314
315=== added file 'Percona-Server/unittest/gunit/tc_log_mmap-t.cc'
316--- Percona-Server/unittest/gunit/tc_log_mmap-t.cc 1970-01-01 00:00:00 +0000
317+++ Percona-Server/unittest/gunit/tc_log_mmap-t.cc 2013-12-18 14:41:08 +0000
318@@ -0,0 +1,201 @@
319+/* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
320+
321+ This program is free software; you can redistribute it and/or modify
322+ it under the terms of the GNU General Public License as published by
323+ the Free Software Foundation; version 2 of the License.
324+
325+ This program is distributed in the hope that it will be useful,
326+ but WITHOUT ANY WARRANTY; without even the implied warranty of
327+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
328+ GNU General Public License for more details.
329+
330+ You should have received a copy of the GNU General Public License
331+ along with this program; if not, write to the Free Software
332+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
333+
334+#include "my_config.h"
335+#include <gtest/gtest.h>
336+#include "log.h"
337+#include "sql_class.h"
338+#include "test_utils.h"
339+#include "thread_utils.h"
340+
341+using my_testing::Server_initializer;
342+
343+/*
344+ Override msync/fsync, saves a *lot* of time during unit testing.
345+ */
346+class TC_LOG_MMAP_no_msync : public TC_LOG_MMAP
347+{
348+protected:
349+ virtual int do_msync_and_fsync(int fd, void *addr, size_t len, int flags)
350+ {
351+ return 0;
352+ }
353+};
354+
355+/*
356+ This class is a friend of TC_LOG_MMAP, so it needs to be outside the unittest
357+ namespace.
358+*/
359+class TCLogMMapTest : public ::testing::Test
360+{
361+public:
362+ virtual void SetUp()
363+ {
364+ initializer.SetUp();
365+ total_ha_2pc= 2;
366+ tc_heuristic_recover= 0;
367+ EXPECT_EQ(0, tc_log_mmap.open("tc_log_mmap_test"));
368+ }
369+
370+ virtual void TearDown()
371+ {
372+ tc_log_mmap.close();
373+ initializer.TearDown();
374+ }
375+
376+ THD *thd()
377+ {
378+ return initializer.thd();
379+ }
380+
381+ void testCommit(ulonglong xid)
382+ {
383+ thd()->transaction.xid_state.xid.set(xid);
384+ EXPECT_EQ(TC_LOG_MMAP::RESULT_SUCCESS, tc_log_mmap.commit(thd(), true));
385+ thd()->transaction.cleanup();
386+ }
387+
388+ ulong testLog(ulonglong xid)
389+ {
390+ return tc_log_mmap.log_xid(thd(), xid);
391+ }
392+
393+ void testUnlog(ulong cookie, ulonglong xid)
394+ {
395+ tc_log_mmap.unlog(cookie, xid);
396+ }
397+
398+protected:
399+ TC_LOG_MMAP_no_msync tc_log_mmap;
400+ Server_initializer initializer;
401+};
402+
403+namespace tc_log_mmap_unittest {
404+
405+TEST_F(TCLogMMapTest, TClogCommit)
406+{
407+ // test calling of log/unlog for xid=1
408+ testCommit(1);
409+}
410+
411+class TC_Log_MMap_thread : public thread::Thread
412+{
413+public:
414+ TC_Log_MMap_thread()
415+ : m_start_xid(0), m_end_xid(0),
416+ m_tc_log_mmap(NULL)
417+ {
418+ }
419+
420+ void init (ulonglong start_value, ulonglong end_value,
421+ TCLogMMapTest* tc_log_mmap)
422+ {
423+ m_start_xid= start_value;
424+ m_end_xid= end_value;
425+ m_tc_log_mmap= tc_log_mmap;
426+ }
427+
428+ virtual void run()
429+ {
430+ ulonglong xid= m_start_xid;
431+ while (xid < m_end_xid)
432+ {
433+ m_tc_log_mmap->testCommit(xid++);
434+ }
435+ }
436+
437+protected:
438+ ulonglong m_start_xid, m_end_xid;
439+ TCLogMMapTest* m_tc_log_mmap;
440+};
441+
442+TEST_F(TCLogMMapTest, ConcurrentAccess)
443+{
444+ static const unsigned MAX_WORKER_THREADS= 10;
445+ static const unsigned VALUE_INTERVAL= 100;
446+
447+ TC_Log_MMap_thread tclog_threads[MAX_WORKER_THREADS];
448+
449+ ulonglong start_value= 0;
450+ for (unsigned i=0; i < MAX_WORKER_THREADS; ++i)
451+ {
452+ tclog_threads[i].init(start_value, start_value + VALUE_INTERVAL, this);
453+ tclog_threads[i].start();
454+ start_value+= VALUE_INTERVAL;
455+ }
456+
457+ for (unsigned i=0; i < MAX_WORKER_THREADS; ++i)
458+ tclog_threads[i].join();
459+}
460+
461+
462+TEST_F(TCLogMMapTest, FillAllPagesAndReuse)
463+{
464+ /* Get maximum number of XIDs which can be stored in TC log. */
465+ const uint MAX_XIDS= tc_log_mmap.size();
466+ ulong cookie;
467+ /* Fill TC log. */
468+ for(my_xid xid= 1; xid < MAX_XIDS; ++xid)
469+ (void)testLog(xid);
470+ cookie= testLog(MAX_XIDS);
471+ /*
472+ Now free one slot and try to reuse it.
473+ This should work and not crash on assert.
474+ */
475+ testUnlog(cookie, MAX_XIDS);
476+ testLog(MAX_XIDS + 1);
477+}
478+
479+
480+TEST_F(TCLogMMapTest, ConcurrentOverflow)
481+{
482+ const uint WORKER_THREADS= 10;
483+ const uint XIDS_TO_REUSE= 100;
484+ /* Get maximum number of XIDs which can be stored in TC log. */
485+ const uint MAX_XIDS= tc_log_mmap.size();
486+ ulong cookies[XIDS_TO_REUSE];
487+
488+ /* Fill TC log. Remember cookies for last XIDS_TO_REUSE xids. */
489+ for(my_xid xid= 1; xid <= MAX_XIDS - XIDS_TO_REUSE; ++xid)
490+ testLog(xid);
491+ for (uint i= 0; i < XIDS_TO_REUSE; ++i)
492+ cookies[i]= testLog(MAX_XIDS - XIDS_TO_REUSE + 1 + i);
493+
494+ /*
495+ Now create several threads which will try to do commit.
496+ Since log is full they will have to wait until we free some slots.
497+ */
498+ TC_Log_MMap_thread threads[WORKER_THREADS];
499+ for (uint i= 0; i < WORKER_THREADS; ++i)
500+ {
501+ threads[i].init(MAX_XIDS + i * (XIDS_TO_REUSE/WORKER_THREADS),
502+ MAX_XIDS + (i + 1) * (XIDS_TO_REUSE/WORKER_THREADS), this);
503+ threads[i].start();
504+ }
505+
506+ /*
507+ Once started all threads should block since we are out of free slots
508+ in the log, Resume threads by freeing necessary slots. Resumed thread
509+ should not hang or assert.
510+ */
511+ for (uint i= 0; i < XIDS_TO_REUSE; ++i)
512+ testUnlog(cookies[i], MAX_XIDS - XIDS_TO_REUSE + 1 + i);
513+
514+ /* Wait till all threads are done. */
515+ for (uint i=0; i < WORKER_THREADS; ++i)
516+ threads[i].join();
517+}
518+
519+}

Subscribers

People subscribed via source and target branches