phpmainthread.go 5.7 KB

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