Coverage Report

Created: 2025-08-28 06:57

/src/MapServer/src/mappool.c
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 * $Id$
3
 *
4
 * Project:  MapServer
5
 * Purpose:  Implement new style connection pooling.
6
 * Author:   Frank Warmerdam, warmerdam@pobox.com
7
 *
8
 ******************************************************************************
9
 * Copyright (c) 1996-2005 Regents of the University of Minnesota.
10
 *
11
 * Permission is hereby granted, free of charge, to any person obtaining a
12
 * copy of this software and associated documentation files (the "Software"),
13
 * to deal in the Software without restriction, including without limitation
14
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15
 * and/or sell copies of the Software, and to permit persons to whom the
16
 * Software is furnished to do so, subject to the following conditions:
17
 *
18
 * The above copyright notice and this permission notice shall be included in
19
 * all copies of this Software or works derived from this Software.
20
 *
21
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27
 * DEALINGS IN THE SOFTWARE.
28
 ******************************************************************************
29
30
             New MapServer Connection Pooling
31
             ================================
32
33
This attempts to describe how the new connection pooling support (introduced
34
Sept/2004) works and what the maintainer of a connection type needs to do
35
to take advantage of it.
36
37
First, the new connection pooling support makes the assumption that
38
a connection can be abstracted as a void pointer.  Further it assumes that
39
the connection identify is unique defined by the connection string and
40
the connection type on layers.  So if two layers have the same connection
41
type and connection string that they can share a connection handle.
42
43
The old connection sharing was based on searching previous layers in the
44
same map for a layer to copy an existing connection from.  That was ok for
45
sharing connection between layers in the same map, but didn't address the
46
need to share connections over a longer term as is the case in a FastCGI
47
situation where the cgi is long running and handles many CGI requests.
48
49
The mapool.c code essentially maintains a cache of open connections with the
50
following information for each:
51
  - connectiontype / connection
52
  - connection handle
53
  - reference count
54
  - life span indicator
55
  - callback function to close the connection.
56
57
The life span indicator is controlled by the CLOSE_CONNECTION PROCESSING
58
option on the layer(s).  If this is set to NORMAL (or not set at all) the
59
connection will be closed as soon as the reference count drops to zero.  This
60
is MS_LIFE_ZEROREF, and in normal use results in connections only be shared
61
between layers in a given map as the connection will be closed when no layers
62
are open against it anymore.  The other possible value for the CLOSE_CONNECTION
63
option is DEFER which basically results in the connection being kept open
64
till the application closes (msCleanup() will ensure the connection is closed
65
on exit if called).  This case is called MS_LIFE_FOREVER.
66
The CLOSE_CONNECTION=ALWAYS setting provides to suppress the connection pooling
67
for a particular layer. In this case the MS_LIFE_SINGLE setting is used, which
68
ensures that a new connection is created for each request and it is always
69
closed when the connection is released. This kind of connection cannot be
70
reused.
71
72
The callback is a function provided with the connection handle when it is
73
registered.  It takes a single "void *" argument which is the connection
74
handle.
75
76
Updating a Driver
77
-----------------
78
79
The following are the steps to ensure a particular format/database supports
80
connection pooling (ie. mappostgis.c, maporacle.c, mapogr.cpp, etc)
81
We will use POSTGIS names for convenience.
82
83
1) in msPOSTGISLayerOpen() call msConnPoolRequest(layer) in order to get
84
   a database connection.
85
86
        layerinfo->conn = (PGconn *) msConnPoolRequest( layer );
87
88
2) In msPOSTGISLayerOpen(): if msConnPoolRequest() returned NULL then
89
   manually open a connection to the database (ie. PQconnectcb()) and then
90
   register this handle with the pool API by calling msmsConnPoolRegister().
91
92
      layerinfo->conn = PQconnectdb( layer->connection );
93
94
            if (PQstatus(layerinfo->conn) == CONNECTION_BAD)
95
               <report error>
96
            else
97
                msConnPoolRegister( layer, layerinfo->conn,
98
                                    msPOSTGISCloseConnection );
99
100
3) Implement a callback function (msPOSTGISCloseConnection) that the
101
   connection pooling API can call when it wants to close the connection.
102
103
   static void msPOSTGISCloseConnection( void *conn_handle )
104
105
   {
106
       PQfinish( (PGconn*) conn_handle );
107
   }
108
109
4) In msPOSTGISLayerClose() release the connection handle instead of
110
   directly closing it.
