+---------- On Sep 10, Sean Owen said:
> The problem is, under heavy load, we get into serious lock contention
> problems reading from it.

Are you speculating or have you looked at the mutex statistics?

> 99.99% of the time we are just reading, so
> ideally I'd like to use something akin to a read/write lock, however, the
> overhead of an actual rwlock will probably only make matters worse.
>
> The table is far too large (~50,000 entries)to just load up every time an
> interp is initialized.

We need to know more about your usage pattern.  Each NSV is a hash
table.  Are you using a lot of NSVs or one NSV with 50,000 keys?  Do you
access the NSV once per request, or many times per request?

> Any ideas for me? Is there any way to create a shared read-only
> datastructure that doesn't use thread interlocking? Do I need to code my
> datastructure up in C?

Yes, you'd need to do it in C.

I'd do something like this, but I haven't tested this code:

/*
 * An "atomicdata" is a global hash table. One atomicdata in the system
 * is considered "active".
 *
 * Each atomicdata has a reference count. When an atomicdata's reference
 * count is zero and it is not the active atomicdata, all resources
 * consumed by that atomicdata are freed.
 *
 * The command "dqd_atomicdata set {k1 v1 k2 v2 ...}" creates a new
 * atomicdata with reference count zero. It fills it with the key/value
 * pairs k1/v1, k2/v2, etc. Then it atomically makes that new atomicdata
 * be the active atomicdata. If there was already an active atomicdata,
 * then the command BLOCKS until the old atomicdata's reference count
 * is zero and then frees the old atomicdata's resources. Any other
 * thread that has a reference to the old atomicdata continues to access
 * the old atomicdata (blocking this thread) until it releases its
 * reference.
 *
 * The command "dqd_atomicdata get k" causes the current thread to
 * check whether it has a reference to an atomicdata. If it does not,
 * then it acquires a reference to the active atomicdata and increments
 * the active atomicdata's reference count. Once the thread is certain
 * that it has a reference to some atomicdata, it looks up "k" in the
 * atomicdata. If "k" is found, then the command returns the value
 * associated with "k". Otherwise, it returns an error. In either case,
 * the reference to the atomicdata is saved for use by future calls to
 * "dqd_atomicdata get" in the same thread.
 *
 * The command "dqd_atomicdata release" checks whether the current
 * thread has a reference to some atomicdata. If it does not, then the
 * command simply returns "0". If the thread does have a reference to an
 * atomicdata, then the command decrements the atomicdata's reference
 * count and forgets the reference.
 *
 * Each connection thread automatically performs the equivalent of
 * "dqd_atomicdata release" after completing each HTTP request. You
 * should use "dqd_atomicdata release" in threads that you create and in
 * scheduled procs.
 *
 * A thread must lock a mutex momentarily to acquire a reference to the
 * active atomicdata, and to release its reference, and to replace the
 * active atomicdata. No locking is required when a thread already has a
 * reference to an atomicdata.
 */

#ifndef USE_TCL8X
#define USE_TCL8X
#endif

#include "ns.h"

int Ns_ModuleVersion = 1;

typedef struct AtomicData {
    Tcl_HashTable table;
    int refCnt;
} AtomicData;

/*
 * Threads will access this to get the current table.  When we need
 * to modify the table, we'll construct a new one from scratch and
 * atomically swap them out.
 */
static AtomicData *global_ad;

/* These will protect access to global_ad->refCnt. */
static Ns_Mutex ad_lock;
static Ns_Cond ad_cond;

/* Hold each thread's reference to global_ad. */
static Ns_Tls ad_tls;

static AtomicData *
acquire(void)
{
    AtomicData *ad = Ns_TlsGet(&ad_tls);

    if (ad == NULL) {

        Ns_MutexLock(&ad_lock);
        ad = global_ad;
        ad->refCnt++;
        Ns_MutexUnlock(&ad_lock);

        Ns_TlsSet(&ad_tls, ad);
    }

    return ad;
}

