diff options
Diffstat (limited to 'src/backend/storage/lmgr/s_lock.c')
-rw-r--r-- | src/backend/storage/lmgr/s_lock.c | 364 |
1 files changed, 364 insertions, 0 deletions
diff --git a/src/backend/storage/lmgr/s_lock.c b/src/backend/storage/lmgr/s_lock.c new file mode 100644 index 00000000000..6dc38b5955c --- /dev/null +++ b/src/backend/storage/lmgr/s_lock.c @@ -0,0 +1,364 @@ +/*------------------------------------------------------------------------- + * + * s_lock.c + * Spinlock support routines + * + * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/storage/lmgr/s_lock.c,v 1.1 2001/09/27 19:10:02 tgl Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <sys/time.h> +#include <unistd.h> + +#include "miscadmin.h" +#include "storage/s_lock.h" + + +/*---------- + * Each time we busy spin we select the next element of this array as the + * number of microseconds to wait. This accomplishes pseudo random back-off. + * + * Note that on most platforms, specified values will be rounded up to the + * next multiple of a clock tick, which is often ten milliseconds (10000). + * So, we are being way overoptimistic to assume that these different values + * are really different, other than the last. But there are a few platforms + * with better-than-usual timekeeping, and on these we will get pretty good + * pseudo-random behavior. + * + * Total time to cycle through all 20 entries will be at least 100 msec, + * more commonly (10 msec resolution) 220 msec, and on some platforms + * as much as 420 msec (when the remainder of the current tick cycle is + * ignored in deciding when to time out, as on FreeBSD and older Linuxen). + * We use the 100msec figure to figure max_spins, so actual timeouts may + * be as much as four times the nominal value, but will never be less. + *---------- + */ +#define S_NSPINCYCLE 20 + +int s_spincycle[S_NSPINCYCLE] = +{1, 10, 100, 1000, + 10000, 1000, 1000, 1000, + 10000, 1000, 1000, 10000, + 1000, 1000, 10000, 1000, + 10000, 1000, 10000, 30000 +}; + +#define AVG_SPINCYCLE 5000 /* average entry in microsec: 100ms / 20 */ + +#define DEFAULT_TIMEOUT (100*1000000) /* default timeout: 100 sec */ + + +/* + * s_lock_stuck() - complain about a stuck spinlock + */ +static void +s_lock_stuck(volatile slock_t *lock, const char *file, const int line) +{ + fprintf(stderr, + "\nFATAL: s_lock(%p) at %s:%d, stuck spinlock. Aborting.\n", + lock, file, line); + fprintf(stdout, + "\nFATAL: s_lock(%p) at %s:%d, stuck spinlock. Aborting.\n", + lock, file, line); + abort(); +} + + +/* + * s_lock_sleep() - sleep a pseudo-random amount of time, check for timeout + * + * The 'timeout' is given in microsec, or may be 0 for "infinity". Note that + * this will be a lower bound (a fairly loose lower bound, on most platforms). + * + * 'microsec' is the number of microsec to delay per loop. Normally + * 'microsec' is 0, specifying to use the next s_spincycle[] value. + * Some callers may pass a nonzero interval, specifying to use exactly that + * delay value rather than a pseudo-random delay. + */ +void +s_lock_sleep(unsigned spins, int timeout, int microsec, + volatile slock_t *lock, + const char *file, const int line) +{ + struct timeval delay; + + if (microsec > 0) + { + delay.tv_sec = microsec / 1000000; + delay.tv_usec = microsec % 1000000; + } + else + { + delay.tv_sec = 0; + delay.tv_usec = s_spincycle[spins % S_NSPINCYCLE]; + microsec = AVG_SPINCYCLE; /* use average to figure timeout */ + } + + if (timeout > 0) + { + unsigned max_spins = timeout / microsec; + + if (spins > max_spins) + s_lock_stuck(lock, file, line); + } + + (void) select(0, NULL, NULL, NULL, &delay); +} + + +/* + * s_lock(lock) - take a spinlock with backoff + */ +void +s_lock(volatile slock_t *lock, const char *file, const int line) +{ + unsigned spins = 0; + + /* + * If you are thinking of changing this code, be careful. This same + * loop logic is used in other places that call TAS() directly. + * + * While waiting for a lock, we check for cancel/die interrupts (which is + * a no-op if we are inside a critical section). The interrupt check + * can be omitted in places that know they are inside a critical + * section. Note that an interrupt must NOT be accepted after + * acquiring the lock. + */ + while (TAS(lock)) + { + s_lock_sleep(spins++, DEFAULT_TIMEOUT, 0, lock, file, line); + CHECK_FOR_INTERRUPTS(); + } +} + +/* + * Various TAS implementations that cannot live in s_lock.h as no inline + * definition exists (yet). + * In the future, get rid of tas.[cso] and fold it into this file. + */ + + +#if defined(__GNUC__) +/************************************************************************* + * All the gcc flavors that are not inlined + */ + + +#if defined(__m68k__) +static void +tas_dummy() /* really means: extern int tas(slock_t + * **lock); */ +{ + __asm__ __volatile__( + "\ +.global _tas \n\ +_tas: \n\ + movel sp@(0x4),a0 \n\ + tas a0@ \n\ + beq _success \n\ + moveq #-128,d0 \n\ + rts \n\ +_success: \n\ + moveq #0,d0 \n\ + rts \n\ +"); +} + +#endif /* __m68k__ */ + +#if defined(__APPLE__) && defined(__ppc__) +/* used in darwin. */ +/* We key off __APPLE__ here because this function differs from + * the LinuxPPC implementation only in compiler syntax. + */ +static void +tas_dummy() +{ + __asm__ __volatile__( + "\ + .globl tas \n\ + .globl _tas \n\ +_tas: \n\ +tas: \n\ + lwarx r5,0,r3 \n\ + cmpwi r5,0 \n\ + bne fail \n\ + addi r5,r5,1 \n\ + stwcx. r5,0,r3 \n\ + beq success \n\ +fail: li r3,1 \n\ + blr \n\ +success: \n\ + li r3,0 \n\ + blr \n\ +"); +} + +#endif /* __APPLE__ && __ppc__ */ + +#if defined(__powerpc__) +/* Note: need a nice gcc constrained asm version so it can be inlined */ +static void +tas_dummy() +{ + __asm__ __volatile__( + "\ +.global tas \n\ +tas: \n\ + lwarx 5,0,3 \n\ + cmpwi 5,0 \n\ + bne fail \n\ + addi 5,5,1 \n\ + stwcx. 5,0,3 \n\ + beq success \n\ +fail: li 3,1 \n\ + blr \n\ +success: \n\ + li 3,0 \n\ + blr \n\ +"); +} + +#endif /* __powerpc__ */ + +#if defined(__mips__) && !defined(__sgi) +static void +tas_dummy() +{ + __asm__ __volatile__( + "\ +.global tas \n\ +tas: \n\ + .frame $sp, 0, $31 \n\ + ll $14, 0($4) \n\ + or $15, $14, 1 \n\ + sc $15, 0($4) \n\ + beq $15, 0, fail\n\ + bne $14, 0, fail\n\ + li $2, 0 \n\ + .livereg 0x2000FF0E,0x00000FFF \n\ + j $31 \n\ +fail: \n\ + li $2, 1 \n\ + j $31 \n\ +"); +} + +#endif /* __mips__ && !__sgi */ + +#else /* not __GNUC__ */ +/*************************************************************************** + * All non gcc + */ + + + +#if defined(sun3) +static void +tas_dummy() /* really means: extern int tas(slock_t + * *lock); */ +{ + asm("LLA0:"); + asm(" .data"); + asm(" .text"); + asm("|#PROC# 04"); + asm(" .globl _tas"); + asm("_tas:"); + asm("|#PROLOGUE# 1"); + asm(" movel sp@(0x4),a0"); + asm(" tas a0@"); + asm(" beq LLA1"); + asm(" moveq #-128,d0"); + asm(" rts"); + asm("LLA1:"); + asm(" moveq #0,d0"); + asm(" rts"); + asm(" .data"); +} + +#endif /* sun3 */ + + + +#if defined(NEED_SPARC_TAS_ASM) +/* + * sparc machines not using gcc + */ +static void +tas_dummy() /* really means: extern int tas(slock_t + * *lock); */ +{ + asm(".seg \"data\""); + asm(".seg \"text\""); + asm("_tas:"); + + /* + * Sparc atomic test and set (sparc calls it "atomic load-store") + */ + asm("ldstub [%r8], %r8"); + asm("retl"); + asm("nop"); +} + +#endif /* NEED_SPARC_TAS_ASM */ + + + + +#if defined(NEED_I386_TAS_ASM) +/* non gcc i386 based things */ +#endif /* NEED_I386_TAS_ASM */ + + + +#endif /* not __GNUC__ */ + + + + +/*****************************************************************************/ +#if defined(S_LOCK_TEST) + +/* + * test program for verifying a port. + */ + +volatile slock_t test_lock; + +void +main() +{ + S_INIT_LOCK(&test_lock); + + if (!S_LOCK_FREE(&test_lock)) + { + printf("S_LOCK_TEST: failed, lock not initialized.\n"); + exit(1); + } + + S_LOCK(&test_lock); + + if (S_LOCK_FREE(&test_lock)) + { + printf("S_LOCK_TEST: failed, lock not locked\n"); + exit(2); + } + + printf("S_LOCK_TEST: this will hang for a few minutes and then abort\n"); + printf(" with a 'stuck spinlock' message if S_LOCK()\n"); + printf(" and TAS() are working.\n"); + s_lock(&test_lock, __FILE__, __LINE__); + + printf("S_LOCK_TEST: failed, lock not locked~\n"); + exit(3); + +} + +#endif /* S_LOCK_TEST */ |