Skip to content

Commit 780c635

Browse files
author
Sven Sandberg
committed
WL#7083 step 5.1. Anonymous transactions: Maintain a counter of ongoing anonymous transactions.
In order to prevent switching gtid_mode from ON_PERMISSIVE to ON when there are ongoing anonymous transactions, we introduce a counter that counts the number of ongoing anonymous transactions. @atomic_class.h - Auxiliary class to use C++ syntax to access atomic integers. @rpl_gtid.h - Add member functions in Gtid_state to acquire and release anonymous ownership. @rpl_gtid_execution.cc - Acquire anonymous ownership when executing set gtid_next='anonymous'. @rpl_gtid_state.cc - Release anonymous ownership in update_gtid_impl. - Acquire anonymous ownership in generate_automatic_gtid. @binlog.cc: - Decrease global counter when releasing anonymous ownership. - When thread holds anonymous ownership, and gtid_end_transaction is called, it should call update_on_commit instead of clear_owned_gtids, so that it releases anonymous ownership.
1 parent 525731e commit 780c635

File tree

5 files changed

+273
-29
lines changed

5 files changed

+273
-29
lines changed

sql/atomic_class.h

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
2+
3+
This program is free software; you can redistribute it and/or
4+
modify it under the terms of the GNU General Public License as
5+
published by the Free Software Foundation; version 2 of the
6+
License.
7+
8+
This program is distributed in the hope that it will be useful, but
9+
WITHOUT ANY WARRANTY; without even the implied warranty of
10+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+
General Public License for more details.
12+
13+
You should have received a copy of the GNU General Public License
14+
along with this program; if not, write to the Free Software
15+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
16+
02110-1301 USA */
17+
18+
19+
#ifndef ATOMIC_CLASS_H_INCLUDED
20+
#define ATOMIC_CLASS_H_INCLUDED
21+
22+
#include "my_atomic.h"
23+
24+
/**
25+
Wrapper class to use C++ syntax to access atomic integers.
26+
*/
27+
#define DEFINE_ATOMIC_CLASS(NAME, SUFFIX, TYPE) \
28+
class Atomic_##NAME \
29+
{ \
30+
public: \
31+
/* Create a new Atomic_* object. */ \
32+
Atomic_##NAME(TYPE n= 0) \
33+
{ \
34+
my_atomic_store##SUFFIX(&value, n); \
35+
} \
36+
/* Atomically read the value. */ \
37+
TYPE atomic_get() \
38+
{ \
39+
return my_atomic_load##SUFFIX(&value); \
40+
} \
41+
/* Atomically set the value. */ \
42+
void atomic_set(TYPE n) \
43+
{ \
44+
my_atomic_store##SUFFIX(&value, n); \
45+
} \
46+
/* Atomically add to the value, and return the old value. */ \
47+
TYPE atomic_add(TYPE n) \
48+
{ \
49+
return my_atomic_add##SUFFIX(&value, n); \
50+
} \
51+
/* Atomically set the value and return the old value. */ \
52+
TYPE atomic_get_and_set(TYPE n) \
53+
{ \
54+
return my_atomic_fas##SUFFIX(&value, n); \
55+
} \
56+
/* If the old value is equal to *old, set the value to new and */ \
57+
/* return true. Otherwise, store the old value in '*old' and */ \
58+
/* return false. */ \
59+
bool atomic_compare_and_swap(TYPE *old, TYPE n) \
60+
{ \
61+
return my_atomic_cas##SUFFIX(&value, old, n); \
62+
} \
63+
/* Read the value *non-atomically*. */ \
64+
TYPE non_atomic_get() \
65+
{ \
66+
return value; \
67+
} \
68+
/* Set the value *non-atomically*. */ \
69+
void non_atomic_set(TYPE n) \
70+
{ \
71+
value= n; \
72+
} \
73+
/* Add to the value *non-atomically*. */ \
74+
TYPE non_atomic_add(TYPE n) \
75+
{ \
76+
TYPE ret= value; \
77+
value+= n; \
78+
return ret; \
79+
} \
80+
/* Set the value to the greatest of (old, n). */ \
81+
\
82+
/* The function will internally requires multiple atomic */ \
83+
/* operations. If the old value is known (or guessed), and less */ \
84+
/* than n, it requires one atomic operation less. Therefore, */ \
85+
/* the caller should set *guess to whatever is the likely value */ \
86+
/* that the variable currently has, if such a guess is known. */ \
87+
\
88+
/* @return If the new value is changed to n, *guess is set to */ \
89+
/* the old value and the the function returns true. Otherwise, */ \
90+
/* *guess is set to the current value (which is greater than or */ \
91+
/* equal to n), and the function returns false. */ \
92+
bool atomic_set_to_max(TYPE n, TYPE *guess= NULL) \
93+
{ \
94+
TYPE _guess; \
95+
if (guess == NULL) \
96+
{ \
97+
_guess= n - 1; \
98+
guess= &_guess; \
99+
} \
100+
else \
101+
DBUG_ASSERT(*guess < n); \
102+
bool ret; \
103+
do { \
104+
ret= atomic_compare_and_swap(guess, n); \
105+
} while (!ret && *guess < n); \
106+
return ret; \
107+
} \
108+
\
109+
private: \
110+
volatile TYPE value; \
111+
}
112+
113+
DEFINE_ATOMIC_CLASS(int32, 32, int32);
114+
//DEFINE_ATOMIC_CLASS(int64, 64, int64);
115+
//DEFINE_ATOMIC_CLASS(pointer, ptr, void *);
116+
117+
#endif //ifndef ATOMIC_CLASS_H_INCLUDED

