Skip to content

Commit a63c71c

Browse files
fix: race condition in auto-scaler during Caddy config reload
scaleWorkerThread and scaleRegularThread read the global mainThread from goroutines started by initAutoScaling. During a Caddy reload, FrankenPHPApp.Start() calls Shutdown() then Init(), which overwrites mainThread. The scaler goroutines could read the stale global concurrently with the write. Fix: capture mainThread.done and mainThread.state into package-level variables at initAutoScaling time. Scaler goroutines use the captured references instead of the global, eliminating the race.
1 parent 006f37f commit a63c71c

1 file changed

Lines changed: 10 additions & 4 deletions

File tree

scaling.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ var (
3232
scaleChan chan *frankenPHPContext
3333
autoScaledThreads = []*phpThread{}
3434
scalingMu = new(sync.RWMutex)
35+
// scalingDone and scalingState are captured at initAutoScaling time
36+
// to avoid reading the global mainThread from scaler goroutines
37+
scalingDone chan struct{}
38+
scalingState *state.ThreadState
3539
)
3640

3741
func initAutoScaling(mainThread *phpMainThread) {
@@ -44,6 +48,8 @@ func initAutoScaling(mainThread *phpMainThread) {
4448
scaleChan = make(chan *frankenPHPContext)
4549
maxScaledThreads := mainThread.maxThreads - mainThread.numThreads
4650
autoScaledThreads = make([]*phpThread, 0, maxScaledThreads)
51+
scalingDone = mainThread.done
52+
scalingState = mainThread.state
4753
scalingMu.Unlock()
4854

4955
go startUpscalingThreads(maxScaledThreads, scaleChan, mainThread.done)
@@ -83,14 +89,14 @@ func addWorkerThread(worker *worker) (*phpThread, error) {
8389
// scaleWorkerThread adds a worker PHP thread automatically
8490
func scaleWorkerThread(worker *worker) {
8591
// probe CPU usage before acquiring the lock (avoids holding lock during 120ms sleep)
86-
if !cpu.ProbeCPUs(cpuProbeTime, maxCpuUsageForScaling, mainThread.done) {
92+
if !cpu.ProbeCPUs(cpuProbeTime, maxCpuUsageForScaling, scalingDone) {
8793
return
8894
}
8995

9096
scalingMu.Lock()
9197
defer scalingMu.Unlock()
9298

93-
if !mainThread.state.Is(state.Ready) {
99+
if !scalingState.Is(state.Ready) {
94100
return
95101
}
96102

@@ -113,14 +119,14 @@ func scaleWorkerThread(worker *worker) {
113119
// scaleRegularThread adds a regular PHP thread automatically
114120
func scaleRegularThread() {
115121
// probe CPU usage before acquiring the lock (avoids holding lock during 120ms sleep)
116-
if !cpu.ProbeCPUs(cpuProbeTime, maxCpuUsageForScaling, mainThread.done) {
122+
if !cpu.ProbeCPUs(cpuProbeTime, maxCpuUsageForScaling, scalingDone) {
117123
return
118124
}
119125

120126
scalingMu.Lock()
121127
defer scalingMu.Unlock()
122128

123-
if !mainThread.state.Is(state.Ready) {
129+
if !scalingState.Is(state.Ready) {
124130
return
125131
}
126132

0 commit comments

Comments
 (0)