forked from vozbu/libslave
-
Notifications
You must be signed in to change notification settings - Fork 1
/
MysqlGuard.h
164 lines (144 loc) · 5.68 KB
/
MysqlGuard.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#ifndef __SLAVE_MYSQL_GUARD_H__
#define __SLAVE_MYSQL_GUARD_H__
#include <pthread.h>
#include <mutex>
#include <stdexcept>
#include <mysql/mysql.h>
namespace mysql_guard
{
// mysql_init wrapper that calls mysql_library_init(..) and mysql_thread_init(..)
// if necessary and schedules mysql_thread_end() and mysql_library_end() on
// thread/process exit.
// Actually calls MysqlGuard::init() and mysql_init().
inline MYSQL* mysql_safe_init(MYSQL *mysql);
// mysql_real_connect thread-safe wrapper.
// mysql_real_connect is not thread-safe now at least when using SSL cecurity.
// https://bugs.mysql.com/bug.php?id=88994
// Actually surrounds mysql_real_query(..) call with MysqlGuard::connectMutex() lock.
inline MYSQL* mysql_safe_connect(MYSQL* mysql, const char* host, const char* user,
const char* passwd, const char* db, unsigned int port,
const char* unix_socket, unsigned long client_flag);
class MysqlGuard
{
public:
/**
* Call mysql_library_init and mysql_thread_init if necessary
* (both must be called before any mysql_* call).
* Schedule mysql library cleanup at thread and/or process exit.
* Is already used by mysql_safe_init(..).
*/
static void init()
{
static Global sGlobal;
thread_local Local sLocal(sGlobal.m_DestructionKey);
};
/**
* mysql_real_connect is not thread-safe now at least when using SSL security.
* https://bugs.mysql.com/bug.php?id=88994
* Lock it before connect or use mysql_safe_connect(..) wrapper above.
*/
static std::mutex& connectMutex()
{
static std::mutex sConnectMutex;
return sConnectMutex;
}
private:
/**
* Global mysql_library_init initializer.
* Must have one instance as static variable.
* Calls mysql_library_end upon destruction.
*/
struct Global
{
// pthread local key for use in Local structure. See Local description.
pthread_key_t m_DestructionKey;
Global()
{
// Set destructor function for proper call of mysql_thread_end.
// See Local description.
if (0 != pthread_key_create(&m_DestructionKey, Local::threadDestructor))
throw std::runtime_error("pthread_key_create for mysql init returned an error");
if (0 != mysql_library_init(0, nullptr, nullptr))
{
pthread_key_delete(m_DestructionKey);
throw std::runtime_error("mysql_library_init returned an error");
}
}
~Global()
{
mysql_library_end();
pthread_key_delete(m_DestructionKey);
}
};
/**
* Thread-local mysql_thread_init initializer.
* Must have one instance as thread_local variable.
* Calls mysql_thread_end if necessary upon destruction.
* There is a problem with automatic mysql thread deinitialization:
* https://bugs.mysql.com/bug.php?id=66740
* First of all, mysql_thread_end must be called in every thread (that called
* mysql_thread_init) before mysql_library_end, or 5 sec lag and warning will occur.
* Second, mysql_thread_end relies on some pthread specific record, so
* mysql_thread_end must be called before pthread specific vector is cleaned.
* On some platforms, this vector is cleaned BEFORE calling of C++ thread_local
* destructors (this order is not specified though). Thus we cannot rely on
* destructor of a thread local variable and have to use pthread destructor
* function for calling mysql_thread_end in proper time.
* On the other hand there is no guarantee that pthread destructor will be
* called before destruction of static objects, so we have to install
* mysql thread cleaner in destructor of thread_local object too.
*/
struct Local
{
// Prevents calling mysql_thread_end twice.
bool m_NeedsCleanup = true;
// pthread local key for pthread destructor.
pthread_key_t& m_DestructionKey;
// Call mysql_thread_end if mysql_thread_init was called previously.
void destroyIfNecessary()
{
if (m_NeedsCleanup)
{
mysql_thread_end();
m_NeedsCleanup = false;
pthread_setspecific(m_DestructionKey, nullptr);
}
}
// destroyIfNecessary wrapper for passing to pthread_key_create.
static void threadDestructor(void* arg)
{
Local* sLocal = (Local*)arg; // C-style cast for a C-style void*.
sLocal->destroyIfNecessary();
}
Local(pthread_key_t& aDestructionKey)
: m_DestructionKey(aDestructionKey)
{
if (0 != pthread_setspecific(m_DestructionKey, this))
throw std::runtime_error("pthread_setspecific for mysql init returned an error");
if (0 != mysql_thread_init())
{
pthread_setspecific(m_DestructionKey, nullptr);
throw std::runtime_error("mysql_thread_init returned an error");
}
}
~Local()
{
destroyIfNecessary();
}
};
};
MYSQL *mysql_safe_init(MYSQL *mysql)
{
MysqlGuard::init();
return mysql_init(mysql);
}
MYSQL* mysql_safe_connect(MYSQL* mysql, const char* host, const char* user,
const char* passwd, const char* db, unsigned int port,
const char* unix_socket, unsigned long client_flag)
{
std::lock_guard<std::mutex> lock(MysqlGuard::connectMutex());
return mysql_real_connect(mysql, host, user, passwd, db, port, unix_socket,
client_flag);
}
} // namespace mysql_guard
#endif