phpmainthread.go 5.4 KB

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