struct rspamd_redis_pool_elt;
+enum rspamd_redis_pool_connection_state {
+ RSPAMD_REDIS_POOL_CONN_INACTIVE = 0,
+ RSPAMD_REDIS_POOL_CONN_ACTIVE,
+ RSPAMD_REDIS_POOL_CONN_FINALISING
+};
+
struct rspamd_redis_pool_connection {
struct redisAsyncContext *ctx;
struct rspamd_redis_pool_elt *elt;
GList *entry;
ev_timer timeout;
- gboolean active;
+ enum rspamd_redis_pool_connection_state state;
gchar tag[MEMPOOL_UID_LEN];
ref_entry_t ref;
};
static void
rspamd_redis_pool_conn_dtor (struct rspamd_redis_pool_connection *conn)
{
- if (conn->active) {
+ if (conn->state == RSPAMD_REDIS_POOL_CONN_ACTIVE) {
msg_debug_rpool ("active connection removed");
if (conn->ctx) {
redisAsyncContext *ac = conn->ctx;
/* To prevent on_disconnect here */
- conn->active = TRUE;
+ conn->state = RSPAMD_REDIS_POOL_CONN_FINALISING;
g_hash_table_remove (conn->elt->pool->elts_by_ctx, ac);
conn->ctx = NULL;
ac->onDisconnect = NULL;
g_free (elt);
}
+static void
+rspamd_redis_on_quit (redisAsyncContext *c, gpointer r, gpointer priv)
+{
+ struct rspamd_redis_pool_connection *conn =
+ (struct rspamd_redis_pool_connection *)priv;
+
+ msg_debug_rpool ("quit command reply for the connection %p, refcount: %d",
+ conn->ctx, conn->ref.refcount);
+ /*
+ * We now schedule timer to enforce removal after callback is executed
+ * to prevent races. But actually, the connection will likely be freed by
+ * hiredis itself. It is quite brain damaged logic but it is better to
+ * deal with it... Dtor will definitely stop this timer.
+ */
+ conn->timeout.repeat = 0.1;
+ ev_timer_again (conn->elt->pool->event_loop, &conn->timeout);
+}
+
static void
rspamd_redis_conn_timeout (EV_P_ ev_timer *w, int revents)
{
struct rspamd_redis_pool_connection *conn =
(struct rspamd_redis_pool_connection *)w->data;
- g_assert (!conn->active);
- msg_debug_rpool ("scheduled removal of connection %p, refcount: %d",
- conn->ctx, conn->ref.refcount);
- REF_RELEASE (conn);
+ g_assert (conn->state != RSPAMD_REDIS_POOL_CONN_ACTIVE);
+
+ if (conn->state == RSPAMD_REDIS_POOL_CONN_INACTIVE) {
+ msg_debug_rpool ("scheduled soft removal of connection %p, refcount: %d",
+ conn->ctx, conn->ref.refcount);
+ redisAsyncCommand (conn->ctx, rspamd_redis_on_quit, conn, "QUIT");
+ conn->state = RSPAMD_REDIS_POOL_CONN_FINALISING;
+ ev_timer_again (EV_A_ w);
+
+ /* Prevent reusing */
+ if (conn->entry) {
+ g_queue_unlink (conn->elt->inactive, conn->entry);
+ conn->entry = NULL;
+ }
+ }
+ else {
+ /* Finalising by timeout */
+ msg_debug_rpool ("final removal of connection %p, refcount: %d",
+ conn->ctx, conn->ref.refcount);
+ REF_RELEASE (conn);
+ }
+
}
static void
conn->timeout.data = conn;
ev_timer_init (&conn->timeout,
rspamd_redis_conn_timeout,
- real_timeout, 0.0);
+ real_timeout, real_timeout / 2.0);
ev_timer_start (conn->elt->pool->event_loop, &conn->timeout);
}
* Here, we know that redis itself will free this connection
* so, we need to do something very clever about it
*/
-
- if (!conn->active) {
+ if (conn->state != RSPAMD_REDIS_POOL_CONN_ACTIVE) {
/* Do nothing for active connections as it is already handled somewhere */
if (conn->ctx) {
msg_debug_rpool ("inactive connection terminated: %s, refs: %d",
conn = g_malloc0 (sizeof (*conn));
conn->entry = g_list_prepend (NULL, conn);
conn->elt = elt;
- conn->active = TRUE;
+ conn->state = RSPAMD_REDIS_POOL_CONN_ACTIVE;
g_hash_table_insert (elt->pool->elts_by_ctx, ctx, conn);
g_queue_push_head_link (elt->active, conn->entry);
conn);
if (password) {
- redisAsyncCommand (ctx, NULL, NULL, "AUTH %s", password);
+ redisAsyncCommand (ctx, NULL, NULL,
+ "AUTH %s", password);
}
if (db) {
- redisAsyncCommand (ctx, NULL, NULL, "SELECT %s", db);
+ redisAsyncCommand (ctx, NULL, NULL,
+ "SELECT %s", db);
}
}
struct rspamd_redis_pool *pool;
pool = g_malloc0 (sizeof (*pool));
- pool->elts_by_key = g_hash_table_new_full (g_int64_hash, g_int64_equal, NULL,
- rspamd_redis_pool_elt_dtor);
+ pool->elts_by_key = g_hash_table_new_full (g_int64_hash, g_int64_equal,
+ NULL, rspamd_redis_pool_elt_dtor);
pool->elts_by_ctx = g_hash_table_new (g_direct_hash, g_direct_equal);
return pool;
if (g_queue_get_length (elt->inactive) > 0) {
conn_entry = g_queue_pop_head_link (elt->inactive);
conn = conn_entry->data;
- g_assert (!conn->active);
+ g_assert (conn->state != RSPAMD_REDIS_POOL_CONN_ACTIVE);
if (conn->ctx->err == REDIS_OK) {
ev_timer_stop (elt->pool->event_loop, &conn->timeout);
- conn->active = TRUE;
+ conn->state = RSPAMD_REDIS_POOL_CONN_ACTIVE;
g_queue_push_tail_link (elt->active, conn_entry);
msg_debug_rpool ("reused existing connection to %s:%d: %p",
ip, port, conn->ctx);
conn = g_hash_table_lookup (pool->elts_by_ctx, ctx);
if (conn != NULL) {
- g_assert (conn->active);
+ g_assert (conn->state == RSPAMD_REDIS_POOL_CONN_ACTIVE);
if (ctx->err != REDIS_OK) {
/* We need to terminate connection forcefully */
/* Just move it to the inactive queue */
g_queue_unlink (conn->elt->active, conn->entry);
g_queue_push_head_link (conn->elt->inactive, conn->entry);
- conn->active = FALSE;
+ conn->state = RSPAMD_REDIS_POOL_CONN_INACTIVE;
rspamd_redis_pool_schedule_timeout (conn);
msg_debug_rpool ("mark connection %p inactive", conn->ctx);
}