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: no longer in the source branch.
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
=== modified file 'Percona-Server/sql/handler.cc'
--- Percona-Server/sql/handler.cc 2013-12-16 12:54:12 +0000
+++ Percona-Server/sql/handler.cc 2013-12-18 14:41:08 +0000
@@ -1920,7 +1920,7 @@
1920 if (info.commit_list)1920 if (info.commit_list)
1921 sql_print_information("Starting crash recovery...");1921 sql_print_information("Starting crash recovery...");
19221922
1923#ifndef WILL_BE_DELETED_LATER1923#if 0
1924 /*1924 /*
1925 for now, only InnoDB supports 2pc. It means we can always safely1925 for now, only InnoDB supports 2pc. It means we can always safely
1926 rollback all pending transactions, without risking inconsistent data1926 rollback all pending transactions, without risking inconsistent data
19271927
=== modified file 'Percona-Server/sql/log.cc'
--- Percona-Server/sql/log.cc 2013-12-16 08:45:31 +0000
+++ Percona-Server/sql/log.cc 2013-12-18 14:41:08 +0000
@@ -1,4 +1,5 @@
1/* Copyright (c) 2000, 2013 Oracle and/or its affiliates. All rights reserved.1/* Copyright (c) 2000, 2013 Oracle and/or its affiliates. All rights reserved.
2 Copyright (c) 2009, 2013, Monty Program Ab
2 Copyright (C) 2012 Percona Inc.3 Copyright (C) 2012 Percona Inc.
34
4 This program is free software; you can redistribute it and/or modify5 This program is free software; you can redistribute it and/or modify
@@ -2639,7 +2640,8 @@
2639 inited=2;2640 inited=2;
26402641
2641 npages=(uint)file_length/tc_log_page_size;2642 npages=(uint)file_length/tc_log_page_size;
2642 DBUG_ASSERT(npages >= 3); // to guarantee non-empty pool2643 if (npages < 3) // to guarantee non-empty pool
2644 goto err;
2643 if (!(pages=(PAGE *)my_malloc(npages*sizeof(PAGE), MYF(MY_WME|MY_ZEROFILL))))2645 if (!(pages=(PAGE *)my_malloc(npages*sizeof(PAGE), MYF(MY_WME|MY_ZEROFILL))))
2644 goto err;2646 goto err;
2645 inited=3;2647 inited=3;
@@ -2650,9 +2652,10 @@
2650 pg->state=PS_POOL;2652 pg->state=PS_POOL;
2651 mysql_mutex_init(key_PAGE_lock, &pg->lock, MY_MUTEX_INIT_FAST);2653 mysql_mutex_init(key_PAGE_lock, &pg->lock, MY_MUTEX_INIT_FAST);
2652 mysql_cond_init(key_PAGE_cond, &pg->cond, 0);2654 mysql_cond_init(key_PAGE_cond, &pg->cond, 0);
2653 pg->start=(my_xid *)(data + i*tc_log_page_size);2655 pg->ptr= pg->start=(my_xid *)(data + i*tc_log_page_size);
2654 pg->end=(my_xid *)(pg->start + tc_log_page_size);
2655 pg->size=pg->free=tc_log_page_size/sizeof(my_xid);2656 pg->size=pg->free=tc_log_page_size/sizeof(my_xid);
2657 pg->end=pg->start + pg->size;
2658
2656 }2659 }
2657 pages[0].size=pages[0].free=2660 pages[0].size=pages[0].free=
2658 (tc_log_page_size-TC_LOG_HEADER_SIZE)/sizeof(my_xid);2661 (tc_log_page_size-TC_LOG_HEADER_SIZE)/sizeof(my_xid);
@@ -2678,8 +2681,9 @@
26782681
2679 syncing= 0;2682 syncing= 0;
2680 active=pages;2683 active=pages;
2684 DBUG_ASSERT(npages >= 2);
2681 pool=pages+1;2685 pool=pages+1;
2682 pool_last=pages+npages-1;2686 pool_last_ptr= &((pages+npages-1)->next);
26832687
2684 return 0;2688 return 0;
26852689
@@ -2688,6 +2692,17 @@
2688 return 1;2692 return 1;
2689}2693}
26902694
2695
2696/**
2697 Get the total amount of potentially usable slots for XIDs in TC log.
2698*/
2699
2700uint TC_LOG_MMAP::size() const
2701{
2702 return (tc_log_page_size-TC_LOG_HEADER_SIZE)/sizeof(my_xid) +
2703 (npages - 1) * (tc_log_page_size/sizeof(my_xid));
2704}
2705
2691/**2706/**
2692 there is no active page, let's got one from the pool.2707 there is no active page, let's got one from the pool.
26932708
@@ -2705,13 +2720,12 @@
2705 PAGE **p, **best_p=0;2720 PAGE **p, **best_p=0;
2706 int best_free;2721 int best_free;
27072722
2708 if (syncing)2723 mysql_mutex_lock(&LOCK_pool);
2709 mysql_mutex_lock(&LOCK_pool);
27102724
2711 do2725 do
2712 {2726 {
2713 best_p= p= &pool;2727 best_p= p= &pool;
2714 if ((*p)->waiters == 0) // can the first page be used ?2728 if ((*p)->waiters == 0 && (*p)->free > 0) // can the first page be used ?
2715 break; // yes - take it.2729 break; // yes - take it.
27162730
2717 best_free=0; // no - trying second strategy2731 best_free=0; // no - trying second strategy
@@ -2726,20 +2740,21 @@
2726 }2740 }
2727 while ((*best_p == 0 || best_free == 0) && overflow());2741 while ((*best_p == 0 || best_free == 0) && overflow());
27282742
2743 mysql_mutex_assert_owner(&LOCK_active);
2729 active=*best_p;2744 active=*best_p;
2745
2746 /* Unlink the page from the pool. */
2747 if (!(*best_p)->next)
2748 pool_last_ptr= best_p;
2749 *best_p=(*best_p)->next;
2750 mysql_mutex_unlock(&LOCK_pool);
2751
2752 mysql_mutex_lock(&active->lock);
2730 if (active->free == active->size) // we've chosen an empty page2753 if (active->free == active->size) // we've chosen an empty page
2731 {2754 {
2732 tc_log_cur_pages_used++;2755 thread_safe_increment(tc_log_cur_pages_used, &LOCK_status);
2733 set_if_bigger(tc_log_max_pages_used, tc_log_cur_pages_used);2756 set_if_bigger(tc_log_max_pages_used, tc_log_cur_pages_used);
2734 }2757 }
2735
2736 if ((*best_p)->next) // unlink the page from the pool
2737 *best_p=(*best_p)->next;
2738 else
2739 pool_last=*best_p;
2740
2741 if (syncing)
2742 mysql_mutex_unlock(&LOCK_pool);
2743}2758}
27442759
2745/**2760/**
@@ -2772,7 +2787,7 @@
2772 my_xid xid= thd->transaction.xid_state.xid.get_my_xid();2787 my_xid xid= thd->transaction.xid_state.xid.get_my_xid();
27732788
2774 if (all && xid)2789 if (all && xid)
2775 if ((cookie= log_xid(thd, xid)))2790 if (!(cookie= log_xid(thd, xid)))
2776 DBUG_RETURN(RESULT_ABORTED); // Failed to log the transaction2791 DBUG_RETURN(RESULT_ABORTED); // Failed to log the transaction
27772792
2778 if (ha_commit_low(thd, all))2793 if (ha_commit_low(thd, all))
@@ -2834,9 +2849,17 @@
2834 /* no active page ? take one from the pool */2849 /* no active page ? take one from the pool */
2835 if (active == 0)2850 if (active == 0)
2836 get_active_from_pool();2851 get_active_from_pool();
2852 else
2853 mysql_mutex_lock(&active->lock);
28372854
2838 p=active;2855 p=active;
2839 mysql_mutex_lock(&p->lock);2856
2857 /*
2858 p->free is always > 0 here because to decrease it one needs
2859 to take p->lock and before it one needs to take LOCK_active.
2860 But checked that active->free > 0 under LOCK_active and
2861 haven't release it ever since
2862 */
28402863
2841 /* searching for an empty slot */2864 /* searching for an empty slot */
2842 while (*p->ptr)2865 while (*p->ptr)
@@ -2851,37 +2874,49 @@
2851 p->free--;2874 p->free--;
2852 p->state= PS_DIRTY;2875 p->state= PS_DIRTY;
28532876
2854 /* to sync or not to sync - this is the question */2877 mysql_mutex_unlock(&p->lock);
2855 mysql_mutex_unlock(&LOCK_active);2878
2856 mysql_mutex_lock(&LOCK_sync);2879 mysql_mutex_lock(&LOCK_sync);
2857 mysql_mutex_unlock(&p->lock);
2858
2859 if (syncing)2880 if (syncing)
2860 { // somebody's syncing. let's wait2881 { // somebody's syncing. let's wait
2882 mysql_mutex_unlock(&LOCK_active);
2883 mysql_mutex_lock(&p->lock);
2861 p->waiters++;2884 p->waiters++;
2862 /*2885 /*
2863 note - it must be while (), not do ... while () here2886 note - it must be while (), not do ... while () here
2864 as p->state may be not PS_DIRTY when we come here2887 as p->state may be not PS_DIRTY when we come here
2865 */2888 */
2866 while (p->state == PS_DIRTY && syncing)2889 while (p->state == PS_DIRTY && syncing)
2890 {
2891 mysql_mutex_unlock(&p->lock);
2867 mysql_cond_wait(&p->cond, &LOCK_sync);2892 mysql_cond_wait(&p->cond, &LOCK_sync);
2893 mysql_mutex_lock(&p->lock);
2894 }
2868 p->waiters--;2895 p->waiters--;
2869 err= p->state == PS_ERROR;2896 err= p->state == PS_ERROR;
2870 if (p->state != PS_DIRTY) // page was synced2897 if (p->state != PS_DIRTY) // page was synced
2871 {2898 {
2899 mysql_mutex_unlock(&LOCK_sync);
2872 if (p->waiters == 0)2900 if (p->waiters == 0)
2873 mysql_cond_signal(&COND_pool); // in case somebody's waiting2901 mysql_cond_signal(&COND_pool); // in case somebody's waiting
2874 mysql_mutex_unlock(&LOCK_sync);2902 mysql_mutex_unlock(&p->lock);
2875 goto done; // we're done2903 goto done; // we're done
2876 }2904 }
2877 } // page was not synced! do it now2905 DBUG_ASSERT(!syncing);
2878 DBUG_ASSERT(active == p && syncing == 0);2906 mysql_mutex_unlock(&p->lock);
2879 mysql_mutex_lock(&LOCK_active);2907 syncing = p;
2880 syncing=p; // place is vacant - take it2908 mysql_mutex_unlock(&LOCK_sync);
2909
2910 mysql_mutex_lock(&LOCK_active);
2911 }
2912 else
2913 {
2914 syncing=p; // place is vacant - take it
2915 mysql_mutex_unlock(&LOCK_sync);
2916 }
2881 active=0; // page is not active anymore2917 active=0; // page is not active anymore
2882 mysql_cond_broadcast(&COND_active); // in case somebody's waiting2918 mysql_cond_broadcast(&COND_active); // in case somebody's waiting
2883 mysql_mutex_unlock(&LOCK_active);2919 mysql_mutex_unlock(&LOCK_active);
2884 mysql_mutex_unlock(&LOCK_sync);
2885 err= sync();2920 err= sync();
28862921
2887done:2922done:
@@ -2898,22 +2933,31 @@
2898 sit down and relax - this can take a while...2933 sit down and relax - this can take a while...
2899 note - no locks are held at this point2934 note - no locks are held at this point
2900 */2935 */
2901 err= my_msync(fd, syncing->start, 1, MS_SYNC);2936 err= my_msync(fd, syncing->start, syncing->size * sizeof(my_xid), MS_SYNC);
29022937
2903 /* page is synced. let's move it to the pool */2938 /* page is synced. let's move it to the pool */
2904 mysql_mutex_lock(&LOCK_pool);2939 mysql_mutex_lock(&LOCK_pool);
2905 pool_last->next=syncing;2940 (*pool_last_ptr)= syncing;
2906 pool_last=syncing;2941 pool_last_ptr= &(syncing->next);
2907 syncing->next=0;2942 syncing->next=0;
2908 syncing->state= err ? PS_ERROR : PS_POOL;2943 syncing->state= err ? PS_ERROR : PS_POOL;
2909 mysql_cond_broadcast(&syncing->cond); // signal "sync done"
2910 mysql_cond_signal(&COND_pool); // in case somebody's waiting2944 mysql_cond_signal(&COND_pool); // in case somebody's waiting
2911 mysql_mutex_unlock(&LOCK_pool);2945 mysql_mutex_unlock(&LOCK_pool);
29122946
2913 /* marking 'syncing' slot free */2947 /* marking 'syncing' slot free */
2914 mysql_mutex_lock(&LOCK_sync);2948 mysql_mutex_lock(&LOCK_sync);
2949 mysql_cond_broadcast(&syncing->cond); // signal "sync done"
2915 syncing=0;2950 syncing=0;
2916 mysql_cond_signal(&active->cond); // wake up a new syncer2951 /*
2952 we check the "active" pointer without LOCK_active. Still, it's safe -
2953 "active" can change from NULL to not NULL any time, but it
2954 will take LOCK_sync before waiting on active->cond. That is, it can never
2955 miss a signal.
2956 And "active" can change to NULL only by the syncing thread
2957 (the thread that will send a signal below)
2958 */
2959 if (active)
2960 mysql_cond_signal(&active->cond); // wake up a new syncer
2917 mysql_mutex_unlock(&LOCK_sync);2961 mysql_mutex_unlock(&LOCK_sync);
2918 return err;2962 return err;
2919}2963}
@@ -2930,9 +2974,9 @@
29302974
2931 DBUG_ASSERT(*x == xid);2975 DBUG_ASSERT(*x == xid);
2932 DBUG_ASSERT(x >= p->start && x < p->end);2976 DBUG_ASSERT(x >= p->start && x < p->end);
2977
2978 mysql_mutex_lock(&p->lock);
2933 *x=0;2979 *x=0;
2934
2935 mysql_mutex_lock(&p->lock);
2936 p->free++;2980 p->free++;
2937 DBUG_ASSERT(p->free <= p->size);2981 DBUG_ASSERT(p->free <= p->size);
2938 set_if_smaller(p->ptr, x);2982 set_if_smaller(p->ptr, x);
29392983
=== modified file 'Percona-Server/sql/log.h'
--- Percona-Server/sql/log.h 2013-06-20 15:16:00 +0000
+++ Percona-Server/sql/log.h 2013-12-18 14:41:08 +0000
@@ -149,7 +149,7 @@
149 my_off_t file_length;149 my_off_t file_length;
150 uint npages, inited;150 uint npages, inited;
151 uchar *data;151 uchar *data;
152 struct st_page *pages, *syncing, *active, *pool, *pool_last;152 struct st_page *pages, *syncing, *active, *pool, **pool_last_ptr;
153 /*153 /*
154 note that, e.g. LOCK_active is only used to protect154 note that, e.g. LOCK_active is only used to protect
155 'active' pointer, to protect the content of the active page155 'active' pointer, to protect the content of the active page
@@ -167,6 +167,7 @@
167 int rollback(THD *thd, bool all) { return ha_rollback_low(thd, all); }167 int rollback(THD *thd, bool all) { return ha_rollback_low(thd, all); }
168 int prepare(THD *thd, bool all) { return ha_prepare_low(thd, all); }168 int prepare(THD *thd, bool all) { return ha_prepare_low(thd, all); }
169 int recover();169 int recover();
170 uint size() const;
170171
171private:172private:
172 int log_xid(THD *thd, my_xid xid);173 int log_xid(THD *thd, my_xid xid);
@@ -174,6 +175,8 @@
174 void get_active_from_pool();175 void get_active_from_pool();
175 int sync();176 int sync();
176 int overflow();177 int overflow();
178
179 friend class TCLogMMapTest;
177};180};
178#else181#else
179#define TC_LOG_MMAP TC_LOG_DUMMY182#define TC_LOG_MMAP TC_LOG_DUMMY
180183
=== modified file 'Percona-Server/sql/mysqld.cc'
--- Percona-Server/sql/mysqld.cc 2013-12-16 08:45:31 +0000
+++ Percona-Server/sql/mysqld.cc 2013-12-18 14:41:08 +0000
@@ -8217,7 +8217,7 @@
8217 {"Table_open_cache_overflows",(char*) offsetof(STATUS_VAR, table_open_cache_overflows), SHOW_LONGLONG_STATUS},8217 {"Table_open_cache_overflows",(char*) offsetof(STATUS_VAR, table_open_cache_overflows), SHOW_LONGLONG_STATUS},
8218#ifdef HAVE_MMAP8218#ifdef HAVE_MMAP
8219 {"Tc_log_max_pages_used", (char*) &tc_log_max_pages_used, SHOW_LONG},8219 {"Tc_log_max_pages_used", (char*) &tc_log_max_pages_used, SHOW_LONG},
8220 {"Tc_log_page_size", (char*) &tc_log_page_size, SHOW_LONG},8220 {"Tc_log_page_size", (char*) &tc_log_page_size, SHOW_LONG_NOFLUSH},
8221 {"Tc_log_page_waits", (char*) &tc_log_page_waits, SHOW_LONG},8221 {"Tc_log_page_waits", (char*) &tc_log_page_waits, SHOW_LONG},
8222#endif8222#endif
8223#ifdef HAVE_POOL_OF_THREADS8223#ifdef HAVE_POOL_OF_THREADS
82248224
=== modified file 'Percona-Server/unittest/gunit/CMakeLists.txt'
--- Percona-Server/unittest/gunit/CMakeLists.txt 2013-12-05 17:23:10 +0000
+++ Percona-Server/unittest/gunit/CMakeLists.txt 2013-12-18 14:41:08 +0000
@@ -270,6 +270,7 @@
270 segfault270 segfault
271 sql_table271 sql_table
272 table_cache272 table_cache
273 tc_log_mmap
273)274)
274275
275## Merging tests into fewer executables saves *a lot* of276## Merging tests into fewer executables saves *a lot* of
276277
=== added file 'Percona-Server/unittest/gunit/tc_log_mmap-t.cc'
--- Percona-Server/unittest/gunit/tc_log_mmap-t.cc 1970-01-01 00:00:00 +0000
+++ Percona-Server/unittest/gunit/tc_log_mmap-t.cc 2013-12-18 14:41:08 +0000
@@ -0,0 +1,201 @@
1/* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; version 2 of the License.
6
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY; without even the implied warranty of
9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 GNU General Public License for more details.
11
12 You should have received a copy of the GNU General Public License
13 along with this program; if not, write to the Free Software
14 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
15
16#include "my_config.h"
17#include <gtest/gtest.h>
18#include "log.h"
19#include "sql_class.h"
20#include "test_utils.h"
21#include "thread_utils.h"
22
23using my_testing::Server_initializer;
24
25/*
26 Override msync/fsync, saves a *lot* of time during unit testing.
27 */
28class TC_LOG_MMAP_no_msync : public TC_LOG_MMAP
29{
30protected:
31 virtual int do_msync_and_fsync(int fd, void *addr, size_t len, int flags)
32 {
33 return 0;
34 }
35};
36
37/*
38 This class is a friend of TC_LOG_MMAP, so it needs to be outside the unittest
39 namespace.
40*/
41class TCLogMMapTest : public ::testing::Test
42{
43public:
44 virtual void SetUp()
45 {
46 initializer.SetUp();
47 total_ha_2pc= 2;
48 tc_heuristic_recover= 0;
49 EXPECT_EQ(0, tc_log_mmap.open("tc_log_mmap_test"));
50 }
51
52 virtual void TearDown()
53 {
54 tc_log_mmap.close();
55 initializer.TearDown();
56 }
57
58 THD *thd()
59 {
60 return initializer.thd();
61 }
62
63 void testCommit(ulonglong xid)
64 {
65 thd()->transaction.xid_state.xid.set(xid);
66 EXPECT_EQ(TC_LOG_MMAP::RESULT_SUCCESS, tc_log_mmap.commit(thd(), true));
67 thd()->transaction.cleanup();
68 }
69
70 ulong testLog(ulonglong xid)
71 {
72 return tc_log_mmap.log_xid(thd(), xid);
73 }
74
75 void testUnlog(ulong cookie, ulonglong xid)
76 {
77 tc_log_mmap.unlog(cookie, xid);
78 }
79
80protected:
81 TC_LOG_MMAP_no_msync tc_log_mmap;
82 Server_initializer initializer;
83};
84
85namespace tc_log_mmap_unittest {
86
87TEST_F(TCLogMMapTest, TClogCommit)
88{
89 // test calling of log/unlog for xid=1
90 testCommit(1);
91}
92
93class TC_Log_MMap_thread : public thread::Thread
94{
95public:
96 TC_Log_MMap_thread()
97 : m_start_xid(0), m_end_xid(0),
98 m_tc_log_mmap(NULL)
99 {
100 }
101
102 void init (ulonglong start_value, ulonglong end_value,
103 TCLogMMapTest* tc_log_mmap)
104 {
105 m_start_xid= start_value;
106 m_end_xid= end_value;
107 m_tc_log_mmap= tc_log_mmap;
108 }
109
110 virtual void run()
111 {
112 ulonglong xid= m_start_xid;
113 while (xid < m_end_xid)
114 {
115 m_tc_log_mmap->testCommit(xid++);
116 }
117 }
118
119protected:
120 ulonglong m_start_xid, m_end_xid;
121 TCLogMMapTest* m_tc_log_mmap;
122};
123
124TEST_F(TCLogMMapTest, ConcurrentAccess)
125{
126 static const unsigned MAX_WORKER_THREADS= 10;
127 static const unsigned VALUE_INTERVAL= 100;
128
129 TC_Log_MMap_thread tclog_threads[MAX_WORKER_THREADS];
130
131 ulonglong start_value= 0;
132 for (unsigned i=0; i < MAX_WORKER_THREADS; ++i)
133 {
134 tclog_threads[i].init(start_value, start_value + VALUE_INTERVAL, this);
135 tclog_threads[i].start();
136 start_value+= VALUE_INTERVAL;
137 }
138
139 for (unsigned i=0; i < MAX_WORKER_THREADS; ++i)
140 tclog_threads[i].join();
141}
142
143
144TEST_F(TCLogMMapTest, FillAllPagesAndReuse)
145{
146 /* Get maximum number of XIDs which can be stored in TC log. */
147 const uint MAX_XIDS= tc_log_mmap.size();
148 ulong cookie;
149 /* Fill TC log. */
150 for(my_xid xid= 1; xid < MAX_XIDS; ++xid)
151 (void)testLog(xid);
152 cookie= testLog(MAX_XIDS);
153 /*
154 Now free one slot and try to reuse it.
155 This should work and not crash on assert.
156 */
157 testUnlog(cookie, MAX_XIDS);
158 testLog(MAX_XIDS + 1);
159}
160
161
162TEST_F(TCLogMMapTest, ConcurrentOverflow)
163{
164 const uint WORKER_THREADS= 10;
165 const uint XIDS_TO_REUSE= 100;
166 /* Get maximum number of XIDs which can be stored in TC log. */
167 const uint MAX_XIDS= tc_log_mmap.size();
168 ulong cookies[XIDS_TO_REUSE];
169
170 /* Fill TC log. Remember cookies for last XIDS_TO_REUSE xids. */
171 for(my_xid xid= 1; xid <= MAX_XIDS - XIDS_TO_REUSE; ++xid)
172 testLog(xid);
173 for (uint i= 0; i < XIDS_TO_REUSE; ++i)
174 cookies[i]= testLog(MAX_XIDS - XIDS_TO_REUSE + 1 + i);
175
176 /*
177 Now create several threads which will try to do commit.
178 Since log is full they will have to wait until we free some slots.
179 */
180 TC_Log_MMap_thread threads[WORKER_THREADS];
181 for (uint i= 0; i < WORKER_THREADS; ++i)
182 {
183 threads[i].init(MAX_XIDS + i * (XIDS_TO_REUSE/WORKER_THREADS),
184 MAX_XIDS + (i + 1) * (XIDS_TO_REUSE/WORKER_THREADS), this);
185 threads[i].start();
186 }
187
188 /*
189 Once started all threads should block since we are out of free slots
190 in the log, Resume threads by freeing necessary slots. Resumed thread
191 should not hang or assert.
192 */
193 for (uint i= 0; i < XIDS_TO_REUSE; ++i)
194 testUnlog(cookies[i], MAX_XIDS - XIDS_TO_REUSE + 1 + i);
195
196 /* Wait till all threads are done. */
197 for (uint i=0; i < WORKER_THREADS; ++i)
198 threads[i].join();
199}
200
201}

Subscribers

People subscribed via source and target branches