view src/lock.c @ 183:c912e8288164 noffle

[svn] * src/client.c: Only bail out of fetching multiple articles if the connection fails. If we do get a status from the upstream server note it and see what is reported for the next article. Otherwise failure to retrieve one article will cause all successive article fetches to fail even through they would succeed if tried. * src/lock.c: Fix assert in lazy locking. If another noffle signalled us to release the lock at the next close, and then repeats the signal so that it arrives during LOCK_closeDatabases, the signal handler was trying to close the databases again.
author bears
date Tue, 15 May 2001 13:07:53 +0100
parents 7ba337dafb2c
children fed1334d766b
line wrap: on
line source

/*
  lock.c

  $Id: lock.c 292 2001-05-15 12:07:53Z bears $
*/

#if HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include "lock.h"
#include <errno.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>

#if TIME_WITH_SYS_TIME
#include <sys/time.h>
#include <time.h>
#else
#if HAVE_SYS_TIME_H
#include <sys/time.h>
#else
#include <time.h>
#endif
#endif

#include <unistd.h>
#include "configfile.h"
#include "content.h"
#include "log.h"
#include "database.h"
#include "group.h"
#include "request.h"
#include "portable.h"
#include "server.h"
#include "util.h"

struct Lock
{
    const char *name;
    int lockFd;
    Str lockFile;
    Bool doLazyLocking;
    volatile Bool lazyClose;
    volatile Bool lazyLockBusy;
};

static struct Lock globalLock = { "global", -1, "", TRUE, FALSE, FALSE };
static struct Lock fetchLock = { "fetch", -1, "", FALSE, FALSE, FALSE };

static sig_t oldHandler = NULL;

/* Block/unblock SIGUSR1. */
static Bool
blockSignal( Bool block )
{
    sigset_t sigs;

    sigemptyset(&sigs);
    sigaddset(&sigs, SIGUSR1);
    
    for(;;)
    {
	if ( sigprocmask( block ? SIG_BLOCK : SIG_UNBLOCK, &sigs, NULL ) != 0 )
	{
	    if ( errno != EINTR )
	    {
		Log_err( "Can't block/unblock signal" );
		return FALSE;
	    }
	}
	else
	    return TRUE;
    }
    /* NOTREACHED */
}

/* Check the global lock held. */
static Bool
gotLock( struct Lock *lock )
{
    return ( lock->lockFd != -1 );    
}

static void
lockWaitAlarm( int sig )
{
    UNUSED( sig );
    
    return;
}

static Bool
waitLock( struct Lock *lock, enum LockRequestWait wait )
{
    int fd;
    struct flock l;

    ASSERT( ! gotLock( lock ) );
    Log_dbg( "Waiting for lock %s ...", lock->name );
    if ( lock->lockFile[ 0 ] == '\0' )
	snprintf( lock->lockFile, MAXCHAR, "%s/lock/%s",
		  Cfg_spoolDir(), lock->name );
    if ( ( fd = open( lock->lockFile, O_WRONLY | O_CREAT, 0644 ) ) < 0 )
    {
        Log_err( "Cannot open %s (%s)", lock->lockFile, strerror( errno ) );
        return FALSE;
    }
    l.l_type = F_WRLCK;
    l.l_start = 0;
    l.l_whence = SEEK_SET;
    l.l_len = 0;
    if ( wait == LOCK_WAIT )
    {
	sig_t oldAlarmHandler;
	unsigned oldAlarm;

	oldAlarmHandler = Utl_installSignalHandler( SIGALRM, lockWaitAlarm );
	oldAlarm = alarm( 1 );
	for(;;)
	{
	    alarm( 1 );
	    if ( fcntl( fd, F_SETLKW, &l ) < 0 )
		if ( errno != EINTR )
		{
		    Utl_installSignalHandler( SIGALRM, oldAlarmHandler );
		    alarm( oldAlarm );
		    Log_err( "Cannot lock %s: %s", lock->lockFile,
			     strerror( errno ) );
		    close( lock->lockFd );
		    return FALSE;
		}
		else
		{
		    /* Send SIGUSR1 to the process holding the lock. */
		    if ( fcntl( fd, F_GETLK, &l) == 0 && l.l_type != F_UNLCK )
			if ( kill( l.l_pid, SIGUSR1 ) < 0 )
			    Log_err( "Can't signal process %d: %s", l.l_pid,
				     strerror( errno ) );
		}
	    else
		break;
	}
	Utl_installSignalHandler( SIGALRM, oldAlarmHandler );
	alarm( oldAlarm );
    }
    else
    {
	if ( fcntl( fd, F_SETLK, &l ) < 0 )
	{
	    close( lock->lockFd );
	    return FALSE;
	}
    }
	
    lock->lockFd = fd;
    Log_dbg( "Lock successful" );
    return TRUE;
}