sql/binlog.cc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -740,8 +740,12 @@ class binlog_cache_mngr {
740740
thd->owned_gtid.sidno == THD::OWNED_SIDNO_ANONYMOUS)
741741
{
742742
thd->clear_owned_gtids();
743+
global_sid_lock->rdlock();
744+
gtid_state->release_anonymous_ownership();
745+
global_sid_lock->unlock();
743746
}
744747
}
748+
DEBUG_SYNC(thd, "after_flush_stm_cache_before_flush_trx_cache");
745749
if (int error= trx_cache.flush(thd, &trx_bytes, wrote_xid))
746750
return error;
747751
*bytes_written= stmt_bytes + trx_bytes;
@@ -1242,7 +1246,7 @@ int MYSQL_BIN_LOG::gtid_end_transaction(THD *thd)
12421246
}
12431247
else if (thd->owned_gtid.sidno == THD::OWNED_SIDNO_ANONYMOUS)
12441248
{
1245-
thd->clear_owned_gtids();
1249+
gtid_state->update_on_commit(thd);
12461250
}
12471251
DBUG_RETURN(0);
12481252
}

sql/rpl_gtid.h

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#endif
3131

3232
#include <list>
33+
#include "atomic_class.h"
3334

3435
using binary_log::Uuid;
3536
/**
@@ -2373,6 +2374,50 @@ class Gtid_state
23732374
@param thd Thread for which owned groups are updated.
23742375
*/
23752376
void update_on_rollback(THD *thd);
2377+
2378+
/**
2379+
Acquire anonymous ownership.
2380+
2381+
The caller must hold either sid_lock.rdlock or
2382+
sid_lock.wrlock. (The caller must have taken the lock and checked
2383+
that gtid_mode!=ON before calling this function, or else the
2384+
gtid_mode could have changed to ON by a concurrent SET GTID_MODE.)
2385+
*/
2386+
void acquire_anonymous_ownership()
2387+
{
2388+
DBUG_ENTER("Gtid_state::acquire_anonymous_ownership");
2389+
sid_lock->assert_some_lock();
2390+
DBUG_ASSERT(get_gtid_mode(GTID_MODE_LOCK_SID) != GTID_MODE_ON);
2391+
#ifndef DBUG_OFF
2392+
int32 old_value=
2393+
#endif
2394+
anonymous_gtid_count.atomic_add(1);
2395+
DBUG_PRINT("info", ("anonymous_gtid_count increased to %d", old_value + 1));
2396+
DBUG_ASSERT(old_value >= 0);
2397+
DBUG_VOID_RETURN;
2398+
}
2399+
2400+
/// Release anonymous ownership.
2401+
void release_anonymous_ownership()
2402+
{
2403+
DBUG_ENTER("Gtid_state::release_anonymous_ownership");
2404+
sid_lock->assert_some_lock();
2405+
DBUG_ASSERT(get_gtid_mode(GTID_MODE_LOCK_SID) != GTID_MODE_ON);
2406+
#ifndef DBUG_OFF
2407+
int32 old_value=
2408+
#endif
2409+
anonymous_gtid_count.atomic_add(-1);
2410+
DBUG_PRINT("info", ("anonymous_gtid_count decreased to %d", old_value - 1));
2411+
DBUG_ASSERT(old_value >= 1);
2412+
DBUG_VOID_RETURN;
2413+
}
2414+
2415+
/// Return the number of clients that hold anonymous ownership.
2416+
int32 get_anonymous_ownership_count()
2417+
{
2418+
return anonymous_gtid_count.atomic_get();
2419+
}
2420+
23762421
#endif // ifndef MYSQL_CLIENT
23772422
private:
23782423
/**
@@ -2706,6 +2751,9 @@ class Gtid_state
27062751
/// The SIDNO for this server.
27072752
rpl_sidno server_sidno;
27082753

2754+
/// The number of anonymous transactions owned by any client.
2755+
Atomic_int32 anonymous_gtid_count;
2756+
27092757
/// Used by unit tests that need to access private members.
27102758
#ifdef FRIEND_OF_GTID_STATE
27112759
friend FRIEND_OF_GTID_STATE;

sql/rpl_gtid_execution.cc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,11 @@ bool set_gtid_next(THD *thd, const Gtid_specification &spec)
7575
my_error(ER_CANT_SET_GTID_NEXT_TO_ANONYMOUS_WHEN_GTID_MODE_IS_ON, MYF(0));
7676
goto err;
7777
}
78-
// @todo WL#7083: increase atomic counter here
7978

80-
thd->owned_gtid.sidno= THD::OWNED_SIDNO_ANONYMOUS;
8179
thd->variables.gtid_next.set_anonymous();
80+
thd->owned_gtid.sidno= THD::OWNED_SIDNO_ANONYMOUS;
81+
thd->owned_gtid.gno= 0;
82+
gtid_state->acquire_anonymous_ownership();
8283
}
8384
else
8485
{

sql/rpl_gtid_state.cc

Lines changed: 100 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,49 @@ void Gtid_state::update_gtids_impl(THD *thd, bool is_commit)
179179
{
180180
DBUG_ENTER("Gtid_state::update_gtids_impl");
181181

182+
/*
183+
This variable is true for anonymous transactions, when the
184+
'transaction' has been split into multiple transactions in the
185+
binlog, and the present transaction is not the last one.
186+
187+
This means two things:
188+
189+
- We should not release anonymous ownership in case
190+
gtid_next=anonymous. If we did, it would be possible for user
191+
to set GTID_MODE=ON from a concurrent transaction, making it
192+
impossible to commit the current transaction.
193+
194+
- We should not decrease the counters for GTID-violating
195+
statements. If we did, it would be possible for a concurrent
196+
client to set ENFORCE_GTID_CONSISTENCY=ON despite there is an
197+
ongoing transaction that violates GTID consistency.
198+
199+
The flag is set in two cases:
200+
201+
1. We are committing the statement cache when there are more
202+
changes in the transaction cache.
203+
204+
This happens either because a single statement in the
205+
beginning of a transaction updates both transactional and
206+
non-transactional tables, or because we are committing a
207+
non-transactional update in the middle of a transaction when
208+
binlog_direct_non_transactional_updates=1.
209+
210+
In this case, the flag is set further down in this function.
211+
212+
2. The statement is one of the special statements that may
213+
generate multiple transactions: CREATE...SELECT, DROP TABLE,
214+
DROP DATABASE. See comment for THD::owned_gtid in
215+
sql/sql_class.h.
216+
217+
In this case, the THD::is_commit_in_middle_of_statement flag
218+
is set by the caller and the flag becomes true here.
219+
*/
220+
bool more_transactions_with_same_gtid_next=
221+
thd->is_commit_in_middle_of_statement;
222+
DBUG_PRINT("info", ("thd->is_commit_in_middle_of_statement=%d",
223+
thd->is_commit_in_middle_of_statement));
224+
182225
// Caller must take global_sid_lock.
183226
global_sid_lock->assert_some_lock();
184227

