diff --git a/modules/db_postgres/README b/modules/db_postgres/README index 5fd0e35dbeb..e3f33ef74a4 100644 --- a/modules/db_postgres/README +++ b/modules/db_postgres/README @@ -17,6 +17,7 @@ db_postgres Module 1.3.2. max_db_queries (integer) 1.3.3. timeout (integer) 1.3.4. use_tls (integer) + 1.3.5. lazy_connect (integer) 1.4. Exported Functions 1.5. Installation and Running @@ -43,6 +44,7 @@ db_postgres Module 1.2. Set max_db_queries parameter 1.3. Set timeout parameter 1.4. Set the use_tls parameter + 1.5. Set lazy_connect parameter Chapter 1. Admin Guide @@ -165,6 +167,23 @@ modparam("usrloc", "db_url", "postgres://root:1234@localhost/opensips?tl s_domain=dom1") ... +1.3.5. lazy_connect (integer) + + If set to 1, db_init() only allocates the connection handle. + PQconnectdbParams() runs on the first query (including + PQescapeStringConn while SQL text is built) or on async + connection setup. SIP worker processes that never query + PostgreSQL therefore keep no idle backend sockets. The + db_virtual probe timer still issues SELECT 1 on a temporary + handle so a down URL is not marked up without a real connect. + + Default value is 0 (connect at init, historic behaviour). + + Example 1.5. Set lazy_connect parameter +... +modparam("db_postgres", "lazy_connect", 1) +... + 1.4. Exported Functions NONE diff --git a/modules/db_postgres/db_postgres.c b/modules/db_postgres/db_postgres.c index 041d9fb307d..6b90339e0b8 100644 --- a/modules/db_postgres/db_postgres.c +++ b/modules/db_postgres/db_postgres.c @@ -39,6 +39,7 @@ int db_postgres_exec_query_threshold = 0; /* Warning in case DB query takes too long disabled by default*/ int max_db_queries = 2; int pq_timeout = DEFAULT_PSQL_TIMEOUT; +int pg_lazy_connect = 0; /* Don't be lazy, connect on start */ int db_postgres_bind_api(const str* mod, db_func_t *dbb); @@ -64,6 +65,7 @@ static const param_export_t params[] = { {"max_db_queries", INT_PARAM, &max_db_queries}, {"timeout", INT_PARAM, &pq_timeout}, {"use_tls", INT_PARAM, &use_tls}, + {"lazy_connect", INT_PARAM, &pg_lazy_connect}, {0, 0, 0} }; diff --git a/modules/db_postgres/db_postgres.h b/modules/db_postgres/db_postgres.h index 11086bba6b4..bf562d53c1c 100644 --- a/modules/db_postgres/db_postgres.h +++ b/modules/db_postgres/db_postgres.h @@ -31,6 +31,8 @@ extern int pq_timeout; extern int use_tls; +extern int pg_lazy_connect; + extern struct tls_mgm_binds tls_api; #endif /* DB_POSTGRES_H */ diff --git a/modules/db_postgres/dbase.c b/modules/db_postgres/dbase.c index 5bbdfc8992e..8cf3fc5b2c0 100644 --- a/modules/db_postgres/dbase.c +++ b/modules/db_postgres/dbase.c @@ -149,6 +149,9 @@ static int db_postgres_submit_query(const db_con_t* _con, const str* _s) return(-1); } + if (db_postgres_ensure_connected((struct pg_con *)_con->tail) != 0) + return -1; + submit_func_called = 1; /* this bit of nonsense in case our connection get screwed up */ @@ -245,6 +248,9 @@ static int db_postgres_submit_async_query(const db_con_t* _con, const str* _s) return(-1); } + if (db_postgres_ensure_connected((struct pg_con *)_con->tail) != 0) + return -1; + submit_func_called = 1; /* this bit of nonsense in case our connection get screwed up */ diff --git a/modules/db_postgres/doc/db_postgres_admin.xml b/modules/db_postgres/doc/db_postgres_admin.xml index 1a5a047ff28..4bda348894b 100644 --- a/modules/db_postgres/doc/db_postgres_admin.xml +++ b/modules/db_postgres/doc/db_postgres_admin.xml @@ -192,6 +192,33 @@ modparam("db_postgres", "use_tls", 1) ... modparam("usrloc", "db_url", "postgres://root:1234@localhost/opensips?tls_domain=dom1") ... + + + + +
+ <varname>lazy_connect</varname> (integer) + + If set to 1, the module only allocates the connection handle at + init. The PostgreSQL socket is opened on the first query + (including string escaping via PQescapeStringConn, which + runs while the SQL text is built) or when an async + connection is created. Worker processes that never query + the database therefore keep no idle backend sockets. The db_virtual probe timer still + issues SELECT 1 on a temporary handle so a + down URL is not marked up without a real connect. + + + + Default value is 0 (connect at init). + + + + Set <varname>lazy_connect</varname> parameter + +... +modparam("db_postgres", "lazy_connect", 1) +...
diff --git a/modules/db_postgres/pg_con.c b/modules/db_postgres/pg_con.c index c770f59a11f..4d9a7615c77 100644 --- a/modules/db_postgres/pg_con.c +++ b/modules/db_postgres/pg_con.c @@ -256,6 +256,8 @@ int db_postgres_connect(struct pg_con* ptr) { LM_ERR("PQconnectdbParams: %s\n", PQerrorMessage(ptr->con)); PQfinish(ptr->con); + ptr->con = 0; + ptr->connected = 0; return -1; } @@ -293,6 +295,12 @@ struct pg_con* db_postgres_new_connection(struct db_id* id) ptr->ref = 1; ptr->id = id; + if (pg_lazy_connect) { + LM_DBG("lazy_connect enabled, deferring PQconnect ptr=%p db_id=%p\n", + ptr, ptr->id); + return ptr; + } + LM_DBG("calling db_postgres_connect ptr = %p, db_id = %p\n", ptr, ptr->id); if (db_postgres_connect(ptr)!=0) { @@ -306,6 +314,21 @@ struct pg_con* db_postgres_new_connection(struct db_id* id) return ptr; } +int db_postgres_ensure_connected(struct pg_con* ptr) +{ + if (!ptr) { + LM_ERR("invalid connection parameter value\n"); + return -1; + } + + if (ptr->con) + return 0; + + LM_DBG("opening deferred postgres connection ptr=%p db_id=%p\n", + ptr, ptr->id); + return db_postgres_connect(ptr); +} + /* * Create a new async connection structure, * open the PostgreSQL connection and set reference count to 1 @@ -322,11 +345,18 @@ struct pg_con* db_postgres_new_async_connection(struct db_id* id) } ptr = db_postgres_new_connection(id); + if (!ptr) + return 0; - if (ptr) { - PQsetnonblocking(ptr->con, 1); + /* async queries need a live socket even when lazy_connect is set */ + if (db_postgres_ensure_connected(ptr) != 0) { + LM_ERR("async connect failed, cleaning up %p=pkg_free()\n", ptr); + pkg_free(ptr); + return 0; } + PQsetnonblocking(ptr->con, 1); + return ptr; } diff --git a/modules/db_postgres/pg_con.h b/modules/db_postgres/pg_con.h index e081edd876b..f09a1c95017 100644 --- a/modules/db_postgres/pg_con.h +++ b/modules/db_postgres/pg_con.h @@ -73,6 +73,9 @@ struct pg_con { * Create a new connection structure, * open the PostgreSQL connection and set reference count to 1 */ +int db_postgres_connect(struct pg_con* ptr); +int db_postgres_ensure_connected(struct pg_con* ptr); + struct pg_con* db_postgres_new_connection(struct db_id* id); /* diff --git a/modules/db_postgres/val.c b/modules/db_postgres/val.c index 615f90cbd6a..4b15e68db49 100644 --- a/modules/db_postgres/val.c +++ b/modules/db_postgres/val.c @@ -198,6 +198,10 @@ int db_postgres_val2str(const db_con_t* _con, const db_val_t* _v, return 0; } + /* PQescapeStringConn / PQescapeByteaConn need a live PGconn. + * Query text is built (dispatcher version check, WHERE clauses) + * before submit_query(), so lazy_connect must open here. */ + switch(VAL_TYPE(_v)) { case DB_INT: if (db_int2str(VAL_INT(_v), _s, _len) < 0) { @@ -242,6 +246,8 @@ int db_postgres_val2str(const db_con_t* _con, const db_val_t* _v, *_len, l * 2 + 3); return -4; } else { + if (db_postgres_ensure_connected((struct pg_con *)_con->tail) != 0) + return -4; old_s = _s; *_s++ = '\''; ret = PQescapeStringConn(CON_CONNECTION(_con), _s, VAL_STRING(_v), @@ -268,6 +274,8 @@ int db_postgres_val2str(const db_con_t* _con, const db_val_t* _v, *_len, l * 2 + 3); return -5; } else { + if (db_postgres_ensure_connected((struct pg_con *)_con->tail) != 0) + return -5; old_s = _s; *_s++ = '\''; ret = PQescapeStringConn(CON_CONNECTION(_con), _s, VAL_STR(_v).s, @@ -303,6 +311,8 @@ int db_postgres_val2str(const db_con_t* _con, const db_val_t* _v, *_len, l * 2 + 3); return -7; } else { + if (db_postgres_ensure_connected((struct pg_con *)_con->tail) != 0) + return -7; *_s++ = '\''; tmp_s = (char*)PQescapeByteaConn(CON_CONNECTION(_con), (unsigned char*)VAL_STRING(_v), (size_t)l, (size_t*)&tmp_len); diff --git a/modules/db_virtual/db_virtual.c b/modules/db_virtual/db_virtual.c index 5343f943812..7aa38313baf 100644 --- a/modules/db_virtual/db_virtual.c +++ b/modules/db_virtual/db_virtual.c @@ -336,31 +336,55 @@ int init_private_handles(void){ return -1; } +static str db_virtual_probe_sql = str_init("SELECT 1"); + +/* Real reachability check. db_postgres lazy_connect makes init() a + * handle alloc only; this ping forces PQconnect on the probe handle. + * Do not call this from db_virtual_init() — that would open sockets + * in every SIP worker. */ +static int db_virtual_ping(db_func_t *f, db_con_t *con) +{ + db_res_t *res = NULL; + int rc; + + if (!f || !con) + return -1; + + if (!f->raw_query) + return 0; + + rc = f->raw_query(con, &db_virtual_probe_sql, &res); + if (res && f->free_result) + f->free_result(con, res); + return rc; +} + static void reconnect_timer(unsigned int ticks, void *data) { LM_DBG("reconnect with timer\n"); int i,j; db_con_t * con; + db_func_t * f; for(i=0; i < global-> size; i++){ for(j=0; j < global->set_list[i].size; j++){ /* if CAN DOWN */ if(!(global->set_list[i].db_list[j].flags & CAN_USE)){ - con = - global->set_list[i].db_list[j].dbf.init( - &global->set_list[i].db_list[j].db_url); - if(!con){ + f = &global->set_list[i].db_list[j].dbf; + con = f->init(&global->set_list[i].db_list[j].db_url); + if(!con || db_virtual_ping(f, con) != 0){ LM_DBG("Cant reconnect on timer to db %.*s, %i\n", global->set_list[i].db_list[j].db_url.len, global->set_list[i].db_list[j].db_url.s, global->set_list[i].db_list[j].flags); - + if (con) + f->close(con); }else{ LM_DBG("Can reconnect on timer to db %.*s\n", global->set_list[i].db_list[j].db_url.len, global->set_list[i].db_list[j].db_url.s); - global->set_list[i].db_list[j].dbf.close(con); + f->close(con); global->set_list[i].db_list[j].flags |= CAN_USE; } }