container.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2000-2001 by Sun Microsystems, Inc.
* All rights reserved.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <synch.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <dhcpmsg.h>
#include <unistd.h>
#include <dhcp_svc_private.h>
#include "container.h"
/*
* Container locking code -- warning: serious pain ahead.
*
* This code synchronizes access to a given container across multiple
* threads in this (dsvclockd) process, and optionally synchronizes across
* multiple instances of dsvclockd running on different hosts. The
* synchronization allows multiple readers or a single writer at one time.
*
* Since by definition there is at most one dsvclockd running per host and
* all requests by all threads in all processes running on that host funnel
* into it, this code effectively synchronizes access to a given container
* across all threads in all processes running on a given host. This means
* that the optional synchronization across multiple instances of dsvclockd
* on different hosts provides true cross-host synchronization for all
* threads in all processes on all cooperating machines (though all hosts
* must have write access to a common directory).
*
* The container synchronization here should be viewed as a two step
* process, where the first step is optional:
*
* 1. Synchronize access across the set of cooperating dsvclockd's
* on multiple hosts. This is known as acquiring the host lock.
*
* 2. Synchronize access across the set of threads running inside
* this dsvclockd process. This is known as acquiring the
* intra-process lock.
*
* In order to implement the first (host lock) step, we use fcntl()-based
* file locking on a file inside an NFS-shared directory and rely on NFS to
* do our synchronization for us. Note that this can only be used to
* implement the first step since fcntl()-based locks are process locks,
* and the effects of using these locks with multiple threads are not
* defined. Furthermore, note that this means it requires some fancy
* footwork to ensure that only one thread in a given dsvclockd process
* tries to acquire the fcntl() lock for that process.
*
* In order to implement the second step, we use custom-made reader-writer
* locks since the stock Solaris ones don't quite have the semantics we
* need -- in particular, we need to relax the requirement that the thread
* which acquired the lock is the one releasing it.
*
* Lock ordering guidelines:
*
* For the most part, this code does not acquire more than one container
* lock at a time -- whenever feasible, please do the same. If you must
* acquire more than one lock at a time, the correct order is:
*
* 1. cn_nholds_lock
* 2. cn_lock
* 3. cn_hlock_lock
*/
static int host_unlock(dsvcd_container_t *);
static unsigned int cn_nlocks(dsvcd_container_t *);
/*
* Create a container identified by `cn_id'; returns an instance of the new
* container upon success, or NULL on failure. Note that `cn_id' is
* treated as a pathname and thus must be a unique name for the container
* across all containers, container versions, and datastores -- additionally,
* if `crosshost' is set, then the directory named by `cn_id' must be a
* directory mounted on all cooperating hosts.
*/
{
return (NULL);
return (NULL);
}
cn->cn_hlockcount = 0;
return (cn);
}
/*
* Destroy container `cn'; wait a decent amount of time for activity on the
* container to quiesce first. If the caller has not prohibited other
* threads from calling into the container yet, this may take a long time.
*/
void
{
unsigned int attempts;
unsigned int nstalelocks;
/*
* Wait for up to CN_DESTROY_WAIT seconds for all the lock holders
* to relinquish their locks. If the container has locks that seem
* to be stale, then warn the user before destroying it. The locks
* will be unlocked automatically when we exit.
*/
if (nstalelocks == 0)
break;
(void) sleep(1);
}
if (nstalelocks == 1) {
} else if (nstalelocks != 0) {
}
}
/*
* Wait (block) until a lock of type `locktype' is obtained on container
* `cn'. Returns a DSVC_* return code; if DSVC_SUCCESS is returned, then
* the lock is held upon return. Must be called with the container's
* cn_nholds_lock held on entry; returns with it unlocked.
*/
static int
{
int retval = DSVC_SUCCESS;
/*
* Chain our stack-local waititem onto the list; this keeps us from
* having to worry about allocation failures and also makes it easy
* for cn_unlock() to just pull us off the list without worrying
* about freeing the memory.
*
* Note that we can do this because by definition we are blocked in
* this function until we are signalled.
*/
} else {
}
do {
break;
}
/*
* We got woken up; pull ourselves off of the local waitlist.
*/
else
else
if (retval == DSVC_SUCCESS) {
if (locktype == DSVCD_WRLOCK)
else
}
/*
* If we just acquired a read lock and the next waiter is waiting
* for a readlock too, signal the waiter. Note that we wake each
* reader up one-by-one like this to avoid excessive contention on
* cn_nholds_lock.
*/
return (retval);
}
/*
* Lock container `cn' for reader (shared) access. If the container cannot
* be locked immediately (there is currently a writer lock held or a writer
* lock waiting for the lock), then if `nonblock' is B_TRUE, DSVC_BUSY is
* returned. Otherwise, block until the lock can be obtained. Returns a
* DSVC_* code.
*/
int
{
int retval;
/*
* The container is going away; no new lock requests.
*/
if (cn->cn_closing) {
return (DSVC_SYNCH_ERR);
}
/*
* See if we can grab the lock without having to block; only
* possible if we can acquire the host lock without blocking, if
* the lock is not currently owned by a writer and if there are no
* writers currently enqueued for accessing this lock (we know that
* if there's a waiter it must be a writer since this code doesn't
* enqueue readers until there's a writer enqueued). We enqueue
* these requests to improve fairness.
*/
return (DSVC_SUCCESS);
}
/*
* Cannot grab the lock without blocking somewhere; wait until we
* can grab the host lock, then with that lock held obtain our
* intra-process lock.
*/
if (nonblock)
return (DSVC_BUSY);
if (retval != DSVC_SUCCESS)
return (retval);
/*
* We've got the read lock; if there aren't any writers currently
* contending for our intra-process lock then succeed immediately.
* It's possible for there to be waiters but for nholds to be zero
* via the following scenario:
*
* 1. The last holder of a lock unlocks, dropping nholds to
* zero and signaling the head waiter on the waitlist.
*
* 2. The last holder drops cn_nholds_lock.
*
* 3. We acquire cn_nholds_lock before the signaled waiter
* does.
*
* Note that this case won't cause a deadlock even if we didn't
* check for it here (when the waiter finally gets cn_nholds_lock,
* it'll find that the waitlist is once again non-NULL, and signal
* the us). However, as an optimization, handle the case here.
*/
return (DSVC_SUCCESS);
}
/* cn_wait_for_lock() will drop cn_nholds_lock */
if (retval != DSVC_SUCCESS) {
(void) host_unlock(cn);
return (retval);
}
return (DSVC_SUCCESS);
}
/*
* Lock container `cn' for writer (exclusive) access. If the container
* cannot be locked immediately (there are currently readers or a writer),
* then if `nonblock' is B_TRUE, DSVC_BUSY is returned. Otherwise, block
* until the lock can be obtained. Returns a DSVC_* code.
*/
int
{
int retval;
/*
* The container is going away; no new lock requests.
*/
if (cn->cn_closing) {
return (DSVC_SYNCH_ERR);
}
/*
* See if we can grab the lock without having to block; only
* possible if there are no current writers within our process and
* that we can immediately acquire the host lock.
*/
return (DSVC_SUCCESS);
}
/*
* Cannot grab the lock without blocking somewhere; wait until we
* can grab the host lock, then with that lock held obtain our
* intra-process lock.
*/
if (nonblock)
return (DSVC_BUSY);
if (retval != DSVC_SUCCESS)
return (retval);
/*
* We've got the host lock; if there aren't any writers currently
* contending for our intra-process lock then succeed immediately.
*/
return (DSVC_SUCCESS);
}
/* cn_wait_for_lock() will drop cn_nholds_lock */
if (retval != DSVC_SUCCESS) {
(void) host_unlock(cn);
return (retval);
}
return (DSVC_SUCCESS);
}
/*
* Unlock reader or writer lock on container `cn'; returns a DSVC_* code
*/
int
{
return (DSVC_SYNCH_ERR);
}
(void) host_unlock(cn);
return (DSVC_SUCCESS);
}
/*
* The last reader or a writer just unlocked -- signal the first
* waiter. To avoid a thundering herd, we only signal the first
* waiter, even if there are multiple readers ready to go --
* instead, each reader is responsible for signaling the next
* in cn_wait_for_lock().
*/
(void) host_unlock(cn);
return (DSVC_SUCCESS);
}
/*
* Find out what kind of lock is on `cn'. Note that this is just a
* snapshot in time and without additional locks the answer may be invalid
* by the time the function returns.
*/
{
int nholds;
if (nholds == 0)
return (DSVCD_NOLOCK);
else if (nholds > 0)
return (DSVCD_RDLOCK);
else
return (DSVCD_WRLOCK);
}
/*
* Obtain a lock of type `locktype' on container `cn' such that we have
* shared or exclusive access to this container across all hosts. If
* `nonblock' is true and the lock cannot be obtained return DSVC_BUSY. If
* the lock is already held, the number of instances of the lock "checked
* out" by this host is incremented.
*/
static int
{
int fd;
int error;
if (!cn->cn_crosshost)
return (DSVC_SUCCESS);
/*
* Before we wait for a while, see if the container is going away;
* if so, fail now so the container can drain quicker..
*/
if (cn->cn_closing) {
return (DSVC_SYNCH_ERR);
}
/*
* Note that we only wait if (1) there's already a thread trying to
* grab the host lock on our host or if (2) this host currently
* holds a host shared lock and we need an exclusive lock. Note
* that we do *not* wait in the following situations:
*
* * This host holds an exclusive host lock and another
* exclusive host lock request comes in. We rely on the
* intra-process lock to do the synchronization.
*
* * This host holds an exclusive host lock and a shared host
* lock request comes in. Since this host already has
* exclusive access, we already implicitly hold the shared
* host lock as far as this host is concerned, so just rely
* on the intra-process lock to do the synchronization.
*
* These semantics make sense as long as one remembers that the
* host lock merely provides exclusive or shared access for a given
* host or set of hosts -- that is, exclusive access is exclusive
* access for that machine, not for the given request.
*/
if (nonblock) {
return (DSVC_BUSY);
}
return (DSVC_SYNCH_ERR);
}
}
/*
* Already locked; just bump the held lock count.
*/
cn->cn_hlockcount++;
return (DSVC_SUCCESS);
}
/*
* We're the thread that's going to try to acquire the host lock.
*/
/*
* Create the lock file as a hidden file in the directory named by
* cn_id. So if cn_id is /var/dhcp/SUNWfiles1_dhcptab, we want the
* giggles about the snprintf().
*/
else
basename++;
if (fd == -1) {
return (DSVC_SYNCH_ERR);
}
/*
* For some reason we couldn't acquire the lock. Reset the
* host lock state to "unlocked" and signal another thread
* (if there's one waiting) to pick up where we left off.
*/
}
/*
* Got the lock; wake up all the waiters since they can all succeed
*/
cn->cn_hlockcount++;
return (DSVC_SUCCESS);
}
/*
* Unlock a checked out instance of a shared or exclusive lock on container
* `cn'; if the number of checked out instances goes to zero, then the host
* lock is unlocked so that other hosts may compete for it.
*/
static int
{
if (!cn->cn_crosshost)
return (DSVC_SUCCESS);
/*
* Not the last unlock by this host; just decrement the
* held lock count.
*/
cn->cn_hlockcount--;
return (DSVC_SUCCESS);
}
return (DSVC_SYNCH_ERR);
}
/*
* Note that we don't unlink the lockfile for a number of reasons,
* the most blatant reason being:
*
* 1. Several hosts lock the lockfile for shared access.
* 2. One host unlocks the lockfile and unlinks it (here).
* 3. Another host comes in, goes to exclusively lock the
* lockfile, finds no lockfile, and creates a new one
* (meanwhile, the other hosts are still accessing the
* container through the unlinked lockfile).
*
* We could put in some hairy code to try to unlink lockfiles
* elsewhere (when possible), but it hardly seems worth it since
* inodes are cheap.
*/
cn->cn_hlockcount = 0;
/*
* We need to signal `cn_hlockcv' in case there are threads which
* are waiting on it to attempt flock() exclusive access (see the
* comments in host_lock() for more details about this case).
*/
return (DSVC_SUCCESS);
}
/*
* Return the number of locks currently held for container `cn'.
*/
static unsigned int
{
unsigned int nlocks;
case 0:
break;
case -1:
nlocks = 1;
break;
default:
break;
}
return (nlocks);
}