mirror of
https://github.com/dunglas/frankenphp.git
synced 2025-12-24 13:38:11 +08:00
* Decouple workers. * Moves code to separate file. * Cleans up the exponential backoff. * Initial working implementation. * Refactors php threads to take callbacks. * Cleanup. * Cleanup. * Cleanup. * Cleanup. * Adjusts watcher logic. * Adjusts the watcher logic. * Fix opcache_reset race condition. * Fixing merge conflicts and formatting. * Prevents overlapping of TSRM reservation and script execution. * Adjustments as suggested by @dunglas. * Adds error assertions. * Adds comments. * Removes logs and explicitly compares to C.false. * Resets check. * Adds cast for safety. * Fixes waitgroup overflow. * Resolves waitgroup race condition on startup. * Moves worker request logic to worker.go. * Removes defer. * Removes call from go to c. * Fixes merge conflict. * Adds fibers test back in. * Refactors new thread loop approach. * Removes redundant check. * Adds compareAndSwap. * Refactor: removes global waitgroups and uses a 'thread state' abstraction instead. * Removes unnecessary method. * Updates comment. * Removes unnecessary booleans. * test * First state machine steps. * Splits threads. * Minimal working implementation with broken tests. * Fixes tests. * Refactoring. * Fixes merge conflicts. * Formatting * C formatting. * More cleanup. * Allows for clean state transitions. * Adds state tests. * Adds support for thread transitioning. * Fixes the testdata path. * Formatting. * Allows transitioning back to inactive state. * Fixes go linting. * Formatting. * Removes duplication. * Applies suggestions by @dunglas * Removes redundant check. * Locks the handler on restart. * Removes unnecessary log. * Changes Unpin() logic as suggested by @withinboredom * Adds suggestions by @dunglas and resolves TODO. * Makes restarts fully safe. * Will make the initial startup fail even if the watcher is enabled (as is currently the case) * Also adds compareAndSwap to the test. * Adds comment. * Prevents panic on initial watcher startup.
143 lines
2.9 KiB
Go
143 lines
2.9 KiB
Go
package frankenphp
|
|
|
|
import (
|
|
"slices"
|
|
"strconv"
|
|
"sync"
|
|
)
|
|
|
|
type stateID uint8
|
|
|
|
const (
|
|
// lifecycle states of a thread
|
|
stateBooting stateID = iota
|
|
stateShuttingDown
|
|
stateDone
|
|
|
|
// these states are safe to transition from at any time
|
|
stateInactive
|
|
stateReady
|
|
|
|
// states necessary for restarting workers
|
|
stateRestarting
|
|
stateYielding
|
|
|
|
// states necessary for transitioning between different handlers
|
|
stateTransitionRequested
|
|
stateTransitionInProgress
|
|
stateTransitionComplete
|
|
)
|
|
|
|
type threadState struct {
|
|
currentState stateID
|
|
mu sync.RWMutex
|
|
subscribers []stateSubscriber
|
|
}
|
|
|
|
type stateSubscriber struct {
|
|
states []stateID
|
|
ch chan struct{}
|
|
}
|
|
|
|
func newThreadState() *threadState {
|
|
return &threadState{
|
|
currentState: stateBooting,
|
|
subscribers: []stateSubscriber{},
|
|
mu: sync.RWMutex{},
|
|
}
|
|
}
|
|
|
|
func (ts *threadState) is(state stateID) bool {
|
|
ts.mu.RLock()
|
|
ok := ts.currentState == state
|
|
ts.mu.RUnlock()
|
|
|
|
return ok
|
|
}
|
|
|
|
func (ts *threadState) compareAndSwap(compareTo stateID, swapTo stateID) bool {
|
|
ts.mu.Lock()
|
|
ok := ts.currentState == compareTo
|
|
if ok {
|
|
ts.currentState = swapTo
|
|
ts.notifySubscribers(swapTo)
|
|
}
|
|
ts.mu.Unlock()
|
|
|
|
return ok
|
|
}
|
|
|
|
func (ts *threadState) name() string {
|
|
// TODO: return the actual name for logging/metrics
|
|
return "state:" + strconv.Itoa(int(ts.get()))
|
|
}
|
|
|
|
func (ts *threadState) get() stateID {
|
|
ts.mu.RLock()
|
|
id := ts.currentState
|
|
ts.mu.RUnlock()
|
|
|
|
return id
|
|
}
|
|
|
|
func (ts *threadState) set(nextState stateID) {
|
|
ts.mu.Lock()
|
|
ts.currentState = nextState
|
|
ts.notifySubscribers(nextState)
|
|
ts.mu.Unlock()
|
|
}
|
|
|
|
func (ts *threadState) notifySubscribers(nextState stateID) {
|
|
if len(ts.subscribers) == 0 {
|
|
return
|
|
}
|
|
newSubscribers := []stateSubscriber{}
|
|
// notify subscribers to the state change
|
|
for _, sub := range ts.subscribers {
|
|
if !slices.Contains(sub.states, nextState) {
|
|
newSubscribers = append(newSubscribers, sub)
|
|
continue
|
|
}
|
|
close(sub.ch)
|
|
}
|
|
ts.subscribers = newSubscribers
|
|
}
|
|
|
|
// block until the thread reaches a certain state
|
|
func (ts *threadState) waitFor(states ...stateID) {
|
|
ts.mu.Lock()
|
|
if slices.Contains(states, ts.currentState) {
|
|
ts.mu.Unlock()
|
|
return
|
|
}
|
|
sub := stateSubscriber{
|
|
states: states,
|
|
ch: make(chan struct{}),
|
|
}
|
|
ts.subscribers = append(ts.subscribers, sub)
|
|
ts.mu.Unlock()
|
|
<-sub.ch
|
|
}
|
|
|
|
// safely request a state change from a different goroutine
|
|
func (ts *threadState) requestSafeStateChange(nextState stateID) bool {
|
|
ts.mu.Lock()
|
|
switch ts.currentState {
|
|
// disallow state changes if shutting down
|
|
case stateShuttingDown, stateDone:
|
|
ts.mu.Unlock()
|
|
return false
|
|
// ready and inactive are safe states to transition from
|
|
case stateReady, stateInactive:
|
|
ts.currentState = nextState
|
|
ts.notifySubscribers(nextState)
|
|
ts.mu.Unlock()
|
|
return true
|
|
}
|
|
ts.mu.Unlock()
|
|
|
|
// wait for the state to change to a safe state
|
|
ts.waitFor(stateReady, stateInactive, stateShuttingDown)
|
|
return ts.requestSafeStateChange(nextState)
|
|
}
|