111
112
            msConnPoolRelease( layer, layerinfo->conn );
113
            layerinfo->conn = NULL;
114
115
5) If there was any use of msCheckConnection() or the "sameconnection"
116
   member of the layerObj, then old style connection pooling is already
117
   present.  Remove it.
118
119
That's it!
120
121
Other Notes
122
-----------
123
124
o The connection pooling API will report details about connection
125
  registrations, requests, releases and closes if the layer debug flag is
126
  on for the layers in question.
127
128
o The connection pooling API will let a connection be used/referenced multiple
129
  times from a single thread, but will not allow a connection to be shared
130
  between different threads concurrently.  But if a connection is released
131
  by one thread, it is available for use by another thread.
132
133
 ****************************************************************************/
134
135
#include "mapserver.h"
136
#include "mapthread.h"
137
138
/* defines for lifetime.
139
   A positive number is a time-from-last use in seconds */
140
141
0
#define MS_LIFE_FOREVER -1
142
0
#define MS_LIFE_ZEROREF -2
143
0
#define MS_LIFE_SINGLE -3
144
145
typedef struct {
146
  enum MS_CONNECTION_TYPE connectiontype;
147
  char *connection;
148
149
  int lifespan;
150
  int ref_count;
151
  void *thread_id;
152
  int debug;
153
154
  time_t last_used;
155
156
  void *conn_handle;
157
158
  void (*close)(void *);
159
} connectionObj;
160
161
/*
162
** These static structures are protected by the TLOCK_POOL mutex.
163
*/
164
165
static int connectionCount = 0;
166
static int connectionMax = 0;
167
static connectionObj *connections = NULL;
168
169
/************************************************************************/
170
/*                         msConnPoolRegister()                         */
171
/*                                                                      */
172
/*      Register a new connection with the connection pool tracker.     */
173
/************************************************************************/
174
175
void msConnPoolRegister(layerObj *layer, void *conn_handle,
176
                        void (*close_func)(void *))
