-
-
Notifications
You must be signed in to change notification settings - Fork 300
/
Copy pathphpmainthread.go
209 lines (180 loc) · 5.96 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
207
208
209
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 (
"strings"
"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 ErrMainThreadCreation
}
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
}
//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(disableTimeouts C.bool) *C.char {
if mainThread.phpIni == nil {
mainThread.phpIni = make(map[string]string)
}
// Timeouts are currently fundamentally broken
// with ZTS except on Linux and FreeBSD: https://bugs.php.net/bug.php?id=79464
// Disable timeouts if ZEND_MAX_EXECUTION_TIMERS is not supported
if disableTimeouts {
mainThread.phpIni["max_execution_time"] = "0"
mainThread.phpIni["max_input_time"] = "-1"
}
// Pass the php.ini overrides to PHP before startup
// TODO: if needed this would also be possible on a per-thread basis
var overrides strings.Builder
for k, v := range mainThread.phpIni {
overrides.WriteString(k)
overrides.WriteByte('=')
overrides.WriteString(v)
overrides.WriteByte('\n')
}
return C.CString(overrides.String())
}