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; 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; 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; }