/*------------------------------------------------------------------------- * * 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/buffer/Attic/s_lock.c,v 1.36 2001/03/27 01:16:24 tgl Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" #include #include #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__) 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__ */ #else /* defined(__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 */