-
-
Notifications
You must be signed in to change notification settings - Fork 290
/
Copy pathphpmainthread.go
206 lines (178 loc) · 5.67 KB
/
phpmainthread.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
package frankenphp
// #cgo nocallback frankenphp_new_main_thread
// #cgo nocallback frankenphp_init_persistent_string
// #cgo noescape frankenphp_new_main_thread
// #cgo noescape frankenphp_init_persistent_string
// #include <php_variables.h>
// #include "frankenphp.h"
import "C"
import (
"fmt"
"sync"
"github.com/dunglas/frankenphp/internal/memory"
"github.com/dunglas/frankenphp/internal/phpheaders"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// represents the main PHP thread
// the thread needs to keep running as long as all other threads are running
type phpMainThread struct {
state *threadState
done chan struct{}
numThreads int
maxThreads int
phpIni map[string]string
commonHeaders map[string]*C.zend_string
knownServerKeys map[string]*C.zend_string
sandboxedEnv map[string]*C.zend_string
}
var (
phpThreads []*phpThread
mainThread *phpMainThread
)
// initPHPThreads starts the main PHP thread,
// a fixed number of inactive PHP threads
// and reserves a fixed number of possible PHP threads
func initPHPThreads(numThreads int, numMaxThreads int, phpIni map[string]string) (*phpMainThread, error) {
mainThread = &phpMainThread{
state: newThreadState(),
done: make(chan struct{}),
numThreads: numThreads,
maxThreads: numMaxThreads,
phpIni: phpIni,
sandboxedEnv: initializeEnv(),
}
// initialize the first thread
// this needs to happen before starting the main thread
// since some extensions access environment variables on startup
// the threadIndex on the main thread defaults to 0 -> phpThreads[0].Pin(...)
initialThread := newPHPThread(0)
phpThreads = []*phpThread{initialThread}
if err := mainThread.start(); err != nil {
return nil, err
}
// initialize all other threads
phpThreads = make([]*phpThread, mainThread.maxThreads)
phpThreads[0] = initialThread
for i := 1; i < mainThread.maxThreads; i++ {
phpThreads[i] = newPHPThread(i)
}
// start the underlying C threads
ready := sync.WaitGroup{}
ready.Add(numThreads)
for i := 0; i < numThreads; i++ {
thread := phpThreads[i]
go func() {
thread.boot()
ready.Done()
}()
}
ready.Wait()
return mainThread, nil
}
func drainPHPThreads() {
doneWG := sync.WaitGroup{}
doneWG.Add(len(phpThreads))
mainThread.state.set(stateShuttingDown)
close(mainThread.done)
for _, thread := range phpThreads {
// shut down all reserved threads
if thread.state.compareAndSwap(stateReserved, stateDone) {
doneWG.Done()
continue
}
// shut down all active threads
go func(thread *phpThread) {
thread.shutdown()
doneWG.Done()
}(thread)
}
doneWG.Wait()
mainThread.state.set(stateDone)
mainThread.state.waitFor(stateReserved)
phpThreads = nil
}
func (mainThread *phpMainThread) start() error {
if C.frankenphp_new_main_thread(C.int(mainThread.numThreads)) != 0 {
return MainThreadCreationError
}
mainThread.state.waitFor(stateReady)
// cache common request headers as zend_strings (HTTP_ACCEPT, HTTP_USER_AGENT, etc.)
mainThread.commonHeaders = make(map[string]*C.zend_string, len(phpheaders.CommonRequestHeaders))
for key, phpKey := range phpheaders.CommonRequestHeaders {
mainThread.commonHeaders[key] = C.frankenphp_init_persistent_string(C.CString(phpKey), C.size_t(len(phpKey)))
}
// cache $_SERVER keys as zend_strings (SERVER_PROTOCOL, SERVER_SOFTWARE, etc.)
mainThread.knownServerKeys = make(map[string]*C.zend_string, len(knownServerKeys))
for _, phpKey := range knownServerKeys {
mainThread.knownServerKeys[phpKey] = C.frankenphp_init_persistent_string(toUnsafeChar(phpKey), C.size_t(len(phpKey)))
}
return nil
}
func getInactivePHPThread() *phpThread {
for _, thread := range phpThreads {
if thread.state.is(stateInactive) {
return thread
}
}
for _, thread := range phpThreads {
if thread.state.compareAndSwap(stateReserved, stateBootRequested) {
thread.boot()
return thread
}
}
return nil
}
func getPHPThreadAtState(state stateID) *phpThread {
for _, thread := range phpThreads {
if thread.state.is(state) {
return thread
}
}
return nil
}
//export go_frankenphp_main_thread_is_ready
func go_frankenphp_main_thread_is_ready() {
mainThread.setAutomaticMaxThreads()
if mainThread.maxThreads < mainThread.numThreads {
mainThread.maxThreads = mainThread.numThreads
}
mainThread.state.set(stateReady)
mainThread.state.waitFor(stateDone)
}
// max_threads = auto
// setAutomaticMaxThreads estimates the amount of threads based on php.ini and system memory_limit
// If unable to get the system's memory limit, simply double num_threads
func (mainThread *phpMainThread) setAutomaticMaxThreads() {
if mainThread.maxThreads >= 0 {
return
}
perThreadMemoryLimit := int64(C.frankenphp_get_current_memory_limit())
totalSysMemory := memory.TotalSysMemory()
if perThreadMemoryLimit <= 0 || totalSysMemory == 0 {
mainThread.maxThreads = mainThread.numThreads * 2
return
}
maxAllowedThreads := totalSysMemory / uint64(perThreadMemoryLimit)
mainThread.maxThreads = int(maxAllowedThreads)
if c := logger.Check(zapcore.DebugLevel, "Automatic thread limit"); c != nil {
c.Write(zap.Int("perThreadMemoryLimitMB", int(perThreadMemoryLimit/1024/1024)), zap.Int("maxThreads", mainThread.maxThreads))
}
}
//export go_frankenphp_shutdown_main_thread
func go_frankenphp_shutdown_main_thread() {
mainThread.state.set(stateReserved)
}
//export go_get_custom_php_ini
func go_get_custom_php_ini() *C.char {
if mainThread.phpIni == nil {
return nil
}
// pass the php.ini overrides to PHP before startup
// TODO: if needed this would also be possible on a per-thread basis
overrides := ""
for k, v := range mainThread.phpIni {
overrides += fmt.Sprintf("%s=%s\n", k, v)
}
return C.CString(overrides)
}