static int
release(void)
{
    AtomicData *ad = Ns_TlsGet(&ad_tls);

    if (ad == NULL) return 0;

    Ns_TlsSet(&ad_tls, NULL);
    Ns_MutexLock(&ad_lock);
    if (--ad->refCnt == 0)
        Ns_CondBroadcast(&ad_cond);
    Ns_MutexUnlock(&ad_lock);

    return 1;
}

static void
setGlobal(Tcl_HashTable *newTable)
{
    AtomicData *ad = (AtomicData *) ns_malloc(sizeof *ad);
    AtomicData *old_ad;
    Tcl_HashEntry *he;
    Tcl_HashSearch search;

    ad->table = *newTable;
    ad->refCnt = 0;

    Ns_MutexLock(&ad_lock);
    old_ad = global_ad;
    global_ad = ad;

    while (old_ad->refCnt != 0) {
        Ns_CondWait(&ad_cond, &ad_lock);
    }

    Ns_MutexUnlock(&ad_lock);

    he = Tcl_FirstHashEntry(&old_ad->table, &search);
    while (he != NULL) {
        ns_free((void *) Tcl_GetHashValue(he));
        he = Tcl_NextHashEntry(&search);
    }

    ns_free((void *) old_ad);
}

static int
setCommand(Tcl_Interp *interp, Tcl_Obj *listObj)
{
    int elc;
    Tcl_Obj **elv;
    Tcl_HashTable ht;

    if (Tcl_ListObjGetElements(interp, listObj, &elc, &elv) == TCL_ERROR)
        return TCL_ERROR;

    if (elc & 1) {
        Tcl_SetResult(interp,
            "list must contain an even number of elements", TCL_STATIC);
        return TCL_ERROR;
    }

    Tcl_InitHashTable(&ht, TCL_STRING_KEYS);

    while (elc > 0) {
        int isNew;

        Tcl_HashEntry *he = Tcl_CreateHashEntry(&ht,
            Tcl_GetString(elv[0]), &isNew);

        if (!isNew) ns_free((void *) Tcl_GetHashValue(he));

        Tcl_SetHashValue(he, ns_strdup(Tcl_GetString(elv[1])));

        elc -= 2;
        elv += 2;
    }

    setGlobal(&ht);

    return TCL_OK;
}

static int
getCommand(Tcl_Interp *interp, Tcl_Obj *keyObj)
{
    AtomicData *ad;
    Tcl_HashEntry *he;
    char *key;

    ad = acquire();
    if (ad == NULL) {
        Tcl_SetResult(interp, "no active atomicdata", TCL_STATIC);
        return TCL_ERROR;
    }

    key = Tcl_GetString(keyObj);
    he = Tcl_FindHashEntry(&ad->table, key);
    if (he == NULL) {
        Tcl_AppendResult(interp, "atomicdata does not contain key \"",
            key, "\"", NULL);
        return TCL_ERROR;
    }

    Tcl_SetResult(interp, (char *) Tcl_GetHashValue(he), TCL_VOLATILE);
    return TCL_OK;
}

static int
releaseCommand(Tcl_Interp *interp)
{
    Tcl_SetResult(interp, release() ? "1" : "0", TCL_STATIC);
    return TCL_OK;
}

static int
tclCommand(ClientData dummy, Tcl_Interp *interp,
    int objc, Tcl_Obj * CONST objv[])
{
    if (objc == 2) {
        if (STREQ(Tcl_GetString(objv[1]), "release"))
            return releaseCommand(interp);
    }

    else if (objc == 3) {
        if (STREQ(Tcl_GetString(objv[1]), "get"))
            return getCommand(interp, objv[2]);
        else if (STREQ(Tcl_GetString(objv[1]), "set"))
            return setCommand(interp, objv[2]);
    }

    Tcl_SetResult(interp, "syntax error", TCL_STATIC);
    return TCL_ERROR;
}

static int
interpInit(Tcl_Interp *interp, void *context)
{
    Tcl_CreateObjCommand(interp, "dqd_atomicdata", tclCommand, NULL, NULL);
    return NS_OK;
}

int
Ns_ModuleInit(char *server, char *module)
{
    Ns_TlsAlloc(&ad_tls, NULL);
    Ns_MutexSetName2(&ad_lock, "ad_lock", "");

    return NS_OK;
}

Reply via email to