1 /* Copyright (c) 2013, 2021, Oracle and/or its affiliates.
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, version 2.0,
5    as published by the Free Software Foundation.
6 
7    This program is also distributed with certain software (including
8    but not limited to OpenSSL) that is licensed under separate terms,
9    as designated in a particular file or component or in included license
10    documentation.  The authors of MySQL hereby grant you an additional
11    permission to link the program and your derivative works with the
12    separately licensed software that they have included with MySQL.
13 
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License, version 2.0, for more details.
18 
19    You should have received a copy of the GNU General Public License
20    along with this program; if not, write to the Free Software
21    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA */
22 
23 #include "my_config.h"
24 #include <gtest/gtest.h>
25 #include "tc_log.h"
26 #include "sql_class.h"
27 #include "test_utils.h"
28 #include "thread_utils.h"
29 
30 #ifdef _WIN32
31 #include <process.h> // getpid
32 #endif
33 
34 using my_testing::Server_initializer;
35 
36 /**
37   Override msync/fsync, saves a *lot* of time during unit testing.
38 */
39 
40 class TC_LOG_MMAP_no_msync : public TC_LOG_MMAP
41 {
42 protected:
do_msync_and_fsync(int fd,void * addr,size_t len,int flags)43   virtual int do_msync_and_fsync(int fd, void *addr, size_t len, int flags)
44   {
45     return 0;
46   }
47 };
48 
49 /**
50   This class is a friend of TC_LOG_MMAP, so it needs to be outside the unittest
51   namespace.
52 */
53 
54 class TCLogMMapTest : public ::testing::Test
55 {
56 public:
TCLogMMapTest()57   TCLogMMapTest()
58   : tc_log_mmap(NULL)
59   {
60   }
61 
62 
SetUp()63   virtual void SetUp()
64   {
65     initializer.SetUp();
66     total_ha_2pc= 2;
67     tc_heuristic_recover= TC_HEURISTIC_NOT_USED;
68     /*
69       Assign a transaction coordinator object to am
70       instance of TCLogMMapTest. This transaction coordinator
71       is shared among all threads are run.
72     */
73 
74     tc_log_mmap= new TC_LOG_MMAP_no_msync();
75     // Make a slightly randomized name for the file,
76     // to avoid recovery from other runs.
77     char namebuff[FN_REFLEN];
78     my_snprintf(namebuff, FN_REFLEN,
79                 "tc_log_mmap_test_%d", static_cast<int>(getpid()));
80     ASSERT_EQ(0, tc_log_mmap->open(namebuff));
81   }
82 
83 
TearDown()84   virtual void TearDown()
85   {
86     tc_log_mmap->close();
87     delete tc_log_mmap;
88     initializer.TearDown();
89   }
90 
91 
thd()92   THD *thd()
93   {
94     return initializer.thd();
95   }
96 
97 
98   /**
99     Run test case in single threaded environment.
100     This method uses THD value hold by the initializer data member.
101   */
102 
testCommit(ulonglong xid)103   void testCommit(ulonglong xid)
104   {
105     testCommit(xid, thd());
106   }
107 
108 
109   /**
110     Run a test case with the THD supplied outside. This method is used
111     when there are several threads running the same test case. In this case
112     we need a dedicated THD object for every thread in order not to hit
113     upon the wrong condition when two threads free a memroot (indirectly by
114     calling thd->get_transaction()->cleanup()) for the same THD object.
115   */
116 
testCommit(ulonglong xid,THD * thd_val)117   void testCommit(ulonglong xid, THD *thd_val)
118   {
119     XID_STATE *xid_state= thd_val->get_transaction()->xid_state();
120     xid_state->set_query_id(xid);
121     EXPECT_EQ(TC_LOG_MMAP::RESULT_SUCCESS, tc_log_mmap->commit(thd_val, true));
122     thd_val->get_transaction()->cleanup();
123   }
124 
testLog(ulonglong xid)125   ulong testLog(ulonglong xid)
126   {
127     return tc_log_mmap->log_xid(xid);
128   }
129 
testUnlog(ulong cookie,ulonglong xid)130   void testUnlog(ulong cookie, ulonglong xid)
131   {
132     tc_log_mmap->unlog(cookie, xid);
133   }
134 
135 protected:
136   TC_LOG_MMAP_no_msync* tc_log_mmap;
137   Server_initializer initializer;
138 };
139 
140 namespace tc_log_mmap_unittest {
141 
TEST_F(TCLogMMapTest,TClogCommit)142 TEST_F(TCLogMMapTest, TClogCommit)
143 {
144   // test calling of log/unlog for xid=1
145   testCommit(1);
146 }
147 
148 class TC_Log_MMap_thread : public thread::Thread
149 {
150 public:
TC_Log_MMap_thread()151   TC_Log_MMap_thread()
152   : m_start_xid(0), m_end_xid(0),
153     m_tc_log_mmap(NULL), initializer(NULL)
154   {
155   }
156 
~TC_Log_MMap_thread()157   ~TC_Log_MMap_thread()
158   {
159     initializer->TearDown();
160     delete initializer;
161   }
162 
init(ulonglong start_value,ulonglong end_value,TCLogMMapTest * tc_log_mmap,Server_initializer * initializer_value)163   void init (ulonglong start_value, ulonglong end_value,
164              TCLogMMapTest* tc_log_mmap, Server_initializer* initializer_value)
165   {
166     m_start_xid= start_value;
167     m_end_xid= end_value;
168     m_tc_log_mmap= tc_log_mmap;
169     initializer= initializer_value;
170     initializer->SetUp();
171   }
172 
run()173   virtual void run()
174   {
175     ulonglong xid= m_start_xid;
176     while (xid < m_end_xid)
177     {
178       m_tc_log_mmap->testCommit(xid++, initializer->thd());
179     }
180   }
181 
182 protected:
183   ulonglong m_start_xid, m_end_xid;
184   TCLogMMapTest* m_tc_log_mmap;
185   Server_initializer* initializer;
186 };
187 
TEST_F(TCLogMMapTest,ConcurrentAccess)188 TEST_F(TCLogMMapTest, ConcurrentAccess)
189 {
190   static const unsigned MAX_WORKER_THREADS= 10;
191   static const unsigned VALUE_INTERVAL= 100;
192 
193   TC_Log_MMap_thread tclog_threads[MAX_WORKER_THREADS];
194 
195   ulonglong start_value= 0;
196   for (unsigned i=0; i < MAX_WORKER_THREADS; ++i)
197   {
198     /*
199       Each thread gets a dedicated instance of class Server_initializer and
200       hence it also gets a separate THD object.
201     */
202     tclog_threads[i].init(start_value, start_value + VALUE_INTERVAL, this,
203                           new Server_initializer());
204     tclog_threads[i].start();
205     start_value+= VALUE_INTERVAL;
206   }
207 
208   for (unsigned i=0; i < MAX_WORKER_THREADS; ++i)
209     tclog_threads[i].join();
210 }
211 
212 
TEST_F(TCLogMMapTest,FillAllPagesAndReuse)213 TEST_F(TCLogMMapTest, FillAllPagesAndReuse)
214 {
215   /* Get maximum number of XIDs which can be stored in TC log. */
216   const uint MAX_XIDS= tc_log_mmap->size();
217   ulong cookie;
218   /* Fill TC log. */
219   for(my_xid xid= 1; xid < MAX_XIDS; ++xid)
220     (void)testLog(xid);
221   cookie= testLog(MAX_XIDS);
222   /*
223     Now free one slot and try to reuse it.
224     This should work and not crash on assert.
225   */
226   testUnlog(cookie, MAX_XIDS);
227   testLog(MAX_XIDS + 1);
228 }
229 
230 
TEST_F(TCLogMMapTest,ConcurrentOverflow)231 TEST_F(TCLogMMapTest, ConcurrentOverflow)
232 {
233   const uint WORKER_THREADS= 10;
234   const uint XIDS_TO_REUSE= 100;
235 
236   /*
237     Get maximum number of XIDs which can be stored in TC log.
238   */
239   const uint MAX_XIDS= tc_log_mmap->size();
240   ulong cookies[XIDS_TO_REUSE];
241 
242   /*
243     Fill TC log. Remember cookies for last XIDS_TO_REUSE xids.
244   */
245   for(my_xid xid= 1; xid <= MAX_XIDS - XIDS_TO_REUSE; ++xid)
246     testLog(xid);
247   for (uint i= 0; i < XIDS_TO_REUSE; ++i)
248     cookies[i]= testLog(MAX_XIDS - XIDS_TO_REUSE + 1 + i);
249 
250   /*
251     Now create several threads which will try to do commit.
252     Since log is full they will have to wait until we free some slots.
253   */
254   TC_Log_MMap_thread threads[WORKER_THREADS];
255   for (uint i= 0; i < WORKER_THREADS; ++i)
256   {
257     /*
258       Each thread gets a dedicated instance of class Server_initializer and hence
259       it also gets a separate THD object.
260     */
261     threads[i].init(MAX_XIDS + i * (XIDS_TO_REUSE/WORKER_THREADS),
262                     MAX_XIDS + (i + 1) * (XIDS_TO_REUSE/WORKER_THREADS), this,
263                     new Server_initializer());
264     threads[i].start();
265   }
266 
267   /*
268     Once started all threads should block since we are out of free slots
269     in the log, Resume threads by freeing necessary slots. Resumed thread
270     should not hang or assert.
271   */
272   for (uint i= 0; i < XIDS_TO_REUSE; ++i)
273     testUnlog(cookies[i], MAX_XIDS - XIDS_TO_REUSE + 1 + i);
274 
275   /* Wait till all threads are done. */
276   for (uint i=0; i < WORKER_THREADS; ++i)
277     threads[i].join();
278 }
279 
280 }
281