Skip to content

Commit 3490ac0

Browse files
committed
Fix GH-13437: FPM: ERROR: scoreboard: failed to lock (already locked)
This changes locking for scoreboard to reduce contention between readers and adds retries for acquiring scoreboard for read. Closes GH-15805
1 parent e7af08d commit 3490ac0

File tree

4 files changed

+101
-8
lines changed

4 files changed

+101
-8
lines changed

NEWS

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ PHP NEWS
1111
- FFI:
1212
. Fixed bug #79075 (FFI header parser chokes on comments). (nielsdos)
1313

14+
- FPM:
15+
. Fixed bug GH-13437 (FPM: ERROR: scoreboard: failed to lock (already
16+
locked)). (Jakub Zelenka)
17+
1418
- Iconv:
1519
. Fixed bug GH-17047 (UAF on iconv filter failure). (nielsdos)
1620

sapi/fpm/fpm/fpm_atomic.h

+19
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,25 @@ static inline int fpm_spinlock(atomic_t *lock, int try_once) /* {{{ */
156156
}
157157
/* }}} */
158158

159+
static inline int fpm_spinlock_with_max_retries(atomic_t *lock, unsigned int max_retries)
160+
{
161+
unsigned int retries = 0;
162+
163+
for (;;) {
164+
if (atomic_cmp_set(lock, 0, 1)) {
165+
return 1;
166+
}
167+
168+
sched_yield();
169+
170+
if (++retries > max_retries) {
171+
return 0;
172+
}
173+
}
174+
175+
return 1;
176+
}
177+
159178
#define fpm_unlock(lock) lock = 0
160179

161180
#endif

sapi/fpm/fpm/fpm_scoreboard.c

+74-8
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,22 @@ int fpm_scoreboard_init_main(void)
7373
return 0;
7474
}
7575

76+
static inline void fpm_scoreboard_readers_decrement(struct fpm_scoreboard_s *scoreboard)
77+
{
78+
fpm_spinlock(&scoreboard->lock, 1);
79+
if (scoreboard->reader_count > 0) {
80+
scoreboard->reader_count -= 1;
81+
}
82+
#ifdef PHP_FPM_ZLOG_TRACE
83+
unsigned int current_reader_count = scoreboard->reader_count;
84+
#endif
85+
fpm_unlock(scoreboard->lock);
86+
#ifdef PHP_FPM_ZLOG_TRACE
87+
/* this is useful for debugging but currently needs to be hidden as external logger would always log it */
88+
zlog(ZLOG_DEBUG, "scoreboard: for proc %d reader decremented to value %u", getpid(), current_reader_count);
89+
#endif
90+
}
91+
7692
static struct fpm_scoreboard_s *fpm_scoreboard_get_for_update(struct fpm_scoreboard_s *scoreboard) /* {{{ */
7793
{
7894
if (!scoreboard) {
@@ -93,7 +109,33 @@ void fpm_scoreboard_update_begin(struct fpm_scoreboard_s *scoreboard) /* {{{ */
93109
return;
94110
}
95111

96-
fpm_spinlock(&scoreboard->lock, 0);
112+
int retries = 0;
113+
while (1) {
114+
fpm_spinlock(&scoreboard->lock, 1);
115+
if (scoreboard->reader_count == 0) {
116+
if (!fpm_spinlock_with_max_retries(&scoreboard->writer_active, FPM_SCOREBOARD_SPINLOCK_MAX_RETRIES)) {
117+
/* in this case the writer might have crashed so just warn and continue as the lock was acquired */
118+
zlog(ZLOG_WARNING, "scoreboard: writer %d waited too long for another writer to release lock.", getpid());
119+
}
120+
#ifdef PHP_FPM_ZLOG_TRACE
121+
else {
122+
zlog(ZLOG_DEBUG, "scoreboard: writer lock acquired by writer %d", getpid());
123+
}
124+
#endif
125+
fpm_unlock(scoreboard->lock);
126+
break;
127+
}
128+
fpm_unlock(scoreboard->lock);
129+
130+
if (++retries > FPM_SCOREBOARD_SPINLOCK_MAX_RETRIES) {
131+
/* decrement reader count by 1 (assuming a killed or crashed reader) */
132+
fpm_scoreboard_readers_decrement(scoreboard);
133+
zlog(ZLOG_WARNING, "scoreboard: writer detected a potential crashed reader, decrementing reader count.");
134+
retries = 0;
135+
}
136+
137+
sched_yield();
138+
}
97139
}
98140
/* }}} */
99141

