Beispiel #1
0
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;
}
Beispiel #2
0
void
sqlx_cache_debug(sqlx_cache_t *cache)
{
	EXTRA_ASSERT(cache != NULL);

	if (!GRID_DEBUG_ENABLED())
		return;

	GRID_DEBUG("--- REPO %p -----------------", (void*)cache);
	GRID_DEBUG(" > used     [%d, %d]",
			cache->beacon_used.first, cache->beacon_used.last);
	GRID_DEBUG(" > idle     [%d, %d]",
			cache->beacon_idle.first, cache->beacon_idle.last);
	GRID_DEBUG(" > idle_hot [%d, %d]",
			cache->beacon_idle_hot.first, cache->beacon_idle_hot.last);
	GRID_DEBUG(" > free     [%d, %d]",
			cache->beacon_free.first, cache->beacon_free.last);

	/* Dump all the bases */
	for (guint bd=0; bd < cache->bases_count ;bd++)
		sqlx_base_debug(__FUNCTION__, GET(cache,bd));

	/* Now dump all te references in the hashtable */
	gboolean runner(gpointer k, gpointer v, gpointer u) {
		(void) u;
		GRID_DEBUG("REF %d <- %s", GPOINTER_TO_INT(v), hashstr_str(k));
		return FALSE;
	}
Beispiel #3
0
/**
 * 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);
}
Beispiel #4
0
void
sqlx_cache_clean(sqlx_cache_t *cache)
{
	GRID_DEBUG("%s(%p) *** CLEANUP ***", __FUNCTION__, (void*)cache);
	if (!cache)
		return;

	if (cache->bases) {
		for (guint bd=0; bd < cache->bases_count ;bd++) {
			sqlx_base_t *base = cache->bases + bd;

			switch (base->status) {
				case SQLX_BASE_FREE:
					EXTRA_ASSERT(base->name == NULL);
					break;
				case SQLX_BASE_IDLE:
				case SQLX_BASE_IDLE_HOT:
				case SQLX_BASE_USED:
					sqlx_base_debug(__FUNCTION__, base);
					break;
				case SQLX_BASE_CLOSING:
					GRID_ERROR("Base being closed while the cache is being cleaned");
					break;
			}

			g_free0 (base->name);
			base->name = NULL;
		}
		g_free(cache->bases);
	}

	g_mutex_clear(&cache->lock);
	if (cache->cond_array) {
		for (guint i=0; i<cache->cond_count ;i++)
			g_cond_clear(cache->cond_array + i);
		g_free(cache->cond_array);
	}
	if (cache->bases_by_name)
		g_tree_destroy(cache->bases_by_name);

	g_free(cache);
}
Beispiel #5
0
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;
}
Beispiel #6
0
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;
}
Beispiel #7
0
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;
}
Beispiel #8
0
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;
}