177
178
0
{
179
0
  const char *close_connection = NULL;
180
0
  connectionObj *conn = NULL;
181
182
0
  if (layer->debug)
183
0
    msDebug("msConnPoolRegister(%s,%s,%p)\n", layer->name, layer->connection,
184
0
            conn_handle);
185
186
  /* -------------------------------------------------------------------- */
187
  /*      We can't meaningful keep a connection with no connection or     */
188
  /*      connection type string on the layer.                            */
189
  /* -------------------------------------------------------------------- */
190
0
  if (layer->connection == NULL) {
191
0
    if (layer->tileindex != NULL && layer->connectiontype == MS_OGR) {
192
      /* this is ok, no need to make a fuss */
193
0
    } else {
194
0
      msDebug("%s: Missing CONNECTION on layer %s.\n", "msConnPoolRegister()",
195
0
              layer->name);
196
197
0
      msSetError(MS_MISCERR, "Missing CONNECTION on layer %s.",
198
0
                 "msConnPoolRegister()", layer->name);
199
0
    }
200
0
    return;
201
0
  }
202
203
  /* -------------------------------------------------------------------- */
204
  /*      Grow the array of connection information objects if needed.     */
205
  /* -------------------------------------------------------------------- */
206
0
  msAcquireLock(TLOCK_POOL);
207
208
0
  if (connectionCount == connectionMax) {
209
0
    connectionMax += 10;
210
0
    connectionObj *newConnections = (connectionObj *)realloc(
211
0
        connections, sizeof(connectionObj) * connectionMax);
212
0
    if (newConnections == NULL) {
213
0
      msSetError(MS_MEMERR, NULL, "msConnPoolRegister()");
214
0
      msReleaseLock(TLOCK_POOL);
215
0
      return;
216
0
    }
217
0
    connections = newConnections;
218
0
  }
219
220
  /* -------------------------------------------------------------------- */
221
  /*      Set the new connection information.                             */
222
  /* -------------------------------------------------------------------- */
223
0
  conn = connections + connectionCount;
224
225
0
  connectionCount++;
226
227
0
  conn->connectiontype = layer->connectiontype;
228
0
  conn->connection = msStrdup(layer->connection);
229
0
  conn->close = close_func;
230
0
  conn->ref_count = 1;
231
0
  conn->thread_id = msGetThreadId();
232
0
  conn->last_used = time(NULL);
233
0
  conn->conn_handle = conn_handle;
234
0
  conn->debug = layer->debug;
235
236
  /* -------------------------------------------------------------------- */
237
  /*      Categorize the connection handling information.                 */
238
  /* -------------------------------------------------------------------- */
239
0
  close_connection = msLayerGetProcessingKey(layer, "CLOSE_CONNECTION");
240
241
0
  if (close_connection == NULL)
242
0
    close_connection = "NORMAL";
243
244
0
  if (strcasecmp(close_connection, "NORMAL") == 0)
245
0
    conn->lifespan = MS_LIFE_ZEROREF;
246
0
  else if (strcasecmp(close_connection, "DEFER") == 0)
247
0
    conn->lifespan = MS_LIFE_FOREVER;
248
0
  else if (strcasecmp(close_connection, "ALWAYS") == 0)
249
0
    conn->lifespan = MS_LIFE_SINGLE;
250
0
  else {
251
0
    msDebug("msConnPoolRegister(): "
252
0
            "Unrecognised CLOSE_CONNECTION value '%s'\n",
253
0
            close_connection);
254
255
0
    msSetError(MS_MISCERR, "Unrecognised CLOSE_CONNECTION value '%s'",
256
0
               "msConnPoolRegister()", close_connection);
257
0
    conn->lifespan = MS_LIFE_ZEROREF;
258
0
  }
259
260
0
  msReleaseLock(TLOCK_POOL);
261
0
}
262
263
/************************************************************************/
264
/*                          msConnPoolClose()                           */
265
/*                                                                      */
266
/*      Close the indicated connection.  The index in the connection    */
267
/*      table is passed.  Remove the connection from the table as       */
268
/*      well.                                                           */
269
/************************************************************************/
270
271
static void msConnPoolClose(int conn_index)
272
273
0
{
274
0
  connectionObj *conn = connections + conn_index;
275
276
0
  if (conn->ref_count > 0) {
277
0
    if (conn->debug)
278
0
      msDebug("msConnPoolClose(): "
279
0
              "Closing connection %s even though ref_count=%d.\n",
280
0
              conn->connection, conn->ref_count);
281
282
0
    msSetError(MS_MISCERR, "Closing connection %s even though ref_count=%d.",
283
0
               "msConnPoolClose()", conn->connection, conn->ref_count);
284
0
  }
285
286
0
  if (conn->debug)
287
0
    msDebug("msConnPoolClose(%s,%p)\n", conn->connection, conn->conn_handle);
288
289
0
  if (conn->close != NULL)
290
0
    conn->close(conn->conn_handle);
291
292
  /* free malloced() stuff in this connection */
293
0
  free(conn->connection);
294
295
0
  connectionCount--;
296
0
  if (connectionCount == 0) {
297
    /* if there are no connections left we will "cleanup".  */
298
0
    connectionMax = 0;
299
0
    free(connections);
300
0
    connections = NULL;
301
0
  } else {
302
    /* move the last connection in place of our now closed one */
303
0
    memcpy(connections + conn_index, connections + connectionCount,
304
0
           sizeof(connectionObj));
305
0
  }
306
0
}
307
308
/************************************************************************/
309
/*                         msConnPoolRequest()                          */
310
/*                                                                      */
311
/*      Ask for a connection from the connection pool for use with      */
312
/*      the current layer.  If found (CONNECTION and CONNECTIONTYPE     */
313
/*      match) then return it and up the ref count.  Otherwise          */
314
/*      return NULL.                                                    */
315
/************************************************************************/
316
317
void *msConnPoolRequest(layerObj *layer)
318
319
0
{
320
0
  int i;
321
0
  const char *close_connection;
322
323
0
  if (layer->connection == NULL)
324
0
    return NULL;
325
326
  /* check if we must always create a new connection */
327
0
  close_connection = msLayerGetProcessingKey(layer, "CLOSE_CONNECTION");
328
0
  if (close_connection && strcasecmp(close_connection, "ALWAYS") == 0)
329
0
    return NULL;
330
331
0
  msAcquireLock(TLOCK_POOL);
332
0
  for (i = 0; i < connectionCount; i++) {
333
0
    connectionObj *conn = connections + i;
334
335
0
    if (layer->connectiontype == conn->connectiontype &&
336
0
        strcasecmp(layer->connection, conn->connection) == 0 &&
337
0
        (conn->ref_count == 0 || conn->thread_id == msGetThreadId()) &&
338
0
        conn->lifespan != MS_LIFE_SINGLE) {
339
0
      void *conn_handle = NULL;
340
341
0
      conn->ref_count++;
342
0
      conn->thread_id = msGetThreadId();
343
0
      conn->last_used = time(NULL);
344
345
0
      if (layer->debug) {
346
0
        msDebug("msConnPoolRequest(%s,%s) -> got %p\n", layer->name,
347
0
                layer->connection, conn->conn_handle);
348
0
        conn->debug = layer->debug;
349
0
      }
350
351
0
      conn_handle = conn->conn_handle;
352
353
0
      msReleaseLock(TLOCK_POOL);
354
0
      return conn_handle;
355
0
    }
356
0
  }
357
358
0
  msReleaseLock(TLOCK_POOL);
359
360
0
  return NULL;
361
0
}
362
363
/************************************************************************/
364
/*                         msConnPoolRelease()                          */
365
/*                                                                      */
366
/*      Release the passed connection for the given layer.              */
367
/*      Internally the reference count is dropped, and the              */
368
/*      connection may be closed.  We assume the caller has already     */
369
/*      acquired the pool lock.                                         */
370
/************************************************************************/
371
372
void msConnPoolRelease(layerObj *layer, void *conn_handle)
373
374
0
{
375
0
  int i;
376
377
0
  if (layer->debug)
378
0
    msDebug("msConnPoolRelease(%s,%s,%p)\n", layer->name, layer->connection,
379
0
            conn_handle);
380
381
0
  if (layer->connection == NULL)
382
0
    return;
383
384
0
  msAcquireLock(TLOCK_POOL);
385
0
  for (i = 0; i < connectionCount; i++) {
386
0
    connectionObj *conn = connections + i;
387
388
0
    if (layer->connectiontype == conn->connectiontype &&
389
0
        strcasecmp(layer->connection, conn->connection) == 0 &&
390
0
        conn->conn_handle == conn_handle) {
391
0
      conn->ref_count--;
392
0
      conn->last_used = time(NULL);
393
394
0
      if (conn->ref_count == 0)
395
0
        conn->thread_id = 0;
396
397
0
      if (conn->ref_count == 0 && (conn->lifespan == MS_LIFE_ZEROREF ||
398
0
                                   conn->lifespan == MS_LIFE_SINGLE))
399
0
        msConnPoolClose(i);
400
401
0
      msReleaseLock(TLOCK_POOL);
402
0
      return;
403
0
    }
404
0
  }
405
406
0
  msReleaseLock(TLOCK_POOL);
407
408
0
  msDebug("%s: Unable to find handle for layer '%s'.\n", "msConnPoolRelease()",
409
0
          layer->name);
410
411
0
  msSetError(MS_MISCERR, "Unable to find handle for layer '%s'.",
412
0
             "msConnPoolRelease()", layer->name);
413
0
}
414
415
/************************************************************************/
416
/*                   msConnPoolMapCloseUnreferenced()                   */
417
/*                                                                      */
418
/*      Close any unreferenced connections.                             */
419
/************************************************************************/
420
421
void msConnPoolCloseUnreferenced()
422
423
0
{
424
0
  int i;
425
426
  /* this really needs to be commented out before committing.  */
427
  /* msDebug( "msConnPoolCloseUnreferenced()\n" ); */
428
429
0
  msAcquireLock(TLOCK_POOL);
430
0
  for (i = connectionCount - 1; i >= 0; i--) {
431
0
    connectionObj *conn = connections + i;
432
433
0
    if (conn->ref_count == 0) {
434
      /* for now we don't assume the locks are re-entrant, so release */
435
      /* it so msConnPoolClose() can get it.  */
436
0
      msConnPoolClose(i);
437
0
    }
438
0
  }
439
0
  msReleaseLock(TLOCK_POOL);
440
0
}
441
442
/************************************************************************/
443
/*                       msConnPoolFinalCleanup()                       */
444
/*                                                                      */
445
/*      Close any remaining open connections.   This is normally        */
446
/*      called just before (voluntary) application termination.         */
447
/************************************************************************/
448
449
void msConnPoolFinalCleanup()
450
451
0
{
452
  /* this really needs to be commented out before committing.  */
453
  /* msDebug( "msConnPoolFinalCleanup()\n" ); */
454
455
0
  msAcquireLock(TLOCK_POOL);
456
0
  while (connectionCount > 0)
457
0
    msConnPoolClose(0);
458
0
  msReleaseLock(TLOCK_POOL);
459
0
}