@@ -32,11 +32,14 @@ import (
3232 "runtime"
3333 "strings"
3434 "sync"
35+ "sync/atomic"
3536 "syscall"
3637 "time"
3738 "unsafe"
3839 // debug on Linux
3940 //_ "github.com/ianlancetaylor/cgosymbolizer"
41+
42+ "github.com/dunglas/frankenphp/internal/state"
4043)
4144
4245type contextKeyStruct struct {}
5659 contextKey = contextKeyStruct {}
5760 serverHeader = []string {"FrankenPHP" }
5861
59- isRunning bool
60- onServerShutdown []func ()
62+ isRunning bool
63+ isOpcacheResetting atomic.Bool
64+ threadsAreRestarting atomic.Bool
65+ onServerShutdown []func ()
6166
6267 // Set default values to make Shutdown() idempotent
6368 globalMu sync.Mutex
@@ -698,6 +703,61 @@ func go_is_context_done(threadIndex C.uintptr_t) C.bool {
698703 return C .bool (phpThreads [threadIndex ].frankenPHPContext ().isDone )
699704}
700705
706+ //export go_schedule_opcache_reset
707+ func go_schedule_opcache_reset (threadIndex C.uintptr_t ) C.bool {
708+ if isOpcacheResetting .CompareAndSwap (false , true ) {
709+ restartThreadsForOpcacheReset (nil )
710+ return C .bool (true )
711+ }
712+
713+ return C .bool (phpThreads [threadIndex ].state .Is (state .Restarting ))
714+ }
715+
716+ // restart all threads for an opcache_reset
717+ func restartThreadsForOpcacheReset (exceptThisThread * phpThread ) {
718+ if threadsAreRestarting .Load () {
719+ // ignore reloads while a restart is already ongoing
720+ return
721+ }
722+
723+ // disallow scaling threads while restarting workers
724+ scalingMu .Lock ()
725+ defer scalingMu .Unlock ()
726+
727+ // drain workers
728+ globalLogger .Info ("Restarting all PHP threads for opcache_reset" )
729+ threadsToRestart := drainWorkerThreads ()
730+
731+ // drain regular threads
732+ globalLogger .Info ("Draining regular PHP threads for opcache_reset" )
733+ wg := sync.WaitGroup {}
734+ for _ , thread := range regularThreads {
735+ if thread .state .Is (state .Ready ) {
736+ threadsToRestart = append (threadsToRestart , thread )
737+ thread .state .Set (state .Restarting )
738+ close (thread .drainChan )
739+
740+ wg .Go (func () {
741+ thread .state .WaitFor (state .Yielding )
742+ })
743+ }
744+ }
745+
746+ // other threads may not parse new scripts while this thread is scheduling an opcache_reset
747+ // sleeping a bit here makes this much less likely to happen
748+ // waiting for all other threads to drain first can potentially deadlock
749+ time .Sleep (100 * time .Millisecond )
750+
751+ go func () {
752+ wg .Wait ()
753+ for _ , thread := range threadsToRestart {
754+ thread .drainChan = make (chan struct {})
755+ thread .state .Set (state .Ready )
756+ isOpcacheResetting .Store (false )
757+ }
758+ }()
759+ }
760+
701761// ExecuteScriptCLI executes the PHP script passed as parameter.
702762// It returns the exit status code of the script.
703763func ExecuteScriptCLI (script string , args []string ) int {
0 commit comments