|
@@ -30,21 +30,25 @@
|
|
|
|
|
|
#include "database.h"
|
|
|
#include "error.h"
|
|
|
+#include "global_data.h"
|
|
|
#include "list.h"
|
|
|
-#include "thread.h"
|
|
|
|
|
|
-#define IS_RESETTING 1
|
|
|
-#define IS_WRITING 2
|
|
|
+#define EXPECT_SYNC 1
|
|
|
+#define IGNORE_RESULT 2
|
|
|
+#define IS_RESETTING 4
|
|
|
+#define MS_IN_S 1000
|
|
|
|
|
|
typedef struct {
|
|
|
list_t l;
|
|
|
PGconn *conn;
|
|
|
- thread_context_t *ctx;
|
|
|
- db_query_param_t *param;
|
|
|
- list_t *prepared_statement;
|
|
|
+ db_conn_pool_t *pool;
|
|
|
+ const list_t *prepared_statement;
|
|
|
+ queue_t queries;
|
|
|
h2o_socket_t *sock;
|
|
|
+ size_t query_num;
|
|
|
uint_fast32_t flags;
|
|
|
- h2o_timeout_entry_t h2o_timeout_entry;
|
|
|
+ int sd;
|
|
|
+ h2o_timeout_entry_t timeout;
|
|
|
} db_conn_t;
|
|
|
|
|
|
typedef struct {
|
|
@@ -53,98 +57,117 @@ typedef struct {
|
|
|
const char *query;
|
|
|
} prepared_statement_t;
|
|
|
|
|
|
-static int do_database_write(db_conn_t *db_conn);
|
|
|
-static int do_execute_query(db_conn_t *db_conn, bool direct_notification);
|
|
|
-static void error_notification(thread_context_t *ctx, bool timeout, const char *error_string);
|
|
|
-static void on_database_connect_error(db_conn_t *db_conn, bool timeout, const char *error_string);
|
|
|
+static h2o_socket_t *create_socket(int sd, h2o_loop_t *loop);
|
|
|
+static int do_execute_query(db_conn_t *conn, db_query_param_t *param);
|
|
|
+static void error_notification(db_conn_pool_t *pool, bool timeout, const char *error_string);
|
|
|
+static void on_database_connect_error(db_conn_t *conn, bool timeout, const char *error_string);
|
|
|
+static void on_database_connect_read_ready(h2o_socket_t *sock, const char *err);
|
|
|
static void on_database_connect_timeout(h2o_timeout_entry_t *entry);
|
|
|
-static void on_database_error(db_conn_t *db_conn, const char *error_string);
|
|
|
-static void on_database_read_ready(h2o_socket_t *db_sock, const char *err);
|
|
|
-static void on_database_timeout(h2o_timeout_entry_t *entry);
|
|
|
-static void on_database_write_ready(h2o_socket_t *db_sock, const char *err);
|
|
|
-static void poll_database_connection(h2o_socket_t *db_sock, const char *err);
|
|
|
-static void process_query(db_conn_t *db_conn);
|
|
|
-static void start_database_connect(thread_context_t *ctx, db_conn_t *db_conn);
|
|
|
-
|
|
|
-static int do_database_write(db_conn_t *db_conn)
|
|
|
+static void on_database_connect_write_ready(h2o_socket_t *sock, const char *err);
|
|
|
+static void on_database_error(db_conn_t *conn, const char *error_string);
|
|
|
+static void on_database_read_ready(h2o_socket_t *sock, const char *err);
|
|
|
+static void on_database_timeout(h2o_timeout_entry_t *timeout);
|
|
|
+static void on_database_write_ready(h2o_socket_t *sock, const char *err);
|
|
|
+static void poll_database_connection(h2o_socket_t *sock, const char *err);
|
|
|
+static void prepare_statements(db_conn_t *conn);
|
|
|
+static void process_queries(db_conn_t *conn);
|
|
|
+static void start_database_connect(db_conn_pool_t *pool, db_conn_t *conn);
|
|
|
+
|
|
|
+static h2o_socket_t *create_socket(int sd, h2o_loop_t *loop)
|
|
|
{
|
|
|
- assert(db_conn->param);
|
|
|
+ sd = dup(sd);
|
|
|
|
|
|
- int ret = db_conn->param->on_write_ready(db_conn->param, db_conn->conn);
|
|
|
+ if (sd < 0) {
|
|
|
+ STANDARD_ERROR("dup");
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ const int flags = fcntl(sd, F_GETFD);
|
|
|
|
|
|
- if (!ret)
|
|
|
- db_conn->flags &= ~IS_WRITING;
|
|
|
- else if (ret < 0) {
|
|
|
- ERROR(PQerrorMessage(db_conn->conn));
|
|
|
- on_database_error(db_conn, DB_ERROR);
|
|
|
+ if (flags < 0 || fcntl(sd, F_SETFD, flags | FD_CLOEXEC)) {
|
|
|
+ STANDARD_ERROR("fcntl");
|
|
|
+ close(sd);
|
|
|
+ return NULL;
|
|
|
}
|
|
|
- else {
|
|
|
- h2o_socket_notify_write(db_conn->sock, on_database_write_ready);
|
|
|
- ret = 0;
|
|
|
+
|
|
|
+ h2o_socket_t * const ret = h2o_evloop_socket_create(loop, sd, H2O_SOCKET_FLAG_DONT_READ);
|
|
|
+
|
|
|
+ if (!ret) {
|
|
|
+ errno = ENOMEM;
|
|
|
+ STANDARD_ERROR("h2o_evloop_socket_create");
|
|
|
+ close(sd);
|
|
|
}
|
|
|
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
-static int do_execute_query(db_conn_t *db_conn, bool direct_notification)
|
|
|
+static int do_execute_query(db_conn_t *conn, db_query_param_t *param)
|
|
|
{
|
|
|
- const int ec = db_conn->param->flags & IS_PREPARED ?
|
|
|
- PQsendQueryPrepared(db_conn->conn,
|
|
|
- db_conn->param->command,
|
|
|
- db_conn->param->nParams,
|
|
|
- db_conn->param->paramValues,
|
|
|
- db_conn->param->paramLengths,
|
|
|
- db_conn->param->paramFormats,
|
|
|
- db_conn->param->resultFormat) :
|
|
|
- PQsendQuery(db_conn->conn, db_conn->param->command);
|
|
|
- int ret = 1;
|
|
|
-
|
|
|
- if (ec) {
|
|
|
- if (db_conn->param->flags & IS_SINGLE_ROW)
|
|
|
- PQsetSingleRowMode(db_conn->conn);
|
|
|
-
|
|
|
- db_conn->h2o_timeout_entry.cb = on_database_timeout;
|
|
|
- h2o_timeout_link(db_conn->ctx->event_loop.h2o_ctx.loop,
|
|
|
- &db_conn->ctx->db_state.h2o_timeout,
|
|
|
- &db_conn->h2o_timeout_entry);
|
|
|
- h2o_socket_read_start(db_conn->sock, on_database_read_ready);
|
|
|
-
|
|
|
- const int send_status = PQflush(db_conn->conn);
|
|
|
+ assert(conn->query_num);
|
|
|
+ assert((conn->queries.head && conn->query_num < conn->pool->config->max_pipeline_query_num) ||
|
|
|
+ (!conn->queries.head && conn->query_num == conn->pool->config->max_pipeline_query_num));
|
|
|
+
|
|
|
+ const int ec = param->flags & IS_PREPARED ?
|
|
|
+ PQsendQueryPrepared(conn->conn,
|
|
|
+ param->command,
|
|
|
+ param->nParams,
|
|
|
+ param->paramValues,
|
|
|
+ param->paramLengths,
|
|
|
+ param->paramFormats,
|
|
|
+ param->resultFormat) :
|
|
|
+ PQsendQueryParams(conn->conn,
|
|
|
+ param->command,
|
|
|
+ param->nParams,
|
|
|
+ param->paramTypes,
|
|
|
+ param->paramValues,
|
|
|
+ param->paramLengths,
|
|
|
+ param->paramFormats,
|
|
|
+ param->resultFormat);
|
|
|
+
|
|
|
+ if (!ec) {
|
|
|
+ ERROR(PQerrorMessage(conn->conn));
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
|
|
|
- if (send_status < 0) {
|
|
|
- if (direct_notification)
|
|
|
- db_conn->param = NULL;
|
|
|
+ if (!PQpipelineSync(conn->conn)) {
|
|
|
+ LIBRARY_ERROR("PQpipelineSync", PQerrorMessage(conn->conn));
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
|
|
|
- LIBRARY_ERROR("PQflush", PQerrorMessage(db_conn->conn));
|
|
|
- on_database_error(db_conn, DB_ERROR);
|
|
|
- }
|
|
|
- else {
|
|
|
- ret = 0;
|
|
|
+ const int send_status = PQflush(conn->conn);
|
|
|
|
|
|
- if (send_status)
|
|
|
- h2o_socket_notify_write(db_conn->sock, on_database_write_ready);
|
|
|
- }
|
|
|
+ if (send_status < 0) {
|
|
|
+ LIBRARY_ERROR("PQflush", PQerrorMessage(conn->conn));
|
|
|
+ return 1;
|
|
|
}
|
|
|
- else {
|
|
|
- if (direct_notification)
|
|
|
- db_conn->param = NULL;
|
|
|
-
|
|
|
- ERROR(PQerrorMessage(db_conn->conn));
|
|
|
- on_database_error(db_conn, DB_ERROR);
|
|
|
+ else if (send_status)
|
|
|
+ h2o_socket_notify_write(conn->sock, on_database_write_ready);
|
|
|
+
|
|
|
+ if (!conn->queries.head && !(conn->flags & (EXPECT_SYNC | IGNORE_RESULT))) {
|
|
|
+ assert(!h2o_timeout_is_linked(&conn->timeout));
|
|
|
+ conn->timeout.cb = on_database_timeout;
|
|
|
+ h2o_timeout_link(conn->pool->loop, &conn->pool->timeout, &conn->timeout);
|
|
|
+ h2o_socket_read_start(conn->sock, on_database_read_ready);
|
|
|
}
|
|
|
|
|
|
- return ret;
|
|
|
+ param->l.next = NULL;
|
|
|
+ *conn->queries.tail = ¶m->l;
|
|
|
+ conn->queries.tail = ¶m->l.next;
|
|
|
+ conn->query_num--;
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
-static void error_notification(thread_context_t *ctx, bool timeout, const char *error_string)
|
|
|
+static void error_notification(db_conn_pool_t *pool, bool timeout, const char *error_string)
|
|
|
{
|
|
|
- if (!--ctx->db_state.db_conn_num) {
|
|
|
+ assert(pool->conn_num < pool->config->max_db_conn_num);
|
|
|
+
|
|
|
+ if (++pool->conn_num == pool->config->max_db_conn_num) {
|
|
|
// We don't want to keep requests waiting for an unbounded amount of time.
|
|
|
- list_t *iter = ctx->db_state.queries.head;
|
|
|
+ list_t *iter = pool->queries.head;
|
|
|
|
|
|
- ctx->db_state.queries.head = NULL;
|
|
|
- ctx->db_state.queries.tail = &ctx->db_state.queries.head;
|
|
|
- ctx->db_state.query_num = 0;
|
|
|
+ pool->queries.head = NULL;
|
|
|
+ pool->queries.tail = &pool->queries.head;
|
|
|
+ pool->query_num = pool->config->max_query_num;
|
|
|
|
|
|
if (iter)
|
|
|
do {
|
|
@@ -161,297 +184,443 @@ static void error_notification(thread_context_t *ctx, bool timeout, const char *
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-static void on_database_connect_error(db_conn_t *db_conn, bool timeout, const char *error_string)
|
|
|
+static void on_database_connect_error(db_conn_t *conn, bool timeout, const char *error_string)
|
|
|
{
|
|
|
- thread_context_t * const ctx = db_conn->ctx;
|
|
|
-
|
|
|
- error_notification(ctx, timeout, error_string);
|
|
|
- h2o_timeout_unlink(&db_conn->h2o_timeout_entry);
|
|
|
- h2o_socket_read_stop(db_conn->sock);
|
|
|
- h2o_socket_close(db_conn->sock);
|
|
|
- PQfinish(db_conn->conn);
|
|
|
- free(db_conn);
|
|
|
+ error_notification(conn->pool, timeout, error_string);
|
|
|
+ h2o_timeout_unlink(&conn->timeout);
|
|
|
+ h2o_socket_read_stop(conn->sock);
|
|
|
+ h2o_socket_close(conn->sock);
|
|
|
+ PQfinish(conn->conn);
|
|
|
+ free(conn);
|
|
|
+}
|
|
|
+
|
|
|
+static void on_database_connect_read_ready(h2o_socket_t *sock, const char *err)
|
|
|
+{
|
|
|
+ db_conn_t * const conn = sock->data;
|
|
|
+
|
|
|
+ if (err) {
|
|
|
+ ERROR(err);
|
|
|
+ on_database_connect_error(conn, false, DB_ERROR);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!PQconsumeInput(conn->conn)) {
|
|
|
+ LIBRARY_ERROR("PQconsumeInput", PQerrorMessage(conn->conn));
|
|
|
+ on_database_connect_error(conn, false, DB_ERROR);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const int send_status = PQflush(conn->conn);
|
|
|
+
|
|
|
+ if (send_status < 0) {
|
|
|
+ LIBRARY_ERROR("PQflush", PQerrorMessage(conn->conn));
|
|
|
+ on_database_connect_error(conn, false, DB_ERROR);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ else if (send_status) {
|
|
|
+ h2o_socket_notify_write(conn->sock, on_database_write_ready);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ while (!PQisBusy(conn->conn)) {
|
|
|
+ PGresult * const result = PQgetResult(conn->conn);
|
|
|
+
|
|
|
+ if (result) {
|
|
|
+ switch (PQresultStatus(result)) {
|
|
|
+ case PGRES_COMMAND_OK:
|
|
|
+ break;
|
|
|
+ case PGRES_PIPELINE_SYNC:
|
|
|
+ PQclear(result);
|
|
|
+ h2o_timeout_unlink(&conn->timeout);
|
|
|
+ h2o_socket_read_stop(conn->sock);
|
|
|
+ process_queries(conn);
|
|
|
+ return;
|
|
|
+ default:
|
|
|
+ LIBRARY_ERROR("PQresultStatus", PQresultErrorMessage(result));
|
|
|
+ PQclear(result);
|
|
|
+ on_database_connect_error(conn, false, DB_ERROR);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ PQclear(result);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
static void on_database_connect_timeout(h2o_timeout_entry_t *entry)
|
|
|
{
|
|
|
- db_conn_t * const db_conn = H2O_STRUCT_FROM_MEMBER(db_conn_t, h2o_timeout_entry, entry);
|
|
|
+ db_conn_t * const conn = H2O_STRUCT_FROM_MEMBER(db_conn_t, timeout, entry);
|
|
|
|
|
|
ERROR(DB_TIMEOUT_ERROR);
|
|
|
- on_database_connect_error(db_conn, true, DB_TIMEOUT_ERROR);
|
|
|
+ on_database_connect_error(conn, true, DB_TIMEOUT_ERROR);
|
|
|
}
|
|
|
|
|
|
-static void on_database_error(db_conn_t *db_conn, const char *error_string)
|
|
|
+static void on_database_connect_write_ready(h2o_socket_t *sock, const char *err)
|
|
|
{
|
|
|
- if (db_conn->prepared_statement)
|
|
|
- on_database_connect_error(db_conn, false, error_string);
|
|
|
+ db_conn_t * const conn = sock->data;
|
|
|
+
|
|
|
+ if (err) {
|
|
|
+ ERROR(err);
|
|
|
+ on_database_connect_error(conn, false, err);
|
|
|
+ }
|
|
|
else {
|
|
|
- if (db_conn->param) {
|
|
|
- db_conn->param->on_error(db_conn->param, error_string);
|
|
|
- db_conn->param = NULL;
|
|
|
- }
|
|
|
+ const int send_status = PQflush(conn->conn);
|
|
|
|
|
|
- if (PQstatus(db_conn->conn) == CONNECTION_OK) {
|
|
|
- h2o_timeout_unlink(&db_conn->h2o_timeout_entry);
|
|
|
- h2o_socket_read_stop(db_conn->sock);
|
|
|
- process_query(db_conn);
|
|
|
+ if (send_status < 0) {
|
|
|
+ LIBRARY_ERROR("PQflush", PQerrorMessage(conn->conn));
|
|
|
+ on_database_connect_error(conn, false, DB_ERROR);
|
|
|
}
|
|
|
- else
|
|
|
- start_database_connect(db_conn->ctx, db_conn);
|
|
|
+ else if (send_status)
|
|
|
+ h2o_socket_notify_write(conn->sock, on_database_connect_write_ready);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-static void on_database_read_ready(h2o_socket_t *db_sock, const char *err)
|
|
|
+static void on_database_error(db_conn_t *conn, const char *error_string)
|
|
|
{
|
|
|
- db_conn_t * const db_conn = db_sock->data;
|
|
|
+ if (conn->queries.head)
|
|
|
+ do {
|
|
|
+ db_query_param_t * const param = H2O_STRUCT_FROM_MEMBER(db_query_param_t,
|
|
|
+ l,
|
|
|
+ conn->queries.head);
|
|
|
|
|
|
- if (err)
|
|
|
+ // The callback may free the db_query_param_t structure.
|
|
|
+ conn->queries.head = param->l.next;
|
|
|
+ param->on_error(param, error_string);
|
|
|
+ } while (conn->queries.head);
|
|
|
+
|
|
|
+ start_database_connect(conn->pool, conn);
|
|
|
+}
|
|
|
+
|
|
|
+static void on_database_read_ready(h2o_socket_t *sock, const char *err)
|
|
|
+{
|
|
|
+ db_conn_t * const conn = sock->data;
|
|
|
+
|
|
|
+ if (err) {
|
|
|
ERROR(err);
|
|
|
- else {
|
|
|
- if (PQconsumeInput(db_conn->conn)) {
|
|
|
- const int send_status = PQflush(db_conn->conn);
|
|
|
-
|
|
|
- if (send_status > 0)
|
|
|
- h2o_socket_notify_write(db_conn->sock, on_database_write_ready);
|
|
|
-
|
|
|
- if (send_status >= 0) {
|
|
|
- while (!PQisBusy(db_conn->conn)) {
|
|
|
- PGresult * const result = PQgetResult(db_conn->conn);
|
|
|
-
|
|
|
- if (db_conn->param)
|
|
|
- switch (db_conn->param->on_result(db_conn->param, result)) {
|
|
|
- case WANT_WRITE:
|
|
|
- db_conn->flags |= IS_WRITING;
|
|
|
-
|
|
|
- if (do_database_write(db_conn))
|
|
|
- return;
|
|
|
-
|
|
|
- break;
|
|
|
- case DONE:
|
|
|
- db_conn->param = NULL;
|
|
|
- h2o_timeout_unlink(&db_conn->h2o_timeout_entry);
|
|
|
- break;
|
|
|
- default:
|
|
|
- break;
|
|
|
- }
|
|
|
- else if (result) {
|
|
|
- if (PQresultStatus(result) != PGRES_COMMAND_OK)
|
|
|
- LIBRARY_ERROR("PQresultStatus", PQresultErrorMessage(result));
|
|
|
-
|
|
|
- PQclear(result);
|
|
|
- }
|
|
|
-
|
|
|
- if (!result) {
|
|
|
- assert(!db_conn->param);
|
|
|
- h2o_timeout_unlink(&db_conn->h2o_timeout_entry);
|
|
|
- h2o_socket_read_stop(db_conn->sock);
|
|
|
- process_query(db_conn);
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
+ on_database_error(conn, err);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!PQconsumeInput(conn->conn)) {
|
|
|
+ LIBRARY_ERROR("PQconsumeInput", PQerrorMessage(conn->conn));
|
|
|
+ on_database_error(conn, DB_ERROR);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const int send_status = PQflush(conn->conn);
|
|
|
|
|
|
+ if (send_status < 0) {
|
|
|
+ LIBRARY_ERROR("PQflush", PQerrorMessage(conn->conn));
|
|
|
+ on_database_error(conn, DB_ERROR);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ else if (send_status) {
|
|
|
+ h2o_socket_notify_write(conn->sock, on_database_write_ready);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ while (!PQisBusy(conn->conn)) {
|
|
|
+ PGresult * const result = PQgetResult(conn->conn);
|
|
|
+
|
|
|
+ if (conn->flags & IGNORE_RESULT) {
|
|
|
+ if (result)
|
|
|
+ PQclear(result);
|
|
|
+ else
|
|
|
+ conn->flags &= ~IGNORE_RESULT;
|
|
|
+ }
|
|
|
+ else if (conn->flags & EXPECT_SYNC) {
|
|
|
+ if (PQresultStatus(result) == PGRES_PIPELINE_SYNC) {
|
|
|
+ PQclear(result);
|
|
|
+ conn->flags &= ~EXPECT_SYNC;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ LIBRARY_ERROR("PQresultStatus", PQresultErrorMessage(result));
|
|
|
+ PQclear(result);
|
|
|
+ on_database_error(conn, DB_ERROR);
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- ERROR(PQerrorMessage(db_conn->conn));
|
|
|
+ else if (conn->queries.head) {
|
|
|
+ db_query_param_t * const param = H2O_STRUCT_FROM_MEMBER(db_query_param_t,
|
|
|
+ l,
|
|
|
+ conn->queries.head);
|
|
|
+ // The callback may free the db_query_param_t structure.
|
|
|
+ list_t * const next = param->l.next;
|
|
|
+ const bool nonnull_result = !!result;
|
|
|
+
|
|
|
+ if (param->on_result(param, result) == DONE) {
|
|
|
+ conn->query_num++;
|
|
|
+ h2o_timeout_unlink(&conn->timeout);
|
|
|
+ conn->timeout.cb = on_database_timeout;
|
|
|
+ h2o_timeout_link(conn->pool->loop, &conn->pool->timeout, &conn->timeout);
|
|
|
+ conn->flags |= EXPECT_SYNC;
|
|
|
+ conn->queries.head = next;
|
|
|
+
|
|
|
+ if (!next)
|
|
|
+ conn->queries.tail = &conn->queries.head;
|
|
|
+
|
|
|
+ if (nonnull_result)
|
|
|
+ conn->flags |= IGNORE_RESULT;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ assert(nonnull_result);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ assert(!result);
|
|
|
+ h2o_timeout_unlink(&conn->timeout);
|
|
|
+ h2o_socket_read_stop(conn->sock);
|
|
|
+ break;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- on_database_error(db_conn, DB_ERROR);
|
|
|
+ process_queries(conn);
|
|
|
}
|
|
|
|
|
|
-static void on_database_timeout(h2o_timeout_entry_t *entry)
|
|
|
+static void on_database_timeout(h2o_timeout_entry_t *timeout)
|
|
|
{
|
|
|
- db_conn_t * const db_conn = H2O_STRUCT_FROM_MEMBER(db_conn_t, h2o_timeout_entry, entry);
|
|
|
+ db_conn_t * const conn = H2O_STRUCT_FROM_MEMBER(db_conn_t, timeout, timeout);
|
|
|
|
|
|
ERROR(DB_TIMEOUT_ERROR);
|
|
|
|
|
|
- if (db_conn->param) {
|
|
|
- db_conn->param->on_timeout(db_conn->param);
|
|
|
- db_conn->param = NULL;
|
|
|
+ if (conn->queries.head) {
|
|
|
+ db_query_param_t * const param = H2O_STRUCT_FROM_MEMBER(db_query_param_t,
|
|
|
+ l,
|
|
|
+ conn->queries.head);
|
|
|
+
|
|
|
+ conn->queries.head = param->l.next;
|
|
|
+ param->on_timeout(param);
|
|
|
}
|
|
|
|
|
|
- start_database_connect(db_conn->ctx, db_conn);
|
|
|
+ on_database_error(conn, DB_TIMEOUT_ERROR);
|
|
|
}
|
|
|
|
|
|
-static void on_database_write_ready(h2o_socket_t *db_sock, const char *err)
|
|
|
+static void on_database_write_ready(h2o_socket_t *sock, const char *err)
|
|
|
{
|
|
|
- db_conn_t * const db_conn = db_sock->data;
|
|
|
+ db_conn_t * const conn = sock->data;
|
|
|
|
|
|
if (err) {
|
|
|
ERROR(err);
|
|
|
- on_database_error(db_conn, DB_ERROR);
|
|
|
+ on_database_error(conn, err);
|
|
|
}
|
|
|
else {
|
|
|
- const int send_status = PQflush(db_conn->conn);
|
|
|
+ const int send_status = PQflush(conn->conn);
|
|
|
|
|
|
- if (!send_status) {
|
|
|
- if (db_conn->flags & IS_WRITING && db_conn->param)
|
|
|
- do_database_write(db_conn);
|
|
|
+ if (send_status < 0) {
|
|
|
+ LIBRARY_ERROR("PQflush", PQerrorMessage(conn->conn));
|
|
|
+ on_database_error(conn, DB_ERROR);
|
|
|
}
|
|
|
- else if (send_status < 0) {
|
|
|
- LIBRARY_ERROR("PQflush", PQerrorMessage(db_conn->conn));
|
|
|
- on_database_error(db_conn, DB_ERROR);
|
|
|
+ else {
|
|
|
+ if (send_status)
|
|
|
+ h2o_socket_notify_write(conn->sock, on_database_write_ready);
|
|
|
+
|
|
|
+ process_queries(conn);
|
|
|
}
|
|
|
- else
|
|
|
- h2o_socket_notify_write(db_conn->sock, on_database_write_ready);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-static void poll_database_connection(h2o_socket_t *db_sock, const char *err)
|
|
|
+static void poll_database_connection(h2o_socket_t *sock, const char *err)
|
|
|
{
|
|
|
- db_conn_t * const db_conn = db_sock->data;
|
|
|
+ db_conn_t * const conn = sock->data;
|
|
|
|
|
|
if (err)
|
|
|
ERROR(err);
|
|
|
else {
|
|
|
- const PostgresPollingStatusType status = db_conn->flags & IS_RESETTING ?
|
|
|
- PQresetPoll(db_conn->conn) :
|
|
|
- PQconnectPoll(db_conn->conn);
|
|
|
+ const PostgresPollingStatusType status = conn->flags & IS_RESETTING ?
|
|
|
+ PQresetPoll(conn->conn) :
|
|
|
+ PQconnectPoll(conn->conn);
|
|
|
+ const int sd = PQsocket(conn->conn);
|
|
|
|
|
|
switch (status) {
|
|
|
case PGRES_POLLING_WRITING:
|
|
|
- if (!h2o_socket_is_writing(db_conn->sock))
|
|
|
- h2o_socket_notify_write(db_conn->sock, poll_database_connection);
|
|
|
+ h2o_socket_read_stop(conn->sock);
|
|
|
+
|
|
|
+ if (sd != conn->sd) {
|
|
|
+ h2o_socket_t * const sock = create_socket(sd, conn->pool->loop);
|
|
|
+
|
|
|
+ if (!sock)
|
|
|
+ break;
|
|
|
+
|
|
|
+ h2o_socket_close(conn->sock);
|
|
|
+ conn->sd = sd;
|
|
|
+ conn->sock = sock;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!h2o_socket_is_writing(conn->sock))
|
|
|
+ h2o_socket_notify_write(conn->sock, poll_database_connection);
|
|
|
|
|
|
- h2o_socket_read_stop(db_conn->sock);
|
|
|
return;
|
|
|
case PGRES_POLLING_OK:
|
|
|
- if (PQsetnonblocking(db_conn->conn, 1)) {
|
|
|
- LIBRARY_ERROR("PQsetnonblocking", PQerrorMessage(db_conn->conn));
|
|
|
+ h2o_timeout_unlink(&conn->timeout);
|
|
|
+ h2o_socket_read_stop(conn->sock);
|
|
|
+
|
|
|
+ if (PQsetnonblocking(conn->conn, 1)) {
|
|
|
+ LIBRARY_ERROR("PQsetnonblocking", PQerrorMessage(conn->conn));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!PQenterPipelineMode(conn->conn)) {
|
|
|
+ ERROR("PQenterPipelineMode");
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
- h2o_timeout_unlink(&db_conn->h2o_timeout_entry);
|
|
|
- h2o_socket_read_stop(db_conn->sock);
|
|
|
- process_query(db_conn);
|
|
|
+ if (sd != conn->sd) {
|
|
|
+ h2o_socket_t * const sock = create_socket(sd, conn->pool->loop);
|
|
|
+
|
|
|
+ if (!sock)
|
|
|
+ break;
|
|
|
+
|
|
|
+ h2o_socket_close(conn->sock);
|
|
|
+ conn->sd = sd;
|
|
|
+ conn->sock = sock;
|
|
|
+ }
|
|
|
+
|
|
|
+ prepare_statements(conn);
|
|
|
return;
|
|
|
case PGRES_POLLING_READING:
|
|
|
- h2o_socket_read_start(db_conn->sock, poll_database_connection);
|
|
|
+ if (sd != conn->sd) {
|
|
|
+ h2o_socket_t * const sock = create_socket(sd, conn->pool->loop);
|
|
|
+
|
|
|
+ if (!sock)
|
|
|
+ break;
|
|
|
+
|
|
|
+ h2o_socket_read_stop(conn->sock);
|
|
|
+ h2o_socket_close(conn->sock);
|
|
|
+ conn->sd = sd;
|
|
|
+ conn->sock = sock;
|
|
|
+ }
|
|
|
+
|
|
|
+ h2o_socket_read_start(conn->sock, poll_database_connection);
|
|
|
return;
|
|
|
default:
|
|
|
- ERROR(PQerrorMessage(db_conn->conn));
|
|
|
+ ERROR(PQerrorMessage(conn->conn));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- on_database_connect_error(db_conn, false, DB_ERROR);
|
|
|
+ on_database_connect_error(conn, false, DB_ERROR);
|
|
|
}
|
|
|
|
|
|
-static void process_query(db_conn_t *db_conn)
|
|
|
+static void prepare_statements(db_conn_t *conn)
|
|
|
{
|
|
|
- if (db_conn->prepared_statement) {
|
|
|
- const prepared_statement_t * const p = H2O_STRUCT_FROM_MEMBER(prepared_statement_t,
|
|
|
- l,
|
|
|
- db_conn->prepared_statement);
|
|
|
-
|
|
|
- if (PQsendPrepare(db_conn->conn, p->name, p->query, 0, NULL)) {
|
|
|
- db_conn->prepared_statement = p->l.next;
|
|
|
- db_conn->h2o_timeout_entry.cb = on_database_connect_timeout;
|
|
|
- h2o_timeout_link(db_conn->ctx->event_loop.h2o_ctx.loop,
|
|
|
- &db_conn->ctx->db_state.h2o_timeout,
|
|
|
- &db_conn->h2o_timeout_entry);
|
|
|
- h2o_socket_read_start(db_conn->sock, on_database_read_ready);
|
|
|
- on_database_write_ready(db_conn->sock, NULL);
|
|
|
- }
|
|
|
- else {
|
|
|
- LIBRARY_ERROR("PQsendPrepare", PQerrorMessage(db_conn->conn));
|
|
|
- on_database_connect_error(db_conn, false, DB_ERROR);
|
|
|
+ if (conn->prepared_statement) {
|
|
|
+ const list_t *iter = conn->prepared_statement;
|
|
|
+
|
|
|
+ do {
|
|
|
+ const prepared_statement_t * const p = H2O_STRUCT_FROM_MEMBER(prepared_statement_t,
|
|
|
+ l,
|
|
|
+ iter);
|
|
|
+
|
|
|
+ if (!PQsendPrepare(conn->conn, p->name, p->query, 0, NULL)) {
|
|
|
+ LIBRARY_ERROR("PQsendPrepare", PQerrorMessage(conn->conn));
|
|
|
+ on_database_connect_error(conn, false, DB_ERROR);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ iter = iter->next;
|
|
|
+ } while (iter);
|
|
|
+
|
|
|
+ if (!PQpipelineSync(conn->conn)) {
|
|
|
+ LIBRARY_ERROR("PQpipelineSync", PQerrorMessage(conn->conn));
|
|
|
+ on_database_connect_error(conn, false, DB_ERROR);
|
|
|
+ return;
|
|
|
}
|
|
|
+
|
|
|
+ conn->prepared_statement = NULL;
|
|
|
+ conn->timeout.cb = on_database_connect_timeout;
|
|
|
+ h2o_timeout_link(conn->pool->loop, &conn->pool->timeout, &conn->timeout);
|
|
|
+ h2o_socket_read_start(conn->sock, on_database_connect_read_ready);
|
|
|
+ on_database_connect_write_ready(conn->sock, NULL);
|
|
|
}
|
|
|
- else if (db_conn->ctx->db_state.query_num) {
|
|
|
- db_conn->ctx->db_state.query_num--;
|
|
|
+ else
|
|
|
+ process_queries(conn);
|
|
|
+}
|
|
|
|
|
|
- if (db_conn->ctx->db_state.queries.tail == &db_conn->ctx->db_state.queries.head->next) {
|
|
|
- assert(!db_conn->ctx->db_state.query_num);
|
|
|
- db_conn->ctx->db_state.queries.tail = &db_conn->ctx->db_state.queries.head;
|
|
|
+static void process_queries(db_conn_t *conn)
|
|
|
+{
|
|
|
+ while (conn->query_num && conn->pool->queries.head) {
|
|
|
+ db_query_param_t * const param = H2O_STRUCT_FROM_MEMBER(db_query_param_t,
|
|
|
+ l,
|
|
|
+ conn->pool->queries.head);
|
|
|
+
|
|
|
+ if (++conn->pool->query_num == conn->pool->config->max_query_num) {
|
|
|
+ assert(conn->pool->queries.tail == ¶m->l.next);
|
|
|
+ conn->pool->queries.tail = &conn->pool->queries.head;
|
|
|
}
|
|
|
|
|
|
- db_conn->param = H2O_STRUCT_FROM_MEMBER(db_query_param_t,
|
|
|
- l,
|
|
|
- db_conn->ctx->db_state.queries.head);
|
|
|
- db_conn->ctx->db_state.queries.head = db_conn->ctx->db_state.queries.head->next;
|
|
|
- do_execute_query(db_conn, false);
|
|
|
+ conn->pool->queries.head = param->l.next;
|
|
|
+
|
|
|
+ if (do_execute_query(conn, param)) {
|
|
|
+ param->on_error(param, DB_ERROR);
|
|
|
+
|
|
|
+ if (PQstatus(conn->conn) != CONNECTION_OK) {
|
|
|
+ on_database_error(conn, DB_ERROR);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
- else {
|
|
|
- db_conn->l.next = db_conn->ctx->db_state.db_conn;
|
|
|
- db_conn->ctx->db_state.db_conn = &db_conn->l;
|
|
|
- db_conn->ctx->db_state.free_db_conn_num++;
|
|
|
+
|
|
|
+ if (!conn->queries.head && !(conn->flags & (EXPECT_SYNC | IGNORE_RESULT))) {
|
|
|
+ conn->l.next = conn->pool->conn;
|
|
|
+ conn->pool->conn = &conn->l;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-static void start_database_connect(thread_context_t *ctx, db_conn_t *db_conn)
|
|
|
+static void start_database_connect(db_conn_pool_t *pool, db_conn_t *conn)
|
|
|
{
|
|
|
- if (db_conn) {
|
|
|
- db_conn->flags = IS_RESETTING;
|
|
|
- h2o_timeout_unlink(&db_conn->h2o_timeout_entry);
|
|
|
- h2o_socket_read_stop(db_conn->sock);
|
|
|
- h2o_socket_close(db_conn->sock);
|
|
|
-
|
|
|
- if (!PQresetStart(db_conn->conn)) {
|
|
|
- LIBRARY_ERROR("PQresetStart", PQerrorMessage(db_conn->conn));
|
|
|
+ if (conn) {
|
|
|
+ PGconn * const c = conn->conn;
|
|
|
+
|
|
|
+ h2o_timeout_unlink(&conn->timeout);
|
|
|
+ h2o_socket_read_stop(conn->sock);
|
|
|
+ h2o_socket_close(conn->sock);
|
|
|
+
|
|
|
+ if (!PQresetStart(c)) {
|
|
|
+ LIBRARY_ERROR("PQresetStart", PQerrorMessage(c));
|
|
|
goto error_dup;
|
|
|
}
|
|
|
+
|
|
|
+ memset(conn, 0, sizeof(*conn));
|
|
|
+ conn->conn = c;
|
|
|
+ conn->flags = IS_RESETTING;
|
|
|
}
|
|
|
else {
|
|
|
- ctx->db_state.db_conn_num++;
|
|
|
- db_conn = h2o_mem_alloc(sizeof(*db_conn));
|
|
|
- memset(db_conn, 0, sizeof(*db_conn));
|
|
|
-
|
|
|
- const char * const conninfo = ctx->config->db_host ? ctx->config->db_host : "";
|
|
|
-
|
|
|
- db_conn->conn = PQconnectStart(conninfo);
|
|
|
+ assert(pool->conn_num);
|
|
|
+ pool->conn_num--;
|
|
|
+ conn = h2o_mem_alloc(sizeof(*conn));
|
|
|
+ memset(conn, 0, sizeof(*conn));
|
|
|
+ conn->conn = PQconnectStart(pool->conninfo);
|
|
|
|
|
|
- if (!db_conn->conn) {
|
|
|
+ if (!conn->conn) {
|
|
|
errno = ENOMEM;
|
|
|
STANDARD_ERROR("PQconnectStart");
|
|
|
goto error_connect;
|
|
|
}
|
|
|
|
|
|
- if (PQstatus(db_conn->conn) == CONNECTION_BAD) {
|
|
|
- LIBRARY_ERROR("PQstatus", PQerrorMessage(db_conn->conn));
|
|
|
+ if (PQstatus(conn->conn) == CONNECTION_BAD) {
|
|
|
+ LIBRARY_ERROR("PQstatus", PQerrorMessage(conn->conn));
|
|
|
goto error_dup;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- const int sd = dup(PQsocket(db_conn->conn));
|
|
|
-
|
|
|
- if (sd < 0) {
|
|
|
- STANDARD_ERROR("dup");
|
|
|
- goto error_dup;
|
|
|
- }
|
|
|
-
|
|
|
- const int flags = fcntl(sd, F_GETFD);
|
|
|
-
|
|
|
- if (flags < 0 || fcntl(sd, F_SETFD, flags | FD_CLOEXEC)) {
|
|
|
- STANDARD_ERROR("fcntl");
|
|
|
- goto error_fcntl;
|
|
|
- }
|
|
|
-
|
|
|
- db_conn->sock = h2o_evloop_socket_create(ctx->event_loop.h2o_ctx.loop,
|
|
|
- sd,
|
|
|
- H2O_SOCKET_FLAG_DONT_READ);
|
|
|
-
|
|
|
- if (db_conn->sock) {
|
|
|
- db_conn->sock->data = db_conn;
|
|
|
- db_conn->ctx = ctx;
|
|
|
- db_conn->h2o_timeout_entry.cb = on_database_connect_timeout;
|
|
|
- db_conn->prepared_statement = ctx->global_data->prepared_statements;
|
|
|
- h2o_timeout_link(ctx->event_loop.h2o_ctx.loop,
|
|
|
- &ctx->db_state.h2o_timeout,
|
|
|
- &db_conn->h2o_timeout_entry);
|
|
|
- h2o_socket_notify_write(db_conn->sock, poll_database_connection);
|
|
|
+ conn->sd = PQsocket(conn->conn);
|
|
|
+ conn->sock = create_socket(conn->sd, pool->loop);
|
|
|
+
|
|
|
+ if (conn->sock) {
|
|
|
+ conn->sock->data = conn;
|
|
|
+ conn->pool = pool;
|
|
|
+ conn->prepared_statement = pool->prepared_statements;
|
|
|
+ conn->queries.tail = &conn->queries.head;
|
|
|
+ conn->query_num = pool->config->max_pipeline_query_num;
|
|
|
+ conn->timeout.cb = on_database_connect_timeout;
|
|
|
+ h2o_timeout_link(pool->loop, &pool->timeout, &conn->timeout);
|
|
|
+ h2o_socket_notify_write(conn->sock, poll_database_connection);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- errno = ENOMEM;
|
|
|
- STANDARD_ERROR("h2o_evloop_socket_create");
|
|
|
-error_fcntl:
|
|
|
- close(sd);
|
|
|
error_dup:
|
|
|
- PQfinish(db_conn->conn);
|
|
|
+ PQfinish(conn->conn);
|
|
|
error_connect:
|
|
|
- free(db_conn);
|
|
|
- error_notification(ctx, false, DB_ERROR);
|
|
|
+ free(conn);
|
|
|
+ error_notification(pool, false, DB_ERROR);
|
|
|
}
|
|
|
|
|
|
void add_prepared_statement(const char *name, const char *query, list_t **prepared_statements)
|
|
@@ -465,27 +634,36 @@ void add_prepared_statement(const char *name, const char *query, list_t **prepar
|
|
|
*prepared_statements = &p->l;
|
|
|
}
|
|
|
|
|
|
-int execute_query(thread_context_t *ctx, db_query_param_t *param)
|
|
|
+int execute_database_query(db_conn_pool_t *pool, db_query_param_t *param)
|
|
|
{
|
|
|
int ret = 1;
|
|
|
|
|
|
- if (ctx->db_state.free_db_conn_num) {
|
|
|
- db_conn_t * const db_conn = H2O_STRUCT_FROM_MEMBER(db_conn_t, l, ctx->db_state.db_conn);
|
|
|
+ if (pool->conn) {
|
|
|
+ db_conn_t * const conn = H2O_STRUCT_FROM_MEMBER(db_conn_t, l, pool->conn);
|
|
|
+
|
|
|
+ assert(!conn->queries.head);
|
|
|
+ assert(!(conn->flags & (EXPECT_SYNC | IGNORE_RESULT)));
|
|
|
+ pool->conn = conn->l.next;
|
|
|
+ ret = do_execute_query(conn, param);
|
|
|
|
|
|
- ctx->db_state.db_conn = db_conn->l.next;
|
|
|
- ctx->db_state.free_db_conn_num--;
|
|
|
- db_conn->param = param;
|
|
|
- ret = do_execute_query(db_conn, true);
|
|
|
+ if (ret) {
|
|
|
+ if (PQstatus(conn->conn) == CONNECTION_OK) {
|
|
|
+ conn->l.next = pool->conn;
|
|
|
+ pool->conn = &conn->l;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ start_database_connect(conn->pool, conn);
|
|
|
+ }
|
|
|
}
|
|
|
- else if (ctx->db_state.query_num < ctx->config->max_query_num) {
|
|
|
- if (ctx->db_state.db_conn_num < ctx->config->max_db_conn_num)
|
|
|
- start_database_connect(ctx, NULL);
|
|
|
+ else if (pool->query_num) {
|
|
|
+ if (pool->conn_num)
|
|
|
+ start_database_connect(pool, NULL);
|
|
|
|
|
|
- if (ctx->db_state.db_conn_num) {
|
|
|
+ if (pool->conn_num < pool->config->max_db_conn_num && pool->query_num) {
|
|
|
param->l.next = NULL;
|
|
|
- *ctx->db_state.queries.tail = ¶m->l;
|
|
|
- ctx->db_state.queries.tail = ¶m->l.next;
|
|
|
- ctx->db_state.query_num++;
|
|
|
+ *pool->queries.tail = ¶m->l;
|
|
|
+ pool->queries.tail = ¶m->l.next;
|
|
|
+ pool->query_num--;
|
|
|
ret = 0;
|
|
|
}
|
|
|
}
|
|
@@ -493,31 +671,48 @@ int execute_query(thread_context_t *ctx, db_query_param_t *param)
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
-void free_database_state(h2o_loop_t *loop, db_state_t *db_state)
|
|
|
+void free_database_connection_pool(db_conn_pool_t *pool)
|
|
|
{
|
|
|
- assert(!db_state->query_num && db_state->free_db_conn_num == db_state->db_conn_num);
|
|
|
+ assert(!pool->queries.head);
|
|
|
+ assert(pool->query_num == pool->config->max_query_num);
|
|
|
|
|
|
- list_t *iter = db_state->db_conn;
|
|
|
+ size_t num = 0;
|
|
|
|
|
|
- if (iter)
|
|
|
+ if (pool->conn)
|
|
|
do {
|
|
|
- db_conn_t * const db_conn = H2O_STRUCT_FROM_MEMBER(db_conn_t, l, iter);
|
|
|
-
|
|
|
- iter = iter->next;
|
|
|
- assert(!db_conn->param && !h2o_timeout_is_linked(&db_conn->h2o_timeout_entry));
|
|
|
- h2o_socket_close(db_conn->sock);
|
|
|
- PQfinish(db_conn->conn);
|
|
|
- free(db_conn);
|
|
|
- } while (iter);
|
|
|
-
|
|
|
- h2o_timeout_dispose(loop, &db_state->h2o_timeout);
|
|
|
+ db_conn_t * const conn = H2O_STRUCT_FROM_MEMBER(db_conn_t, l, pool->conn);
|
|
|
+
|
|
|
+ assert(!conn->queries.head);
|
|
|
+ assert(conn->query_num == pool->config->max_pipeline_query_num);
|
|
|
+ assert(!(conn->flags & (EXPECT_SYNC | IGNORE_RESULT)));
|
|
|
+ assert(!h2o_timeout_is_linked(&conn->timeout));
|
|
|
+ h2o_socket_read_stop(conn->sock);
|
|
|
+ h2o_socket_close(conn->sock);
|
|
|
+ PQfinish(conn->conn);
|
|
|
+ pool->conn = pool->conn->next;
|
|
|
+ free(conn);
|
|
|
+ num++;
|
|
|
+ } while (pool->conn);
|
|
|
+
|
|
|
+ assert(num + pool->conn_num == pool->config->max_db_conn_num);
|
|
|
+ h2o_timeout_dispose(pool->loop, &pool->timeout);
|
|
|
}
|
|
|
|
|
|
-void initialize_database_state(h2o_loop_t *loop, db_state_t *db_state)
|
|
|
+void initialize_database_connection_pool(const char *conninfo,
|
|
|
+ const struct config_t *config,
|
|
|
+ const list_t *prepared_statements,
|
|
|
+ h2o_loop_t *loop,
|
|
|
+ db_conn_pool_t *pool)
|
|
|
{
|
|
|
- memset(db_state, 0, sizeof(*db_state));
|
|
|
- db_state->queries.tail = &db_state->queries.head;
|
|
|
- h2o_timeout_init(loop, &db_state->h2o_timeout, H2O_DEFAULT_HTTP1_REQ_TIMEOUT);
|
|
|
+ memset(pool, 0, sizeof(*pool));
|
|
|
+ pool->config = config;
|
|
|
+ pool->conninfo = conninfo ? conninfo : "";
|
|
|
+ pool->loop = loop;
|
|
|
+ pool->prepared_statements = prepared_statements;
|
|
|
+ pool->queries.tail = &pool->queries.head;
|
|
|
+ pool->conn_num = config->max_db_conn_num;
|
|
|
+ pool->query_num = config->max_query_num;
|
|
|
+ h2o_timeout_init(loop, &pool->timeout, config->db_timeout * MS_IN_S);
|
|
|
}
|
|
|
|
|
|
void remove_prepared_statements(list_t *prepared_statements)
|