Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 93fd6de

Browse files
committed
Fix fallback implementation of pg_atomic_write_u32().
I somehow had assumed that in the spinlock (in turn possibly using semaphores) based fallback atomics implementation 32 bit writes could be done without a lock. As far as the write goes that's correct, since postgres supports only platforms with single-copy atomicity for aligned 32bit writes. But writing without holding the spinlock breaks read-modify-write operations like pg_atomic_compare_exchange_u32(), since they'll potentially "miss" a concurrent write, which can't happen in actual hardware implementations. In 9.6+ when using the fallback atomics implementation this could lead to buffer header locks not being properly marked as released, and potentially some related state corruption. I don't see a related danger in 9.5 (earliest release with the API), because pg_atomic_write_u32() wasn't used in a concurrent manner there. The state variable of local buffers, before this change, were manipulated using pg_atomic_write_u32(), to avoid unnecessary synchronization overhead. As that'd not be the case anymore, introduce and use pg_atomic_unlocked_write_u32(), which does not correctly interact with RMW operations. This bug only caused issues when postgres is compiled on platforms without atomics support (i.e. no common new platform), or when compiled with --disable-atomics, which explains why this wasn't noticed in testing. Reported-By: Tom Lane Discussion: <14947.1475690465@sss.pgh.pa.us> Backpatch: 9.5-, where the atomic operations API was introduced.
1 parent 00d7e2f commit 93fd6de

File tree

4 files changed

+48
-2
lines changed

4 files changed

+48
-2
lines changed

src/backend/port/atomics.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,19 @@ pg_atomic_init_u32_impl(volatile pg_atomic_uint32 *ptr, uint32 val_)
112112
ptr->value = val_;
113113
}
114114

115+
void
116+
pg_atomic_write_u32_impl(volatile pg_atomic_uint32 *ptr, uint32 val)
117+
{
118+
/*
119+
* One might think that an unlocked write doesn't need to acquire the
120+
* spinlock, but one would be wrong. Even an unlocked write has to cause a
121+
* concurrent pg_atomic_compare_exchange_u32() (et al) to fail.
122+
*/
123+
SpinLockAcquire((slock_t *) &ptr->sema);
124+
ptr->value = val;
125+
SpinLockRelease((slock_t *) &ptr->sema);
126+
}
127+
115128
bool
116129
pg_atomic_compare_exchange_u32_impl(volatile pg_atomic_uint32 *ptr,
117130
uint32 *expected, uint32 newval)

src/include/port/atomics.h

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,10 +297,12 @@ pg_atomic_read_u32(volatile pg_atomic_uint32 *ptr)
297297
}
298298

299299
/*
300-
* pg_atomic_write_u32 - unlocked write to atomic variable.
300+
* pg_atomic_write_u32 - write to atomic variable.
301301
*
302302
* The write is guaranteed to succeed as a whole, i.e. it's not possible to
303-
* observe a partial write for any reader.
303+
* observe a partial write for any reader. Note that this correctly interacts
304+
* with pg_atomic_compare_exchange_u32, in contrast to
305+
* pg_atomic_unlocked_write_u32().
304306
*
305307
* No barrier semantics.
306308
*/
@@ -312,6 +314,25 @@ pg_atomic_write_u32(volatile pg_atomic_uint32 *ptr, uint32 val)
312314
pg_atomic_write_u32_impl(ptr, val);
313315
}
314316

317+
/*
318+
* pg_atomic_unlocked_write_u32 - unlocked write to atomic variable.
319+
*
320+
* The write is guaranteed to succeed as a whole, i.e. it's not possible to
321+
* observe a partial write for any reader. But note that writing this way is
322+
* not guaranteed to correctly interact with read-modify-write operations like
323+
* pg_atomic_compare_exchange_u32. This should only be used in cases where
324+
* minor performance regressions due to atomics emulation are unacceptable.
325+
*
326+
* No barrier semantics.
327+
*/
328+
static inline void
329+
pg_atomic_unlocked_write_u32(volatile pg_atomic_uint32 *ptr, uint32 val)
330+
{
331+
AssertPointerAlignment(ptr, 4);
332+
333+
pg_atomic_unlocked_write_u32_impl(ptr, val);
334+
}
335+
315336
/*
316337
* pg_atomic_exchange_u32 - exchange newval with current value
317338
*

src/include/port/atomics/fallback.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ pg_atomic_unlocked_test_flag_impl(volatile pg_atomic_flag *ptr)
135135
#define PG_HAVE_ATOMIC_INIT_U32
136136
extern void pg_atomic_init_u32_impl(volatile pg_atomic_uint32 *ptr, uint32 val_);
137137

138+
#define PG_HAVE_ATOMIC_WRITE_U32
139+
extern void pg_atomic_write_u32_impl(volatile pg_atomic_uint32 *ptr, uint32 val);
140+
138141
#define PG_HAVE_ATOMIC_COMPARE_EXCHANGE_U32
139142
extern bool pg_atomic_compare_exchange_u32_impl(volatile pg_atomic_uint32 *ptr,
140143
uint32 *expected, uint32 newval);

src/include/port/atomics/generic.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,15 @@ pg_atomic_write_u32_impl(volatile pg_atomic_uint32 *ptr, uint32 val)
6060
}
6161
#endif
6262

63+
#ifndef PG_HAVE_ATOMIC_UNLOCKED_WRITE_U32
64+
#define PG_HAVE_ATOMIC_UNLOCKED_WRITE_U32
65+
static inline void
66+
pg_atomic_unlocked_write_u32_impl(volatile pg_atomic_uint32 *ptr, uint32 val)
67+
{
68+
ptr->value = val;
69+
}
70+
#endif
71+
6372
/*
6473
* provide fallback for test_and_set using atomic_exchange if available
6574
*/

0 commit comments

Comments
 (0)