debug tags updated
[public/netxms.git] / src / db / libnxdb / dbcp.cpp
CommitLineData
4cd1e46b
AK
1/*
2** NetXMS - Network Management System
3** Database Abstraction Library
e3c5f43a 4** Copyright (C) 2008-2015 Raden Solutions
4cd1e46b
AK
5**
6** This program is free software; you can redistribute it and/or modify
7** it under the terms of the GNU Lesser General Public License as published by
8** the Free Software Foundation; either version 3 of the License, or
9** (at your option) any later version.
10**
11** This program is distributed in the hope that it will be useful,
12** but WITHOUT ANY WARRANTY; without even the implied warranty of
13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14** GNU General Public License for more details.
15**
16** You should have received a copy of the GNU Lesser General Public License
17** along with this program; if not, write to the Free Software
18** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19**
20** File: dbcp.cpp
21**
22**/
23
24#include "libnxdb.h"
25
9bd1bace 26static bool s_initialized = false;
4cd1e46b
AK
27static DB_DRIVER m_driver;
28static TCHAR m_server[256];
29static TCHAR m_login[256];
30static TCHAR m_password[256];
31static TCHAR m_dbName[256];
32static TCHAR m_schema[256];
33
34static int m_basePoolSize;
35static int m_maxPoolSize;
36static int m_cooldownTime;
37static int m_connectionTTL;
38
39static MUTEX m_poolAccessMutex = INVALID_MUTEX_HANDLE;
40static ObjectArray<PoolConnectionInfo> m_connections;
4cd1e46b
AK
41static THREAD m_maintThread = INVALID_THREAD_HANDLE;
42static CONDITION m_condShutdown = INVALID_CONDITION_HANDLE;
a62e0a33 43static CONDITION m_condRelease = INVALID_CONDITION_HANDLE;
4cd1e46b 44
a13efc48
VK
45#define DEBUG_TAG _T("db.cpool")
46
4cd1e46b
AK
47/**
48 * Create connections on pool initialization
49 */
9bd1bace 50static bool DBConnectionPoolPopulate()
4cd1e46b
AK
51{
52 TCHAR errorText[DBDRV_MAX_ERROR_TEXT];
9bd1bace 53 bool success = false;
4cd1e46b
AK
54
55 MutexLock(m_poolAccessMutex);
56 for(int i = 0; i < m_basePoolSize; i++)
57 {
58 PoolConnectionInfo *conn = new PoolConnectionInfo;
59 conn->handle = DBConnect(m_driver, m_server, m_dbName, m_login, m_password, m_schema, errorText);
60 if (conn->handle != NULL)
61 {
62 conn->inUse = false;
63 conn->connectTime = time(NULL);
64 conn->lastAccessTime = conn->connectTime;
e3c5f43a 65 conn->usageCount = 0;
9c133761
VK
66 conn->srcFile[0] = 0;
67 conn->srcLine = 0;
4cd1e46b 68 m_connections.add(conn);
9bd1bace 69 success = true;
4cd1e46b
AK
70 }
71 else
72 {
a13efc48 73 nxlog_debug_tag(DEBUG_TAG, 3, _T("Cannot create DB connection %d (%s)"), i, errorText);
ab644efe 74 delete conn;
4cd1e46b
AK
75 }
76 }
77 MutexUnlock(m_poolAccessMutex);
9bd1bace 78 return success;
4cd1e46b
AK
79}
80
81/**
82 * Shrink connection pool up to base size when possible
83 */
84static void DBConnectionPoolShrink()
85{
86 MutexLock(m_poolAccessMutex);
87
88 time_t now = time(NULL);
89 for(int i = m_basePoolSize; i < m_connections.size(); i++)
90 {
91 PoolConnectionInfo *conn = m_connections.get(i);
92 if (!conn->inUse && (now - conn->lastAccessTime > m_cooldownTime))
93 {
94 DBDisconnect(conn->handle);
95 m_connections.remove(i);
96 i--;
97 }
98 }
99
100 MutexUnlock(m_poolAccessMutex);
101}
102
e3c5f43a
VK
103/*
104 * Reset connection
105 */
106static bool ResetConnection(PoolConnectionInfo *conn)
107{
108 time_t now = time(NULL);
109 DBDisconnect(conn->handle);
110
111 TCHAR errorText[DBDRV_MAX_ERROR_TEXT];
112 conn->handle = DBConnect(m_driver, m_server, m_dbName, m_login, m_password, m_schema, errorText);
113 if (conn->handle != NULL)
114 {
115 conn->connectTime = now;
116 conn->lastAccessTime = now;
117 conn->usageCount = 0;
118
a13efc48 119 nxlog_debug_tag(DEBUG_TAG, 3, _T("Connection %p reconnected"), conn->handle);
e3c5f43a
VK
120 return true;
121 }
122 else
123 {
a13efc48 124 nxlog_debug_tag(DEBUG_TAG, 3, _T("Connection %p reconnect failure (%s)"), conn->handle, errorText);
e3c5f43a
VK
125 return false;
126 }
127}
128
4cd1e46b 129/**
e3c5f43a 130 * Callback for sorting reset list
4cd1e46b 131 */
fa01c3b6 132static int ResetListSortCallback(const PoolConnectionInfo **e1, const PoolConnectionInfo **e2)
e3c5f43a 133{
fa01c3b6 134 return (*e1)->usageCount > (*e2)->usageCount ? -1 : ((*e1)->usageCount == (*e2)->usageCount ? 0 : 1);
e3c5f43a
VK
135}
136
137/**
138 * Reset expired connections
139 */
140static void ResetExpiredConnections()
4cd1e46b
AK
141{
142 time_t now = time(NULL);
4cd1e46b 143
e3c5f43a 144 MutexLock(m_poolAccessMutex);
4cd1e46b 145
5f22dd66 146 int i, availCount = 0;
e3c5f43a 147 ObjectArray<PoolConnectionInfo> reconnList(m_connections.size(), 16, false);
5f22dd66 148 for(i = 0; i < m_connections.size(); i++)
e3c5f43a
VK
149 {
150 PoolConnectionInfo *conn = m_connections.get(i);
151 if (!conn->inUse)
152 {
153 availCount++;
154 if (now - conn->connectTime > m_connectionTTL)
4cd1e46b 155 {
e3c5f43a 156 reconnList.add(conn);
4cd1e46b 157 }
4cd1e46b 158 }
e3c5f43a
VK
159 }
160
78032263 161 int count = std::min(availCount / 2 + 1, reconnList.size()); // reset no more than 50% of available connections
e3c5f43a
VK
162 if (count < reconnList.size())
163 {
164 reconnList.sort(ResetListSortCallback);
165 while(reconnList.size() > count)
166 reconnList.remove(count);
4cd1e46b 167 }
e3c5f43a 168
5f22dd66 169 for(i = 0; i < count; i++)
e3c5f43a 170 reconnList.get(i)->inUse = true;
4cd1e46b 171 MutexUnlock(m_poolAccessMutex);
e3c5f43a
VK
172
173 // do reconnects
5f22dd66 174 for(i = 0; i < count; i++)
e3c5f43a
VK
175 {
176 PoolConnectionInfo *conn = reconnList.get(i);
177 bool success = ResetConnection(conn);
178 MutexLock(m_poolAccessMutex);
179 if (success)
180 {
181 conn->inUse = false;
182 }
183 else
184 {
185 m_connections.remove(conn);
186 }
187 MutexUnlock(m_poolAccessMutex);
188 }
4cd1e46b
AK
189}
190
191/**
192 * Pool maintenance thread
193 */
194static THREAD_RESULT THREAD_CALL MaintenanceThread(void *arg)
195{
930a2a62 196 ThreadSetName("DBPoolMaint");
a13efc48 197 nxlog_debug_tag(DEBUG_TAG, 1, _T("Database Connection Pool maintenance thread started"));
4cd1e46b 198
e3c5f43a 199 while(!ConditionWait(m_condShutdown, (m_connectionTTL > 0) ? m_connectionTTL * 750 : 300000))
4cd1e46b
AK
200 {
201 DBConnectionPoolShrink();
202 if (m_connectionTTL > 0)
203 {
e3c5f43a 204 ResetExpiredConnections();
4cd1e46b
AK
205 }
206 }
207
a13efc48 208 nxlog_debug_tag(DEBUG_TAG, 1, _T("Database Connection Pool maintenance thread stopped"));
4cd1e46b
AK
209 return THREAD_OK;
210}
211
212/**
213 * Start connection pool
214 */
215bool LIBNXDB_EXPORTABLE DBConnectionPoolStartup(DB_DRIVER driver, const TCHAR *server, const TCHAR *dbName,
216 const TCHAR *login, const TCHAR *password, const TCHAR *schema,
217 int basePoolSize, int maxPoolSize, int cooldownTime,
14228410 218 int connTTL)
4cd1e46b 219{
9bd1bace
VK
220 if (s_initialized)
221 return true; // already initialized
222
4cd1e46b
AK
223 m_driver = driver;
224 nx_strncpy(m_server, CHECK_NULL_EX(server), 256);
225 nx_strncpy(m_dbName, CHECK_NULL_EX(dbName), 256);
226 nx_strncpy(m_login, CHECK_NULL_EX(login), 256);
227 nx_strncpy(m_password, CHECK_NULL_EX(password), 256);
228 nx_strncpy(m_schema, CHECK_NULL_EX(schema), 256);
229
230 m_basePoolSize = basePoolSize;
231 m_maxPoolSize = maxPoolSize;
232 m_cooldownTime = cooldownTime;
233 m_connectionTTL = connTTL;
4cd1e46b
AK
234
235 m_poolAccessMutex = MutexCreate();
236 m_connections.setOwner(true);
237 m_condShutdown = ConditionCreate(TRUE);
a62e0a33 238 m_condRelease = ConditionCreate(FALSE);
4cd1e46b 239
9bd1bace
VK
240 if (!DBConnectionPoolPopulate())
241 {
242 // cannot open at least one connection
243 ConditionDestroy(m_condShutdown);
244 ConditionDestroy(m_condRelease);
245 MutexDestroy(m_poolAccessMutex);
246 return false;
247 }
4cd1e46b
AK
248
249 m_maintThread = ThreadCreateEx(MaintenanceThread, 0, NULL);
250
9bd1bace 251 s_initialized = true;
a13efc48 252 nxlog_debug_tag(DEBUG_TAG, 1, _T("Database Connection Pool initialized"));
4cd1e46b
AK
253
254 return true;
255}
256
257/**
258 * Shutdown connection pool
259 */
260void LIBNXDB_EXPORTABLE DBConnectionPoolShutdown()
261{
9bd1bace
VK
262 if (!s_initialized)
263 return;
264
4cd1e46b
AK
265 ConditionSet(m_condShutdown);
266 ThreadJoin(m_maintThread);
267
268 ConditionDestroy(m_condShutdown);
a62e0a33 269 ConditionDestroy(m_condRelease);
4cd1e46b
AK
270 MutexDestroy(m_poolAccessMutex);
271
272 for(int i = 0; i < m_connections.size(); i++)
273 {
274 DBDisconnect(m_connections.get(i)->handle);
275 }
276
277 m_connections.clear();
278
9bd1bace 279 s_initialized = false;
a13efc48 280 nxlog_debug_tag(DEBUG_TAG, 1, _T("Database Connection Pool terminated"));
4cd1e46b
AK
281}
282
283/**
284 * Acquire connection from pool. This function never fails - if it's impossible to acquire
a62e0a33 285 * pooled connection, calling thread will be suspended until there will be connection available.
4cd1e46b 286 */
9c133761 287DB_HANDLE LIBNXDB_EXPORTABLE __DBConnectionPoolAcquireConnection(const char *srcFile, int srcLine)
4cd1e46b 288{
a62e0a33 289retry:
4cd1e46b
AK
290 MutexLock(m_poolAccessMutex);
291
292 DB_HANDLE handle = NULL;
e3c5f43a
VK
293
294 // find less used connection
295 UINT32 count = 0xFFFFFFFF;
296 int index = -1;
297 for(int i = 0; (i < m_connections.size()) && (count > 0); i++)
4cd1e46b
AK
298 {
299 PoolConnectionInfo *conn = m_connections.get(i);
e3c5f43a
VK
300 if (!conn->inUse && (conn->usageCount < count))
301 {
302 count = conn->usageCount;
303 index = i;
4cd1e46b
AK
304 }
305 }
306
e3c5f43a
VK
307 if (index > -1)
308 {
309 PoolConnectionInfo *conn = m_connections.get(index);
310 handle = conn->handle;
311 conn->inUse = true;
312 conn->lastAccessTime = time(NULL);
313 conn->usageCount++;
314 strncpy(conn->srcFile, srcFile, 128);
315 conn->srcLine = srcLine;
316 }
317 else if (m_connections.size() < m_maxPoolSize)
4cd1e46b
AK
318 {
319 TCHAR errorText[DBDRV_MAX_ERROR_TEXT];
320 PoolConnectionInfo *conn = new PoolConnectionInfo;
321 conn->handle = DBConnect(m_driver, m_server, m_dbName, m_login, m_password, m_schema, errorText);
ab644efe
VK
322 if (conn->handle != NULL)
323 {
324 conn->inUse = true;
325 conn->connectTime = time(NULL);
326 conn->lastAccessTime = conn->connectTime;
e3c5f43a 327 conn->usageCount = 0;
9c133761
VK
328 strncpy(conn->srcFile, srcFile, 128);
329 conn->srcLine = srcLine;
ab644efe
VK
330 m_connections.add(conn);
331 handle = conn->handle;
332 }
333 else
334 {
a13efc48 335 nxlog_debug_tag(DEBUG_TAG, 3, _T("Cannot create additional DB connection (%s)"), errorText);
ab644efe
VK
336 delete conn;
337 }
4cd1e46b
AK
338 }
339
340 MutexUnlock(m_poolAccessMutex);
341
342 if (handle == NULL)
343 {
a13efc48 344 nxlog_debug_tag(DEBUG_TAG, 1, _T("Database connection pool exhausted (call from %hs:%d)"), srcFile, srcLine);
a62e0a33 345 ConditionWait(m_condRelease, 10000);
a13efc48 346 nxlog_debug_tag(DEBUG_TAG, 5, _T("Retry acquire connection (call from %hs:%d)"), srcFile, srcLine);
a62e0a33 347 goto retry;
4cd1e46b
AK
348 }
349
a13efc48 350 nxlog_debug_tag(DEBUG_TAG, 7, _T("Handle %p acquired (call from %hs:%d)"), handle, srcFile, srcLine);
4cd1e46b
AK
351 return handle;
352}
353
354/**
355 * Release acquired connection
356 */
357void LIBNXDB_EXPORTABLE DBConnectionPoolReleaseConnection(DB_HANDLE handle)
358{
4cd1e46b
AK
359 MutexLock(m_poolAccessMutex);
360
361 for(int i = 0; i < m_connections.size(); i++)
362 {
363 PoolConnectionInfo *conn = m_connections.get(i);
364 if (conn->handle == handle)
365 {
366 conn->inUse = false;
367 conn->lastAccessTime = time(NULL);
9c133761
VK
368 conn->srcFile[0] = 0;
369 conn->srcLine = 0;
4cd1e46b
AK
370 break;
371 }
372 }
373
374 MutexUnlock(m_poolAccessMutex);
a62e0a33 375
a13efc48 376 nxlog_debug_tag(DEBUG_TAG, 7, _T("Handle %p released"), handle);
a62e0a33 377 ConditionPulse(m_condRelease);
4cd1e46b
AK
378}
379
380/**
381 * Get current size of DB connection pool
382 */
383int LIBNXDB_EXPORTABLE DBConnectionPoolGetSize()
384{
385 MutexLock(m_poolAccessMutex);
386 int size = m_connections.size();
387 MutexUnlock(m_poolAccessMutex);
388 return size;
389}
390
391/**
392 * Get number of acquired connections in DB connection pool
393 */
394int LIBNXDB_EXPORTABLE DBConnectionPoolGetAcquiredCount()
395{
396 int count = 0;
397 MutexLock(m_poolAccessMutex);
398 for(int i = 0; i < m_connections.size(); i++)
399 if (m_connections.get(i)->inUse)
400 count++;
401 MutexUnlock(m_poolAccessMutex);
402 return count;
403}
9c133761
VK
404
405/**
406 * Get copy of active DB connections.
407 * Returned list must be deleted by the caller.
408 */
409ObjectArray<PoolConnectionInfo> LIBNXDB_EXPORTABLE *DBConnectionPoolGetConnectionList()
410{
411 ObjectArray<PoolConnectionInfo> *list = new ObjectArray<PoolConnectionInfo>(32, 32, true);
412 MutexLock(m_poolAccessMutex);
413 for(int i = 0; i < m_connections.size(); i++)
414 if (m_connections.get(i)->inUse)
415 {
416 list->add((PoolConnectionInfo *)nx_memdup(m_connections.get(i), sizeof(PoolConnectionInfo)));
417 }
418 MutexUnlock(m_poolAccessMutex);
419 return list;
420}