aboutsummaryrefslogtreecommitdiff
path: root/src/backend/storage/ipc/spin.c
blob: 479e0b276629b9b10b267bada173721789f9eccd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
/*-------------------------------------------------------------------------
 *
 * spin.c
 *	  routines for managing spin locks
 *
 * POSTGRES has two kinds of locks: semaphores (which put the
 * process to sleep) and spinlocks (which are supposed to be
 * short term locks).  Spinlocks are implemented via test-and-set (TAS)
 * instructions if possible, else via semaphores.  The semaphore method
 * is too slow to be useful :-(
 *
 * 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/ipc/Attic/spin.c,v 1.31 2001/01/24 19:43:07 momjian Exp $
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <errno.h>
#if !defined(HAS_TEST_AND_SET) && defined(HAVE_SYS_SEM_H)
#include <sys/sem.h>
#endif

#include "miscadmin.h"
#include "storage/proc.h"
#include "storage/s_lock.h"


/* Probably should move these to an appropriate header file */
extern SPINLOCK ShmemLock;
extern SPINLOCK ShmemIndexLock;
extern SPINLOCK BufMgrLock;
extern SPINLOCK LockMgrLock;
extern SPINLOCK ProcStructLock;
extern SPINLOCK SInvalLock;
extern SPINLOCK OidGenLockId;
extern SPINLOCK XidGenLockId;
extern SPINLOCK ControlFileLockId;

#ifdef STABLE_MEMORY_STORAGE
extern SPINLOCK MMCacheLock;

#endif


/*
 * Initialize identifiers for permanent spinlocks during startup
 *
 * The same identifiers are used for both TAS and semaphore implementations,
 * although in one case they are indexes into a shmem array and in the other
 * they are semaphore numbers.
 */
static void
InitSpinLockIDs(void)
{
	ShmemLock = (SPINLOCK) SHMEMLOCKID;
	ShmemIndexLock = (SPINLOCK) SHMEMINDEXLOCKID;
	BufMgrLock = (SPINLOCK) BUFMGRLOCKID;
	LockMgrLock = (SPINLOCK) LOCKMGRLOCKID;
	ProcStructLock = (SPINLOCK) PROCSTRUCTLOCKID;
	SInvalLock = (SPINLOCK) SINVALLOCKID;
	OidGenLockId = (SPINLOCK) OIDGENLOCKID;
	XidGenLockId = (SPINLOCK) XIDGENLOCKID;
	ControlFileLockId = (SPINLOCK) CNTLFILELOCKID;

#ifdef STABLE_MEMORY_STORAGE
	MMCacheLock = (SPINLOCK) MMCACHELOCKID;
#endif
}


#ifdef HAS_TEST_AND_SET

/* real spin lock implementation */

typedef struct slock
{
	slock_t		shlock;
} SLock;

#ifdef LOCK_DEBUG
bool Trace_spinlocks = false;

inline static void
PRINT_SLDEBUG(const char * where, SPINLOCK lockid, const SLock * lock)
{
    if (Trace_spinlocks)
        elog(DEBUG, "%s: id=%d", where, lockid);
}
#else  /* not LOCK_DEBUG */
#define PRINT_SLDEBUG(a,b,c)
#endif /* not LOCK_DEBUG */


static SLock *SLockArray = NULL;

#define SLOCKMEMORYSIZE		((int) MAX_SPINS * sizeof(SLock))

/*
 * SLockShmemSize --- return shared-memory space needed
 */
int
SLockShmemSize(void)
{
	return MAXALIGN(SLOCKMEMORYSIZE);
}

/*
 * CreateSpinlocks --- create and initialize spinlocks during startup
 */
void
CreateSpinlocks(PGShmemHeader *seghdr)
{
	int			id;

	/*
	 * We must allocate the space "by hand" because shmem.c isn't up yet
	 */
	SLockArray = (SLock *) (((char *) seghdr) + seghdr->freeoffset);
	seghdr->freeoffset += MAXALIGN(SLOCKMEMORYSIZE);
	Assert(seghdr->freeoffset <= seghdr->totalsize);

	/*
	 * Initialize all spinlocks to "unlocked" state
	 */
	for (id = 0; id < (int) MAX_SPINS; id++)
	{
		SLock	   *slckP = &(SLockArray[id]);

		S_INIT_LOCK(&(slckP->shlock));
	}

	/*
	 * Assign indexes for fixed spinlocks
	 */
	InitSpinLockIDs();
}

