phpthread.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. package frankenphp
  2. // #cgo nocallback frankenphp_new_php_thread
  3. // #include "frankenphp.h"
  4. import "C"
  5. import (
  6. "net/http"
  7. "runtime"
  8. "sync"
  9. "unsafe"
  10. "go.uber.org/zap"
  11. )
  12. // representation of the actual underlying PHP thread
  13. // identified by the index in the phpThreads slice
  14. type phpThread struct {
  15. runtime.Pinner
  16. threadIndex int
  17. requestChan chan *http.Request
  18. drainChan chan struct{}
  19. handlerMu sync.Mutex
  20. handler threadHandler
  21. state *threadState
  22. sandboxedEnv map[string]*C.zend_string
  23. }
  24. // interface that defines how the callbacks from the C thread should be handled
  25. type threadHandler interface {
  26. name() string
  27. beforeScriptExecution() string
  28. afterScriptExecution(exitStatus int)
  29. getActiveRequest() *http.Request
  30. }
  31. func newPHPThread(threadIndex int) *phpThread {
  32. return &phpThread{
  33. threadIndex: threadIndex,
  34. requestChan: make(chan *http.Request),
  35. state: newThreadState(),
  36. }
  37. }
  38. // boot starts the underlying PHP thread
  39. func (thread *phpThread) boot() {
  40. // thread must be in reserved state to boot
  41. if !thread.state.compareAndSwap(stateReserved, stateBooting) && !thread.state.compareAndSwap(stateBootRequested, stateBooting) {
  42. logger.Panic("thread is not in reserved state: " + thread.state.name())
  43. return
  44. }
  45. // boot threads as inactive
  46. thread.handlerMu.Lock()
  47. thread.handler = &inactiveThread{thread: thread}
  48. thread.drainChan = make(chan struct{})
  49. thread.handlerMu.Unlock()
  50. // start the actual posix thread - TODO: try this with go threads instead
  51. if !C.frankenphp_new_php_thread(C.uintptr_t(thread.threadIndex)) {
  52. logger.Panic("unable to create thread", zap.Int("threadIndex", thread.threadIndex))
  53. }
  54. thread.state.waitFor(stateInactive)
  55. }
  56. // shutdown the underlying PHP thread
  57. func (thread *phpThread) shutdown() {
  58. if !thread.state.requestSafeStateChange(stateShuttingDown) {
  59. // already shutting down or done
  60. return
  61. }
  62. close(thread.drainChan)
  63. thread.state.waitFor(stateDone)
  64. thread.drainChan = make(chan struct{})
  65. // threads go back to the reserved state from which they can be booted again
  66. if mainThread.state.is(stateReady) {
  67. thread.state.set(stateReserved)
  68. }
  69. }
  70. // change the thread handler safely
  71. // must be called from outside the PHP thread
  72. func (thread *phpThread) setHandler(handler threadHandler) {
  73. thread.handlerMu.Lock()
  74. defer thread.handlerMu.Unlock()
  75. if !thread.state.requestSafeStateChange(stateTransitionRequested) {
  76. // no state change allowed == shutdown or done
  77. return
  78. }
  79. close(thread.drainChan)
  80. thread.state.waitFor(stateTransitionInProgress)
  81. thread.handler = handler
  82. thread.drainChan = make(chan struct{})
  83. thread.state.set(stateTransitionComplete)
  84. }
  85. // transition to a new handler safely
  86. // is triggered by setHandler and executed on the PHP thread
  87. func (thread *phpThread) transitionToNewHandler() string {
  88. thread.state.set(stateTransitionInProgress)
  89. thread.state.waitFor(stateTransitionComplete)
  90. // execute beforeScriptExecution of the new handler
  91. return thread.handler.beforeScriptExecution()
  92. }
  93. func (thread *phpThread) getActiveRequest() *http.Request {
  94. return thread.handler.getActiveRequest()
  95. }
  96. // Pin a string that is not null-terminated
  97. // PHP's zend_string may contain null-bytes
  98. func (thread *phpThread) pinString(s string) *C.char {
  99. sData := unsafe.StringData(s)
  100. if sData == nil {
  101. return nil
  102. }
  103. thread.Pin(sData)
  104. return (*C.char)(unsafe.Pointer(sData))
  105. }
  106. // C strings must be null-terminated
  107. func (thread *phpThread) pinCString(s string) *C.char {
  108. return thread.pinString(s + "\x00")
  109. }
  110. //export go_frankenphp_before_script_execution
  111. func go_frankenphp_before_script_execution(threadIndex C.uintptr_t) *C.char {
  112. thread := phpThreads[threadIndex]
  113. scriptName := thread.handler.beforeScriptExecution()
  114. // if no scriptName is passed, shut down
  115. if scriptName == "" {
  116. return nil
  117. }
  118. // return the name of the PHP script that should be executed
  119. return thread.pinCString(scriptName)
  120. }
  121. //export go_frankenphp_after_script_execution
  122. func go_frankenphp_after_script_execution(threadIndex C.uintptr_t, exitStatus C.int) {
  123. thread := phpThreads[threadIndex]
  124. if exitStatus < 0 {
  125. panic(ScriptExecutionError)
  126. }
  127. thread.handler.afterScriptExecution(int(exitStatus))
  128. // unpin all memory used during script execution
  129. thread.Unpin()
  130. }
  131. //export go_frankenphp_on_thread_shutdown
  132. func go_frankenphp_on_thread_shutdown(threadIndex C.uintptr_t) {
  133. thread := phpThreads[threadIndex]
  134. thread.Unpin()
  135. thread.state.set(stateDone)
  136. }