static void
releaseLock( struct Lock *lock )
{
    struct flock l;

    ASSERT( gotLock( lock ) );    
    l.l_type = F_UNLCK;
    l.l_start = 0;
    l.l_whence = SEEK_SET;
    l.l_len = 0;
    if ( fcntl( lock->lockFd, F_SETLK, &l ) < 0 )
        Log_err( "Cannot release %s: %s", lock->lockFile,
                 strerror( errno ) );
    close( lock->lockFd );
    lock->lockFd = -1;
    Log_dbg( "Releasing lock" );
}

static Bool
openDatabases( void )
{
    globalLock.lazyClose = FALSE;
    if ( ! waitLock( &globalLock, LOCK_WAIT ) )
    {
	Log_err( "Could not get write lock" );
	return FALSE;
    }
    if ( ! Db_open() )
    {
	Log_err( "Could not open database" );
	releaseLock( &globalLock );
	return FALSE;
    }
    if ( ! Grp_open() )
    {
	Log_err( "Could not open groupinfo" );
	Db_close();
	releaseLock( &globalLock );
	return FALSE;
    }
    if ( ! Req_open() )
    {
	Log_err( "Could not initialize request database" );
	Grp_close();
	Db_close();
	releaseLock( &globalLock );
	return FALSE;
    }

    globalLock.lazyClose = globalLock.doLazyLocking;
    globalLock.lazyLockBusy = TRUE;
    return TRUE;
}

static void
closeDatabases( void )
{
    Grp_close();
    Db_close();
    Req_close();
    Server_flushCache();
    releaseLock( &globalLock );
    globalLock.lazyLockBusy = FALSE;
    globalLock.lazyClose = FALSE;
}

static void
lockSignal( int sig )
{
    UNUSED( sig );

    /*
     * If we hold the lock right now, flag it to be released next close.
     * Otherwise, if we are holding it lazily, release it.
     */
    if ( globalLock.lazyLockBusy )
	globalLock.lazyClose = FALSE;
    else if ( gotLock( &globalLock ) )
	closeDatabases();
    return;
}

/* Open all databases and set global lock. */
Bool
Lock_openDatabases( void )
{
    Bool res;

    /* First time - need to initialise signal handler? */
    if ( oldHandler == NULL )
	oldHandler = Utl_installSignalHandler( SIGUSR1, lockSignal );

    if ( ! blockSignal( TRUE ) )
	return FALSE;
    
    if ( ! globalLock.lazyClose )
	res = openDatabases();
    else
	res = TRUE;
    
    globalLock.lazyLockBusy = res;

    if ( ! blockSignal( FALSE ) )
	return FALSE;

    return res;
}

/* Close all databases and release global lock. */
void
Lock_closeDatabases( void )
{
    blockSignal( TRUE );
    
    if ( ! globalLock.lazyClose )
	closeDatabases();
    else
	globalLock.lazyLockBusy = FALSE;
    
    blockSignal( FALSE );
}

/* Sync all databases to disc. Maintain global lock status. */
void
Lock_syncDatabases( void )
{
    Bool wasOpen;
    
    if ( gotLock( &globalLock ) )
    {
	blockSignal( TRUE );
    
	wasOpen = globalLock.lazyLockBusy;
	closeDatabases();
	if ( wasOpen )
	    openDatabases();
	
	blockSignal( FALSE );
    }
}

/* Check the global lock held. */
Bool
Lock_gotLock( void )
{
    return globalLock.lazyLockBusy;
}

/* Get fetch lock. */
Bool
Lock_getFetchLock( enum LockRequestWait wait )
{
    return waitLock( &fetchLock, wait );
}

/* Release fetch lock. */
void
Lock_releaseFetchLock( void )
{
    releaseLock( &fetchLock );
}

/* Check the fetch lock held. */
Bool
Lock_fetchLock( void )
{
    return gotLock( &fetchLock );
}