/** * PRE: * - The base must be owned by the current thread * - it must be opened only once and locked only once * - the cache-wide lock must be owned by the current thread * * POST: * - The base is returned to the FREE list * - the base is not owned by any thread * - The cache-wide lock is still owned */ static void _expire_base(sqlx_cache_t *cache, sqlx_base_t *b) { hashstr_t *n = b->name; gpointer handle = b->handle; sqlx_base_debug("FREEING", b); EXTRA_ASSERT(b->owner != NULL); EXTRA_ASSERT(b->count_open == 0); EXTRA_ASSERT(b->status == SQLX_BASE_USED); sqlx_base_move_to_list(cache, b, SQLX_BASE_CLOSING); /* the base is for the given thread, it is time to REALLY close it. * But this can take a lot of time. So we can release the pool, * free the handle and unlock the cache */ g_cond_signal(b->cond); g_mutex_unlock(&cache->lock); if (cache->close_hook) cache->close_hook(handle); g_mutex_lock(&cache->lock); b->handle = NULL; b->owner = NULL; b->name = NULL; b->count_open = 0; b->last_update.tv_sec = b->last_update.tv_usec = 0; sqlx_base_move_to_list(cache, b, SQLX_BASE_FREE); g_tree_remove(cache->bases_by_name, n); g_free(n); }
GError * sqlx_cache_unlock_and_close_base(sqlx_cache_t *cache, gint bd, gboolean force) { GError *err = NULL; GRID_TRACE2("%s(%p,%d,%d)", __FUNCTION__, (void*)cache, bd, force); EXTRA_ASSERT(cache != NULL); if (base_id_out(cache, bd)) return NEWERROR(CODE_INTERNAL_ERROR, "invalid base id=%d", bd); g_mutex_lock(&cache->lock); cache->used = TRUE; sqlx_base_t *base; base = GET(cache,bd); switch (base->status) { case SQLX_BASE_FREE: EXTRA_ASSERT(base->count_open == 0); EXTRA_ASSERT(base->owner == NULL); err = NEWERROR(CODE_INTERNAL_ERROR, "base not used"); break; case SQLX_BASE_IDLE: case SQLX_BASE_IDLE_HOT: EXTRA_ASSERT(base->count_open == 0); EXTRA_ASSERT(base->owner == NULL); err = NEWERROR(CODE_INTERNAL_ERROR, "base closed"); break; case SQLX_BASE_USED: EXTRA_ASSERT(base->count_open > 0); // held by the current thread if (!(-- base->count_open)) { // to be closed if (force) { _expire_base(cache, base); } else { sqlx_base_debug("CLOSING", base); base->owner = NULL; if (base->heat >= cache->heat_threshold) sqlx_base_move_to_list(cache, base, SQLX_BASE_IDLE_HOT); else sqlx_base_move_to_list(cache, base, SQLX_BASE_IDLE); } } break; case SQLX_BASE_CLOSING: EXTRA_ASSERT(base->owner != NULL); EXTRA_ASSERT(base->owner != g_thread_self()); err = NEWERROR(CODE_INTERNAL_ERROR, "base being closed"); break; } if (base && !err) sqlx_base_debug(__FUNCTION__, base); g_cond_signal(base->cond); g_mutex_unlock(&cache->lock); return err; }
static gint _expire_specific_base(sqlx_cache_t *cache, sqlx_base_t *b, GTimeVal *now, time_t grace_delay) { if (now) { GTimeVal pivot; memcpy(&pivot, now, sizeof(GTimeVal)); g_time_val_add(&pivot, grace_delay * -1000000L); if (gtv_bigger(&(b->last_update), &pivot)) return 0; } /* At this point, I have the global lock, and the base is IDLE. * We know no one have the lock on it. So we make the base USED * and we get the lock on it. because we have the lock, it is * protected from other uses */ EXTRA_ASSERT(b->status == SQLX_BASE_IDLE || b->status == SQLX_BASE_IDLE_HOT); EXTRA_ASSERT(b->count_open == 0); EXTRA_ASSERT(b->owner == NULL); /* make it used and locked by the current thread */ b->owner = g_thread_self(); sqlx_base_move_to_list(cache, b, SQLX_BASE_USED); _expire_base(cache, b); /* If someone is waiting on the base while it is being closed * (this arrives when someone tries to read it again after * waiting exactly the grace delay), we must notify him so it can * retry (and open it in another file descriptor). */ g_cond_signal(b->cond); return 1; }
static GError * sqlx_base_reserve(sqlx_cache_t *cache, const hashstr_t *hs, sqlx_base_t **result) { sqlx_base_t *base; if (!(base = sqlx_poll_free_base(cache))) return NEWERROR(CODE_INTERNAL_ERROR, "too many bases"); /* base reserved and in PENDING state */ base->name = hashstr_dup(hs); base->count_open = 1; base->handle = NULL; base->owner = g_thread_self(); sqlx_base_move_to_list(cache, base, SQLX_BASE_USED); sqlx_save_id(cache, base); sqlx_base_debug(__FUNCTION__, base); *result = base; return NULL; }
GError * sqlx_cache_open_and_lock_base(sqlx_cache_t *cache, const hashstr_t *hname, gint *result) { gint bd; GError *err = NULL; sqlx_base_t *base = NULL; GRID_TRACE2("%s(%p,%s,%p)", __FUNCTION__, (void*)cache, hname ? hashstr_str(hname) : "NULL", (void*)result); EXTRA_ASSERT(cache != NULL); EXTRA_ASSERT(hname != NULL); EXTRA_ASSERT(result != NULL); gint64 deadline = g_get_monotonic_time(); if (cache->open_timeout >= 0) { deadline += cache->open_timeout * G_TIME_SPAN_MILLISECOND; } else { deadline += 5 * G_TIME_SPAN_MINUTE; } g_mutex_lock(&cache->lock); cache->used = TRUE; retry: bd = sqlx_lookup_id(cache, hname); if (bd < 0) { if (!(err = sqlx_base_reserve(cache, hname, &base))) { bd = base->index; *result = base->index; sqlx_base_debug("OPEN", base); } else { GRID_DEBUG("No base available for [%s] (%d %s)", hashstr_str(hname), err->code, err->message); if (sqlx_expire_first_idle_base(cache, NULL) >= 0) { g_clear_error(&err); goto retry; } } } else { base = GET(cache, bd); switch (base->status) { case SQLX_BASE_FREE: EXTRA_ASSERT(base->count_open == 0); EXTRA_ASSERT(base->owner == NULL); GRID_ERROR("free base referenced"); g_assert_not_reached(); break; case SQLX_BASE_IDLE: case SQLX_BASE_IDLE_HOT: EXTRA_ASSERT(base->count_open == 0); EXTRA_ASSERT(base->owner == NULL); sqlx_base_move_to_list(cache, base, SQLX_BASE_USED); base->count_open ++; base->owner = g_thread_self(); *result = base->index; break; case SQLX_BASE_USED: EXTRA_ASSERT(base->count_open > 0); EXTRA_ASSERT(base->owner != NULL); if (base->owner != g_thread_self()) { GRID_DEBUG("Base [%s] in use by another thread (%X), waiting...", hashstr_str(hname), oio_log_thread_id(base->owner)); // The lock is held by another thread/request if (g_cond_wait_until(base->cond, &cache->lock, deadline)) { GRID_DEBUG("Retrying to open [%s]", hashstr_str(hname)); goto retry; } else { if (cache->open_timeout > 0) { err = NEWERROR(CODE_UNAVAILABLE, "database currently in use by another request" " (we waited %ldms)", cache->open_timeout); } else { err = NEWERROR(CODE_UNAVAILABLE, "database currently in use by another request"); } GRID_DEBUG("failed to open base: " "in use by another request (thread %X)", oio_log_thread_id(base->owner)); break; } } base->owner = g_thread_self(); base->count_open ++; *result = base->index; break; case SQLX_BASE_CLOSING: EXTRA_ASSERT(base->owner != NULL); // Just wait for a notification then retry if (g_cond_wait_until(base->cond, &cache->lock, deadline)) goto retry; else { err = NEWERROR(CODE_UNAVAILABLE, "Database stuck in closing state"); break; } } } if (base) { if (!err) { sqlx_base_debug(__FUNCTION__, base); EXTRA_ASSERT(base->owner == g_thread_self()); EXTRA_ASSERT(base->count_open > 0); } g_cond_signal(base->cond); } g_mutex_unlock(&cache->lock); return err; }
GError * sqlx_cache_open_and_lock_base(sqlx_cache_t *cache, const hashstr_t *hname, gint *result) { gint bd; GError *err = NULL; sqlx_base_t *base = NULL; EXTRA_ASSERT(cache != NULL); EXTRA_ASSERT(hname != NULL); EXTRA_ASSERT(result != NULL); gint64 start = oio_ext_monotonic_time(); gint64 deadline = DEFAULT_CACHE_OPEN_TIMEOUT; if (cache->open_timeout > 0) deadline = cache->open_timeout; GRID_TRACE2("%s(%p,%s,%p) delay = %"G_GINT64_FORMAT, __FUNCTION__, (void*)cache, hname ? hashstr_str(hname) : "NULL", (void*)result, deadline); deadline += start; g_mutex_lock(&cache->lock); cache->used = TRUE; retry: bd = sqlx_lookup_id(cache, hname); if (bd < 0) { if (!(err = sqlx_base_reserve(cache, hname, &base))) { bd = base->index; *result = base->index; sqlx_base_debug("OPEN", base); } else { GRID_DEBUG("No base available for [%s] (%d %s)", hashstr_str(hname), err->code, err->message); if (sqlx_expire_first_idle_base(cache, 0) >= 0) { g_clear_error(&err); goto retry; } } } else { base = GET(cache, bd); gint64 now = oio_ext_monotonic_time (); if (now > deadline) { err = NEWERROR (CODE_UNAVAILABLE, "DB busy (after %"G_GINT64_FORMAT" ms)", (now - start) / G_TIME_SPAN_MILLISECOND); } else switch (base->status) { case SQLX_BASE_FREE: EXTRA_ASSERT(base->count_open == 0); EXTRA_ASSERT(base->owner == NULL); GRID_ERROR("free base referenced"); g_assert_not_reached(); break; case SQLX_BASE_IDLE: case SQLX_BASE_IDLE_HOT: EXTRA_ASSERT(base->count_open == 0); EXTRA_ASSERT(base->owner == NULL); sqlx_base_move_to_list(cache, base, SQLX_BASE_USED); base->count_open ++; base->owner = g_thread_self(); *result = base->index; break; case SQLX_BASE_USED: EXTRA_ASSERT(base->count_open > 0); EXTRA_ASSERT(base->owner != NULL); if (base->owner != g_thread_self()) { GRID_DEBUG("Base [%s] in use by another thread (%X), waiting...", hashstr_str(hname), oio_log_thread_id(base->owner)); /* The lock is held by another thread/request. XXX(jfs): do not use 'now' because it can be a fake clock */ g_cond_wait_until(base->cond, &cache->lock, g_get_monotonic_time() + oio_cache_period_cond_wait); goto retry; } base->owner = g_thread_self(); base->count_open ++; *result = base->index; break; case SQLX_BASE_CLOSING: EXTRA_ASSERT(base->owner != NULL); /* Just wait for a notification then retry XXX(jfs): do not use 'now' because it can be a fake clock */ g_cond_wait_until(base->cond, &cache->lock, g_get_monotonic_time() + oio_cache_period_cond_wait); goto retry; } } if (base) { if (!err) { sqlx_base_debug(__FUNCTION__, base); EXTRA_ASSERT(base->owner == g_thread_self()); EXTRA_ASSERT(base->count_open > 0); } g_cond_signal(base->cond); } g_mutex_unlock(&cache->lock); return err; }
GError * sqlx_cache_open_and_lock_base(sqlx_cache_t *cache, const hashstr_t *hname, gint *result) { gint bd; GError *err = NULL; sqlx_base_t *base = NULL; GTimeVal *deadline = g_alloca(sizeof(GTimeVal)); GRID_TRACE2("%s(%p,%s,%p)", __FUNCTION__, (void*)cache, hname ? hashstr_str(hname) : "NULL", (void*)result); EXTRA_ASSERT(cache != NULL); EXTRA_ASSERT(hname != NULL); EXTRA_ASSERT(result != NULL); if (cache->open_timeout >= 0) { g_get_current_time(deadline); g_time_val_add(deadline, cache->open_timeout * 1000); } else { // wait forever deadline = NULL; } g_mutex_lock(cache->lock); cache->used = TRUE; retry: bd = sqlx_lookup_id(cache, hname); if (bd < 0) { if (!(err = sqlx_base_reserve(cache, hname, &base))) { bd = base->index; *result = base->index; sqlx_base_debug("OPEN", base); } else { GRID_DEBUG("No base available for [%s] (%d %s)", hashstr_str(hname), err->code, err->message); if (sqlx_expire_first_idle_base(cache, NULL) >= 0) { g_clear_error(&err); goto retry; } } } else { base = GET(cache, bd); switch (base->status) { case SQLX_BASE_FREE: EXTRA_ASSERT(base->count_open == 0); EXTRA_ASSERT(base->owner == NULL); GRID_ERROR("free base referenced"); g_assert_not_reached(); break; case SQLX_BASE_IDLE: case SQLX_BASE_IDLE_HOT: EXTRA_ASSERT(base->count_open == 0); EXTRA_ASSERT(base->owner == NULL); sqlx_base_move_to_list(cache, base, SQLX_BASE_USED); base->count_open ++; base->owner = g_thread_self(); *result = base->index; break; case SQLX_BASE_USED: EXTRA_ASSERT(base->count_open > 0); EXTRA_ASSERT(base->owner != NULL); if (base->owner != g_thread_self()) { // The lock is held by another thread/request GRID_DEBUG("Base [%s] in use by another thread (%X), waiting...", hashstr_str(hname), compute_thread_id(base->owner)); /* This is to avoid server thread starvation, * due to all threads waiting on the same base. */ if (cache->max_waiting > 0 && base->count_waiting >= cache->max_waiting) { err = NEWERROR(CODE_UNAVAILABLE, "database currently in use by another request, " "and %d others threads already waiting", base->count_waiting); break; } base->count_waiting++; if (g_cond_timed_wait(base->cond, cache->lock, deadline)) { // Thread was woken up before deadline GRID_DEBUG("Retrying to open [%s]", hashstr_str(hname)); base->count_waiting--; goto retry; } else { // Deadline has been reached base->count_waiting--; if (cache->open_timeout > 0) { err = NEWERROR(CODE_UNAVAILABLE, "database currently in use by another request" " (we waited %ldms)", cache->open_timeout); } else { err = NEWERROR(CODE_UNAVAILABLE, "database currently in use by another request"); } GRID_DEBUG("failed to open base: " "in use by another request (thread %X)", compute_thread_id(base->owner)); break; } } base->owner = g_thread_self(); base->count_open ++; *result = base->index; break; case SQLX_BASE_CLOSING: EXTRA_ASSERT(base->owner != NULL); // Just wait for a notification then retry if (g_cond_timed_wait(base->cond, cache->lock, deadline)) goto retry; else { err = NEWERROR(CODE_UNAVAILABLE, "Database stuck in closing state"); break; } } } if (base) { if (!err) { sqlx_base_debug(__FUNCTION__, base); EXTRA_ASSERT(base->owner == g_thread_self()); EXTRA_ASSERT(base->count_open > 0); } g_cond_signal(base->cond); } g_mutex_unlock(cache->lock); return err; }