void
SpinAcquire(SPINLOCK lockid)
{
	SLock	   *slckP = &(SLockArray[lockid]);

	PRINT_SLDEBUG("SpinAcquire", lockid, slckP);
	/*
	 * Acquire the lock, then record that we have done so (for recovery
	 * in case of elog(ERROR) while holding the lock).  Note we assume
	 * here that S_LOCK will not accept cancel/die interrupts once it has
	 * acquired the lock.  However, interrupts should be accepted while
	 * waiting, if InterruptHoldoffCount is zero.
	 */
	S_LOCK(&(slckP->shlock));
	PROC_INCR_SLOCK(lockid);
	/*
	 * Lock out cancel/die interrupts until we exit the code section
	 * protected by the spinlock.  This ensures that interrupts will not
	 * interfere with manipulations of data structures in shared memory.
	 */
	HOLD_INTERRUPTS();

    PRINT_SLDEBUG("SpinAcquire/done", lockid, slckP);
}

void
SpinRelease(SPINLOCK lockid)
{
	SLock	   *slckP = &(SLockArray[lockid]);

    PRINT_SLDEBUG("SpinRelease", lockid, slckP);
	/*
	 * Check that we are actually holding the lock we are releasing. This
	 * can be done only after MyProc has been initialized.
	 */
    Assert(!MyProc || MyProc->sLocks[lockid] > 0);
	/*
	 * Record that we no longer hold the spinlock, and release it.
	 */
	PROC_DECR_SLOCK(lockid);
	S_UNLOCK(&(slckP->shlock));
	/*
	 * Exit the interrupt holdoff entered in SpinAcquire().
	 */
	RESUME_INTERRUPTS();

    PRINT_SLDEBUG("SpinRelease/done", lockid, slckP);
}

#else /* !HAS_TEST_AND_SET */

/*
 * No TAS, so spinlocks are implemented using SysV semaphores.
 *
 * We support two slightly different APIs here: SpinAcquire/SpinRelease
 * work with SPINLOCK integer indexes for the permanent spinlocks, which
 * are all assumed to live in the first spinlock semaphore set.  There
 * is also an emulation of the s_lock.h TAS-spinlock macros; for that case,
 * typedef slock_t stores the semId and sem number of the sema to use.
 * The semas needed are created by CreateSpinlocks and doled out by
 * s_init_lock_sema.
 *
 * Since many systems have a rather small SEMMSL limit on semas per set,
 * we allocate the semaphores required in sets of SPINLOCKS_PER_SET semas.
 * This value is deliberately made equal to PROC_NSEMS_PER_SET so that all
 * sema sets allocated by Postgres will be the same size; that eases the
 * semaphore-recycling logic in IpcSemaphoreCreate().
 *
 * Note that the SpinLockIds array is not in shared memory; it is filled
 * by the postmaster and then inherited through fork() by backends.  This
 * is OK because its contents do not change after shmem initialization.
 */

#define SPINLOCKS_PER_SET  PROC_NSEMS_PER_SET

static IpcSemaphoreId *SpinLockIds = NULL;

static int numSpinSets = 0;		/* number of sema sets used */
static int numSpinLocks = 0;	/* total number of semas allocated */
static int nextSpinLock = 0;	/* next free spinlock index */

static void SpinFreeAllSemaphores(void);

/*
 * SLockShmemSize --- return shared-memory space needed
 */
int
SLockShmemSize(void)
{
	return 0;
}

/*
 * CreateSpinlocks --- create and initialize spinlocks during startup
 */