@@ -170,7 +212,10 @@ void fpm_scoreboard_update_commit(
170212
scoreboard->active_max = scoreboard->active;
171213
}
172214

173-
fpm_unlock(scoreboard->lock);
215+
fpm_unlock(scoreboard->writer_active);
216+
#ifdef PHP_FPM_ZLOG_TRACE
217+
zlog(ZLOG_DEBUG, "scoreboard: writer lock released by writer %d", getpid());
218+
#endif
174219
}
175220
/* }}} */
176221

@@ -234,16 +279,37 @@ struct fpm_scoreboard_proc_s *fpm_scoreboard_proc_get_from_child(struct fpm_chil
234279

235280
struct fpm_scoreboard_s *fpm_scoreboard_acquire(struct fpm_scoreboard_s *scoreboard, int nohang) /* {{{ */
236281
{
237-
struct fpm_scoreboard_s *s;
238-
239-
s = scoreboard ? scoreboard : fpm_scoreboard;
282+
struct fpm_scoreboard_s *s = scoreboard ? scoreboard : fpm_scoreboard;
240283
if (!s) {
241284
return NULL;
242285
}
243286

244-
if (!fpm_spinlock(&s->lock, nohang)) {
245-
return NULL;
287+
int retries = 0;
288+
while (1) {
289+
/* increment reader if no writer active */
290+
fpm_spinlock(&s->lock, 1);
291+
if (!s->writer_active) {
292+
s->reader_count += 1;
293+
#ifdef PHP_FPM_ZLOG_TRACE
294+
unsigned int current_reader_count = s->reader_count;
295+
#endif
296+
fpm_unlock(s->lock);
297+
#ifdef PHP_FPM_ZLOG_TRACE
298+
zlog(ZLOG_DEBUG, "scoreboard: for proc %d reader incremented to value %u", getpid(), current_reader_count);
299+
#endif
300+
break;
301+
}
302+
fpm_unlock(s->lock);
303+
304+
sched_yield();
305+
306+
if (++retries > FPM_SCOREBOARD_SPINLOCK_MAX_RETRIES) {
307+
zlog(ZLOG_WARNING, "scoreboard: reader waited too long for writer to release lock.");
308+
fpm_scoreboard_readers_decrement(s);
309+
return NULL;
310+
}
246311
}
312+
247313
return s;
248314
}
249315
/* }}} */
@@ -253,7 +319,7 @@ void fpm_scoreboard_release(struct fpm_scoreboard_s *scoreboard) {
253319
return;
254320
}
255321

256-
scoreboard->lock = 0;
322+
fpm_scoreboard_readers_decrement(scoreboard);
257323
}
258324

259325
struct fpm_scoreboard_s *fpm_scoreboard_copy(struct fpm_scoreboard_s *scoreboard, int copy_procs)

sapi/fpm/fpm/fpm_scoreboard.h

+4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
#define FPM_SCOREBOARD_LOCK_HANG 0
1919
#define FPM_SCOREBOARD_LOCK_NOHANG 1
2020

21+
#define FPM_SCOREBOARD_SPINLOCK_MAX_RETRIES 50000
22+
2123
struct fpm_scoreboard_proc_s {
2224
union {
2325
atomic_t lock;
@@ -52,6 +54,8 @@ struct fpm_scoreboard_s {
5254
atomic_t lock;
5355
char dummy[16];
5456
};
57+
atomic_t writer_active;
58+
unsigned int reader_count;
5559
char pool[32];
5660
int pm;
5761
time_t start_epoch;

0 commit comments

Comments
 (0)