@@ -244,42 +287,72 @@ void Gtid_state::update_gtids_impl(THD *thd, bool is_commit)
244287
gtids_only_in_table._add_gtid(thd->owned_gtid);
245288
}
246289
}
290+
291+
broadcast_owned_sidnos(thd);
292+
unlock_owned_sidnos(thd);
293+
294+
thd->clear_owned_gtids();
295+
if (thd->variables.gtid_next.type == GTID_GROUP)
296+
{
297+
DBUG_ASSERT(!more_transactions_with_same_gtid_next);
298+
thd->variables.gtid_next.set_undefined();
299+
}
300+
else
301+
{
302+
/*
303+
Can be UNDEFINED for statements where
304+
gtid_pre_statement_checks skips the test for undefined,
305+
e.g. ROLLBACK.
306+
*/
307+
DBUG_ASSERT(thd->variables.gtid_next.type == AUTOMATIC_GROUP ||
308+
thd->variables.gtid_next.type == UNDEFINED_GROUP);
309+
}
247310
}
311+
else if (thd->owned_gtid.sidno == THD::OWNED_SIDNO_ANONYMOUS)
312+
{
313+
DBUG_ASSERT(thd->variables.gtid_next.type == ANONYMOUS_GROUP ||
314+
thd->variables.gtid_next.type == AUTOMATIC_GROUP);
315+
/*
316+
If there is more in the transaction cache, set
317+
more_transactions_with_same_gtid_next to indicate this.
248318
249-
/*
250-
There may be commands that cause implicit commits, e.g.
251-
SET AUTOCOMMIT=1 may cause the previous statements to commit
252-
without executing a COMMIT command or be on auto-commit mode.
253-
*/
254-
broadcast_owned_sidnos(thd);
255-
unlock_owned_sidnos(thd);
256-
if (thd->variables.gtid_next.type == GTID_GROUP)
257-
thd->variables.gtid_next.set_undefined();
258-
/*
259-
This early return prevents releasing anonymous ownership when a
260-
non-transactional statement is flushed to the binary log in the
261-
middle of a transaction. If we would release ownership in the
262-
middle of a transaction when gtid_next.type==ANONYMOUS_GROUP, it
263-
would be possible for a concurrent transaction to change GTID_MODE
264-
to ON in the middle of a transaction, making it impossible to
265-
commit.
266-
*/
267-
if (opt_bin_log && thd->variables.gtid_next.type == ANONYMOUS_GROUP)
319+
See comment for the declaration of
320+
more_transactions_with_same_gtid_next.
321+
*/
322+
if (opt_bin_log)
323+
{
324+
// Needed before is_binlog_cache_empty.
325+
thd->binlog_setup_trx_data();
326+
if (!thd->is_binlog_cache_empty(true))
327+
{
328+
more_transactions_with_same_gtid_next= true;
329+
DBUG_PRINT("info", ("Transaction cache is non-empty: setting "
330+
"more_transaction_with_same_gtid_next="
331+
"true."));
332+
}
333+
}
334+
if (!(more_transactions_with_same_gtid_next &&
335+
thd->variables.gtid_next.type == ANONYMOUS_GROUP))
336+
{
337+
release_anonymous_ownership();
338+
thd->clear_owned_gtids();
339+
}
340+
}
341+
else
268342
{
269-
// Needed before is_binlog_cache_empty.
270-
thd->binlog_setup_trx_data();
271-
if (!thd->is_binlog_cache_empty(true))
272-
DBUG_VOID_RETURN;
343+
// Nothing is owned. Then it must be a rollback of an automatic
344+
// transaction.
345+
DBUG_ASSERT(!is_commit);
346+
DBUG_ASSERT(thd->variables.gtid_next.type == AUTOMATIC_GROUP);
273347
}
274-
if (!(thd->variables.gtid_next.type == ANONYMOUS_GROUP &&
275-
thd->is_commit_in_middle_of_statement))
276-
thd->clear_owned_gtids();
348+
277349
thd->owned_gtid.dbug_print(NULL,
278350
"set owned_gtid (clear) in update_gtids_impl");
279351

280352
DBUG_VOID_RETURN;
281353
}
282354

355+
283356
int Gtid_state::wait_for_gtid_set(THD* thd, String* gtid_set_text, longlong timeout)
284357
{
285358
int error= 0;
@@ -440,6 +513,7 @@ enum_return_status Gtid_state::generate_automatic_gtid(THD *thd,
440513
// using an anonymous transaction.
441514
thd->owned_gtid.sidno= THD::OWNED_SIDNO_ANONYMOUS;
442515
thd->owned_gtid.gno= 0;
516+
acquire_anonymous_ownership();
443517
thd->owned_gtid.dbug_print(NULL,
444518
"set owned_gtid (anonymous) in generate_automatic_gtid");
445519
}

0 commit comments

Comments
 (0)