void
CreateSpinlocks(PGShmemHeader *seghdr)
{
	int		i;

	if (SpinLockIds == NULL)
	{
		/*
		 * Compute number of spinlocks needed.  If this logic gets any more
		 * complicated, it should be distributed into the affected modules,
		 * similar to the way shmem space estimation is handled.
		 *
		 * For now, though, we just need the fixed spinlocks (MAX_SPINS),
		 * two spinlocks per shared disk buffer, and four spinlocks for XLOG.
		 */
		numSpinLocks = (int) MAX_SPINS + 2 * NBuffers + 4;

		/* might as well round up to a multiple of SPINLOCKS_PER_SET */
		numSpinSets = (numSpinLocks - 1) / SPINLOCKS_PER_SET + 1;
		numSpinLocks = numSpinSets * SPINLOCKS_PER_SET;

		SpinLockIds = (IpcSemaphoreId *)
			malloc(numSpinSets * sizeof(IpcSemaphoreId));
		Assert(SpinLockIds != NULL);
	}

	for (i = 0; i < numSpinSets; i++)
		SpinLockIds[i] = -1;

	/*
	 * Arrange to delete semas on exit --- set this up now so that we
	 * will clean up if allocation fails.  We use our own freeproc,
	 * rather than IpcSemaphoreCreate's removeOnExit option, because
	 * we don't want to fill up the on_shmem_exit list with a separate
	 * entry for each semaphore set.
	 */
	on_shmem_exit(SpinFreeAllSemaphores, 0);

	/* Create sema sets and set all semas to count 1 */
	for (i = 0; i < numSpinSets; i++)
	{
		SpinLockIds[i] = IpcSemaphoreCreate(SPINLOCKS_PER_SET,
											IPCProtection,
											1,
											false);
	}

	/*
	 * Assign indexes for fixed spinlocks
	 */
	Assert(MAX_SPINS <= SPINLOCKS_PER_SET);
	InitSpinLockIDs();

	/* Init counter for allocating dynamic spinlocks */
	nextSpinLock = MAX_SPINS;
}

/*
 * SpinFreeAllSemaphores -
 *	  called at shmem_exit time, ie when exiting the postmaster or
 *	  destroying shared state for a failed set of backends.
 *	  Free up all the semaphores allocated for spinlocks.
 */
static void
SpinFreeAllSemaphores(void)
{
	int			i;

	for (i = 0; i < numSpinSets; i++)
	{
		if (SpinLockIds[i] >= 0)
			IpcSemaphoreKill(SpinLockIds[i]);
	}
	free(SpinLockIds);
	SpinLockIds = NULL;
}

/*
 * SpinAcquire -- grab a fixed spinlock
 *
 * FAILS if the semaphore is corrupted.
 */
void
SpinAcquire(SPINLOCK lock)
{
	/*
	 * See the TAS() version of this routine for primary commentary.
	 *
	 * NOTE we must pass interruptOK = false to IpcSemaphoreLock, to ensure
	 * that a cancel/die interrupt cannot prevent us from recording ownership
	 * of a lock we have just acquired.
	 */
	IpcSemaphoreLock(SpinLockIds[0], lock, false);
	PROC_INCR_SLOCK(lock);
	HOLD_INTERRUPTS();
}

/*
 * SpinRelease -- release a fixed spin lock
 *
 * FAILS if the semaphore is corrupted
 */
void
SpinRelease(SPINLOCK lock)
{
	/* See the TAS() version of this routine for commentary */
#ifdef USE_ASSERT_CHECKING
	/* Check it's locked */
	int			semval;

	semval = IpcSemaphoreGetValue(SpinLockIds[0], lock);
	Assert(semval < 1);
#endif
    Assert(!MyProc || MyProc->sLocks[lockid] > 0);
	PROC_DECR_SLOCK(lock);
	IpcSemaphoreUnlock(SpinLockIds[0], lock);
	RESUME_INTERRUPTS();
}

/*
 * s_lock.h hardware-spinlock emulation
 */

void
s_init_lock_sema(volatile slock_t *lock)
{
	if (nextSpinLock >= numSpinLocks)
		elog(FATAL, "s_init_lock_sema: not enough semaphores");
	lock->semId = SpinLockIds[nextSpinLock / SPINLOCKS_PER_SET];
	lock->sem = nextSpinLock % SPINLOCKS_PER_SET;
	nextSpinLock++;
}

void
s_unlock_sema(volatile slock_t *lock)
{
	IpcSemaphoreUnlock(lock->semId, lock->sem);
}

bool
s_lock_free_sema(volatile slock_t *lock)
{
	return IpcSemaphoreGetValue(lock->semId, lock->sem) > 0;
}

int
tas_sema(volatile slock_t *lock)
{
	/* Note that TAS macros return 0 if *success* */
	return ! IpcSemaphoreTryLock(lock->semId, lock->sem);
}

#endif /* !HAS_TEST_AND_SET */