()
| 176 | } |
| 177 | |
| 178 | func drainWorkerThreads() (drainedThreads []*phpThread) { |
| 179 | var ready sync.WaitGroup |
| 180 | |
| 181 | for _, worker := range workers { |
| 182 | worker.threadMutex.RLock() |
| 183 | ready.Add(len(worker.threads)) |
| 184 | |
| 185 | for _, thread := range worker.threads { |
| 186 | if !thread.state.RequestSafeStateChange(state.Restarting) { |
| 187 | ready.Done() |
| 188 | |
| 189 | // no state change allowed == thread is shutting down |
| 190 | // we'll proceed to restart all other threads anyway |
| 191 | continue |
| 192 | } |
| 193 | |
| 194 | thread.handler.drain() |
| 195 | close(thread.drainChan) |
| 196 | drainedThreads = append(drainedThreads, thread) |
| 197 | |
| 198 | go func(thread *phpThread) { |
| 199 | thread.state.WaitFor(state.Yielding, state.ShuttingDown, state.Done) |
| 200 | ready.Done() |
| 201 | }(thread) |
| 202 | } |
| 203 | |
| 204 | worker.threadMutex.RUnlock() |
| 205 | } |
| 206 | |
| 207 | done := make(chan struct{}) |
| 208 | go func() { |
| 209 | ready.Wait() |
| 210 | close(done) |
| 211 | }() |
| 212 | |
| 213 | select { |
| 214 | case <-done: |
| 215 | case <-time.After(drainGracePeriod): |
| 216 | // Force-kill any thread still stuck in a blocking syscall, then |
| 217 | // keep waiting unconditionally. On platforms where force-kill |
| 218 | // cannot interrupt the syscall (macOS, Windows non-alertable |
| 219 | // Sleep) the thread exits when the syscall completes naturally. |
| 220 | for _, thread := range drainedThreads { |
| 221 | if !thread.state.Is(state.Yielding) { |
| 222 | thread.forceKillMu.RLock() |
| 223 | C.frankenphp_force_kill_thread(thread.forceKill) |
| 224 | thread.forceKillMu.RUnlock() |
| 225 | } |
| 226 | } |
| 227 | <-done |
| 228 | } |
| 229 | |
| 230 | return drainedThreads |
| 231 | } |
| 232 | |
| 233 | // RestartWorkers attempts to restart all workers gracefully. |
| 234 | // All workers must be restarted at the same time to prevent issues with |
no test coverage detected