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