< Summary - Backend C Tests - Coverage Report (WSL)

Information
Class: db_connection_c
Assembly: src.backend.database
File(s): ./src/backend/database/db_connection.c
Line coverage
79%
Covered lines: 143
Uncovered lines: 37
Coverable lines: 180
Total lines: 417
Line coverage: 79.4%
Branch coverage
61%
Covered branches: 40
Total branches: 65
Branch coverage: 61.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 2/18/2026 - 10:50:55 PM Line coverage: 79.4% (143/180) Branch coverage: 61.5% (40/65) Total lines: 417 2/18/2026 - 10:50:55 PM Line coverage: 79.4% (143/180) Branch coverage: 61.5% (40/65) Total lines: 417

File(s)

./src/backend/database/db_connection.c

#LineLine coverage
 1/*
 2 * db_connection.c
 3 * ----------------
 4 * A learning‑focused implementation of a PostgreSQL connection pool.
 5 * The code is heavily commented so that a newcomer can follow each
 6 * design decision and understand how the pool works in a real‑world
 7 * factory setting (50+ machines sending sensor data).
 8 *
 9 * NOTE: This file pairs with `db_connection.h` which declares the public
 10 * API and data structures.
 11 */
 12
 13/* ------------------------------------------------------------ */
 14/* 1.  Standard library includes                                 */
 15/* ------------------------------------------------------------ */
 16#include <stdio.h>
 17#include <stdlib.h>
 18#include <stdbool.h>
 19#include <string.h>
 20#include <pthread.h>
 21#include <libpq-fe.h>          /* libpq – PostgreSQL C client library */
 22#include <errno.h>
 23#include "logger.h"
 24#include "db_connection.h"
 25
 26#ifdef TEST_MODE
 27  // Test modunda PostgreSQL bağlantısını mock'la
 28  #define PQconnectdb(x) ((PGconn*)0xDEADBEEF)
 29  #define PQstatus(x) CONNECTION_OK
 30  #define PQerrorMessage(x) "Mock connection"
 31  #define PQfinish(x)
 32#endif
 33
 34
 35/* ------------------------------------------------------------ */
 36/* 5.  The pool structure (private – defined here for simplicity) */
 37/* ------------------------------------------------------------ */
 38struct ConnectionPool {
 39  DBConnection *connections;          /* array of size max_size */
 40  int           max_size;            /* e.g. 25 */
 41  int           min_size;            /* e.g. 5  */
 42  int           used_cnt;            /* how many are currently in_use */
 43  int          *free_stack;          /* stack of free indices */
 44  int           free_top;            /* index of the top element */
 45  size_t acquire_cnt;
 46  size_t release_cnt;
 47  double total_wait_ms;
 48  pthread_mutex_t lock;              /* protects all mutable fields */
 49  pthread_cond_t  free_cond;         /* signalled when a slot becomes free */
 50  PoolExhaustionPolicy policy;       /* chosen by the integrator */
 51  int           timeout_ms;          /* used when policy == BLOCK_WITH_TIMEOUT */
 52  DatabaseConfig cfg;                /* stored so we can lazily create connections */
 53};
 54
 55/* --- FIFO Queue Structure for Waiting Threads --- */
 56typedef struct WaitNode {
 57  pthread_cond_t cond;
 58  struct WaitNode *next;
 59} WaitNode;
 60
 61static WaitNode *wait_head = NULL;
 62static WaitNode *wait_tail = NULL;
 63
 64/* ------------------------------------------------------------ */
 65/* 6.  Global pool instance – one pool per process                */
 66/* ------------------------------------------------------------ */
 67static ConnectionPool g_pool = {0};
 68
 69/* ------------------------------------------------------------ */
 70/* 7.  Helper: build a libpq connection string from DatabaseConfig */
 71/* ------------------------------------------------------------ */
 1972static char *build_conn_str(const DatabaseConfig *cfg) {
 73  /* The string is allocated on the heap; the caller must free it. */
 1974  size_t needed = 256 + strlen(cfg->host) + strlen(cfg->dbname) +
 1975                  strlen(cfg->user) + strlen(cfg->password) + strlen(cfg->sslmode);
 1976  char *buf = (char *)malloc(needed);
 77
 1978  if (!buf) return NULL;
 79
 1980  snprintf(buf, needed,
 81           "host=%s port=%d dbname=%s user=%s password=%s sslmode=%s connect_timeout=%d",
 1982           cfg->host, cfg->port, cfg->dbname, cfg->user, cfg->password,
 1983           cfg->sslmode, cfg->connect_timeout);
 1984  return buf;
 85}
 86
 87/* ------------------------------------------------------------ */
 88/* 8.  Stack utilities – very small, lock‑free because the pool lock
 89 *    already protects them.
 90 * ------------------------------------------------------------ */
 3791static inline void stack_push(int *stack, int *top, int value) {
 3792  stack[(*top)++] = value;   /* store then increment */
 3793}
 94
 1195static inline int stack_pop(int *stack, int *top) {
 1196  return stack[--(*top)];    /* decrement then retrieve */
 97}
 98
 2199static inline bool stack_is_empty(int top) {
 21100  return top == 0;
 101}
 102
 103/* --- Internal: Reconnect a dead connection --- */
 1104static bool reconnect_connection(DBConnection *c, const DatabaseConfig *cfg) {
 1105  if (c->pg_conn) {
 1106    PQfinish(c->pg_conn);
 1107    c->pg_conn = NULL;
 108  }
 109
 1110  char *cs = build_conn_str(cfg);
 111
 1112  if (!cs) return false;
 113
 1114  LOG_INFO("[db_pool] Reconnecting dead connection at slot %d...", c->index);
 1115  c->pg_conn = PQconnectdb(cs);
 1116  free(cs);
 117
 1118  if (PQstatus(c->pg_conn) != CONNECTION_OK) {
 1119    LOG_ERROR("[db_pool] Reconnect failed: %s", PQerrorMessage(c->pg_conn));
 1120    return false;
 121  }
 122
 0123  return true;
 124}
 125
 126/* ------------------------------------------------------------ */
 127/* 9.  Public API – initialise the pool                         */
 128/* ------------------------------------------------------------ */
 15129ConnectionPool *db_pool_init(const DatabaseConfig *cfg,
 130                             PoolExhaustionPolicy policy,
 131                             int timeout_ms) {
 132  /* 1️ Allocate the connection array */
 15133  g_pool.acquire_cnt = 0;
 15134  g_pool.release_cnt = 0;
 15135  g_pool.total_wait_ms = 0;
 136  // Konfigürasyondan boyutları oku, belirtilmemişse varsayılanları kullan
 15137  g_pool.max_size = (cfg->pool_max > 0) ? cfg->pool_max : 25;
 15138  g_pool.min_size = (cfg->pool_min > 0) ? cfg->pool_min : 5;
 15139  g_pool.used_cnt = 0;
 15140  g_pool.policy    = policy;
 15141  g_pool.timeout_ms = timeout_ms;
 15142  g_pool.cfg = *cfg;             /* copy – we own the data */
 15143  g_pool.connections = calloc(g_pool.max_size, sizeof(DBConnection));
 144
 15145  if (!g_pool.connections) return NULL;
 146
 147  /* 2️ Allocate the free‑slot stack (size = max_size) */
 15148  g_pool.free_stack = malloc(g_pool.max_size *sizeof(int));
 149
 15150  if (!g_pool.free_stack) {
 0151    free(g_pool.connections);
 0152    return NULL;
 153  }
 154
 15155  g_pool.free_top = 0;
 156  /* 3️ Initialise mutex / condition variable */
 15157  pthread_mutex_init(&g_pool.lock, NULL);
 15158  pthread_cond_init(&g_pool.free_cond, NULL);
 159  /* 4 Eagerly create the *minimum* number of connections */
 15160  char *conn_str = build_conn_str(&g_pool.cfg);
 161
 15162  if (!conn_str) return NULL;   /* out‑of‑memory */
 163
 43164  for (int i = 0; i < g_pool.min_size; ++i) {
 28165    PGconn *c = PQconnectdb(conn_str);
 166
 28167    if (PQstatus(c) != CONNECTION_OK) {
 4168      LOG_ERROR("[db_pool_init] failed to connect: %s", PQerrorMessage(c));
 4169      PQfinish(c);
 4170      continue;   /* we keep the slot NULL – lazy creation later */
 171    }
 172
 24173    g_pool.connections[i].pg_conn = c;
 24174    g_pool.connections[i].in_use = false;
 24175    g_pool.connections[i].index   = i;
 24176    stack_push(g_pool.free_stack, &g_pool.free_top, i);
 177  }
 178
 15179  free(conn_str);
 180
 181  /* 5️ Remaining slots stay NULL (lazy) */
 50182  for (int i = g_pool.min_size; i < g_pool.max_size; ++i) {
 35183    g_pool.connections[i].pg_conn = NULL;
 35184    g_pool.connections[i].in_use = false;
 35185    g_pool.connections[i].index   = i;
 186  }
 187
 15188  return &g_pool;
 189}
 190
 191/* ------------------------------------------------------------ */
 192/* 10. Acquire a connection – respects the exhaustion policy      */
 193/* ------------------------------------------------------------ */
 17194DBConnection *db_pool_acquire(void) {
 17195  pthread_mutex_lock(&g_pool.lock);
 196
 197  /* Fast path – a free slot is already on the stack */
 17198  if (!stack_is_empty(g_pool.free_top)) {
 11199    int idx = stack_pop(g_pool.free_stack, &g_pool.free_top);
 11200    DBConnection *c = &g_pool.connections[idx];
 201
 202    /* --- RECONNECTION LOGIC START --- */
 203    /* Check if the connection is still alive */
 11204    if (PQstatus(c->pg_conn) != CONNECTION_OK) {
 1205      if (!reconnect_connection(c, &g_pool.cfg)) {
 206        /* If reconnect fails, we effectively lost a slot.
 207           In a real system, we might retry or return NULL. */
 1208        pthread_mutex_unlock(&g_pool.lock);
 1209        return NULL;
 210      }
 211    }
 212
 213    /* --- RECONNECTION LOGIC END --- */
 10214    c->in_use = true;
 10215    g_pool.used_cnt++;
 10216    pthread_mutex_unlock(&g_pool.lock);
 10217    return c;
 218  }
 219
 220  /* No free slot – try lazy creation if we still have capacity */
 8221  for (int i = g_pool.min_size; i < g_pool.max_size; ++i) {
 5222    if (g_pool.connections[i].pg_conn == NULL) {
 3223      char *cs = build_conn_str(&g_pool.cfg);
 224
 3225      if (!cs) break;   /* out‑of‑memory */
 226
 3227      PGconn *c = PQconnectdb(cs);
 3228      free(cs);
 229
 3230      if (PQstatus(c) != CONNECTION_OK) {
 0231        LOG_ERROR("[db_pool_acquire] lazy connect failed: %s", PQerrorMessage(c));
 0232        PQfinish(c);
 0233        continue;   /* treat as exhausted for now */
 234      }
 235
 3236      g_pool.connections[i].pg_conn = c;
 3237      g_pool.connections[i].in_use = true;
 3238      g_pool.connections[i].index   = i;
 3239      g_pool.used_cnt++;
 3240      pthread_mutex_unlock(&g_pool.lock);
 3241      return &g_pool.connections[i];
 242    }
 243  }
 244
 245  /* -------------------------------------------------------- */
 246  /* 3 Pool exhausted – apply the selected policy            */
 247  /* -------------------------------------------------------- */
 3248  switch (g_pool.policy) {
 2249    case BLOCK_WITH_TIMEOUT: {
 250      /* Dynamically adjust timeout based on current load.
 251       * The more connections in use, the longer we are willing to wait.
 252       * Simple heuristic: base_timeout * (1 + used_cnt / max_size). */
 2253      double load_factor = (double)g_pool.used_cnt / (double)g_pool.max_size;
 2254      int effective_timeout_ms = (int)(g_pool.timeout_ms * (1.0 + load_factor));
 255      struct timespec ts;
 2256      clock_gettime(CLOCK_REALTIME, &ts);
 2257      ts.tv_sec  += effective_timeout_ms / 1000;
 2258      ts.tv_nsec += (effective_timeout_ms % 1000) * 1000000;
 259
 2260      if (ts.tv_nsec >= 1000000000) {
 0261        ts.tv_sec++;
 0262        ts.tv_nsec -= 1000000000;
 263      }
 264
 265      /* Measure how long we actually waited. */
 266      struct timespec wait_start, wait_end;
 2267      clock_gettime(CLOCK_REALTIME, &wait_start);
 2268      int rc = 0;
 269
 4270      while (rc != ETIMEDOUT && stack_is_empty(g_pool.free_top)) {
 2271        rc = pthread_cond_timedwait(&g_pool.free_cond, &g_pool.lock, &ts);
 272      }
 273
 2274      clock_gettime(CLOCK_REALTIME, &wait_end);
 2275      double waited_ms = (wait_end.tv_sec - wait_start.tv_sec) * 1000.0 +
 2276                         (wait_end.tv_nsec - wait_start.tv_nsec) / 1000000.0;
 2277      g_pool.total_wait_ms += waited_ms;
 278
 2279      if (!stack_is_empty(g_pool.free_top)) {
 0280        int idx = stack_pop(g_pool.free_stack, &g_pool.free_top);
 0281        DBConnection *c = &g_pool.connections[idx];
 0282        c->in_use = true;
 0283        g_pool.used_cnt++;
 0284        g_pool.acquire_cnt++;
 0285        pthread_mutex_unlock(&g_pool.lock);
 0286        return c;
 287      }
 288
 289      /* timeout – fall through to failure */
 2290      break;
 291    }
 292
 0293    case QUEUE_REQUESTS: {
 294      /* FIFO Bekleme Kuyruğu Uygulaması:
 295       * Her thread kendi condition variable'ı ile kuyruğa eklenir. */
 0296      WaitNode *my_node = malloc(sizeof(WaitNode));
 0297      pthread_cond_init(&my_node->cond, NULL);
 0298      my_node->next = NULL;
 299
 0300      if (wait_tail == NULL) {
 0301        wait_head = my_node;
 302      } else {
 0303        wait_tail->next = my_node;
 304      }
 305
 0306      wait_tail = my_node;
 307
 0308      while (stack_is_empty(g_pool.free_top) || wait_head != my_node) {
 0309        pthread_cond_wait(&my_node->cond, &g_pool.lock);
 310      }
 311
 0312      int idx = stack_pop(g_pool.free_stack, &g_pool.free_top);
 0313      DBConnection *c = &g_pool.connections[idx];
 0314      c->in_use = true;
 0315      g_pool.used_cnt++;
 0316      g_pool.acquire_cnt++;
 317      /* Kendini kuyruktan çıkar */
 0318      wait_head = my_node->next;
 319
 0320      if (wait_head == NULL) wait_tail = NULL;
 321
 0322      pthread_cond_destroy(&my_node->cond);
 0323      free(my_node);
 0324      pthread_mutex_unlock(&g_pool.lock);
 0325      return c;
 326    }
 327
 1328    case FAIL_FAST:
 329    default:
 330      /* Immediate failure – caller must decide what to do */
 1331      break;
 332  }
 333
 3334  pthread_mutex_unlock(&g_pool.lock);
 3335  return NULL;   /* pool exhausted and policy dictated failure */
 336}
 337
 338/* ------------------------------------------------------------ */
 339/* 11. Release a connection back to the pool                     */
 340/* ------------------------------------------------------------ */
 15341void db_pool_release(DBConnection *conn) {
 15342  if (!conn) return;
 343
 13344  pthread_mutex_lock(&g_pool.lock);
 13345  int idx = conn->index;
 13346  conn->in_use = false;
 13347  stack_push(g_pool.free_stack, &g_pool.free_top, idx);
 13348  g_pool.used_cnt--;
 13349  g_pool.release_cnt++;
 350
 351  /* Kuyruktaki ilk kişiyi uyandır (FIFO) */
 13352  if (wait_head != NULL) {
 0353    pthread_cond_signal(&wait_head->cond);
 354  } else {
 355    /* Kimse kuyrukta değilse BLOCK_WITH_TIMEOUT bekleyenleri uyar */
 13356    pthread_cond_signal(&g_pool.free_cond);
 357  }
 358
 13359  pthread_mutex_unlock(&g_pool.lock);
 360}
 361
 362/* ------------------------------------------------------------ */
 363/* 12. Destroy the pool – close all libpq connections          */
 364/* ------------------------------------------------------------ */
 16365void db_pool_destroy(void) {
 366  /* Student‑style cleanup: close all libpq connections and free memory. */
 16367  pthread_mutex_lock(&g_pool.lock);
 368
 79369  for (int i = 0; i < g_pool.max_size; ++i) {
 63370    if (g_pool.connections[i].pg_conn) {
 25371      PQfinish(g_pool.connections[i].pg_conn);
 25372      g_pool.connections[i].pg_conn = NULL;
 373    }
 374  }
 375
 16376  free(g_pool.connections);
 16377  free(g_pool.free_stack);
 16378  pthread_mutex_unlock(&g_pool.lock);
 16379  pthread_mutex_destroy(&g_pool.lock);
 16380  pthread_cond_destroy(&g_pool.free_cond);
 16381  memset(&g_pool, 0, sizeof(g_pool));
 16382}
 383
 384/* ------------------------------------------------------------ */
 385/* 12.5 Get current metrics                                     */
 386/* ------------------------------------------------------------ */
 8387PoolMetrics db_pool_get_metrics(void) {
 388  PoolMetrics m;
 8389  pthread_mutex_lock(&g_pool.lock);
 8390  m.acquire_cnt = g_pool.acquire_cnt;
 8391  m.release_cnt = g_pool.release_cnt;
 8392  m.used_cnt    = g_pool.used_cnt;
 8393  m.total_wait_ms = g_pool.total_wait_ms;
 8394  pthread_mutex_unlock(&g_pool.lock);
 8395  return m;
 396}
 397
 398/* ------------------------------------------------------------ */
 399/* 13. Example usage (commented out – keep it for learning)   */
 400/* ------------------------------------------------------------ */
 401/*
 402int main(void)
 403{
 404    DatabaseConfig cfg = {"localhost", 5432, "factorydb", "admin", "secret", "require", 5, 5, 25};
 405    ConnectionPool *p = db_pool_init(&cfg, BLOCK_WITH_TIMEOUT, 200);
 406    if (!p) { LOG_ERROR("Failed to init pool"); return 1; }
 407
 408    DBConnection *c = db_pool_acquire();
 409    if (!c) { LOG_ERROR("Could not acquire connection"); return 1; }
 410
 411    // Use the connection, e.g. PQexec(c->pg_conn, "SELECT 1");
 412
 413    db_pool_release(c);
 414    db_pool_destroy();
 415    return 0;
 416}
 417*/

Methods/Properties