php_thread.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. package frankenphp
  2. // #include "frankenphp.h"
  3. import "C"
  4. import (
  5. "net/http"
  6. "runtime"
  7. "unsafe"
  8. )
  9. type phpThread struct {
  10. runtime.Pinner
  11. mainRequest *http.Request
  12. workerRequest *http.Request
  13. requestChan chan *http.Request
  14. worker *worker
  15. // the script name for the current request
  16. scriptName string
  17. // the index in the phpThreads slice
  18. threadIndex int
  19. // right before the first work iteration
  20. onStartup func(*phpThread)
  21. // the actual work iteration (done in a loop)
  22. beforeScriptExecution func(*phpThread)
  23. // after the work iteration is done
  24. afterScriptExecution func(*phpThread, int)
  25. // after the thread is done
  26. onShutdown func(*phpThread)
  27. // exponential backoff for worker failures
  28. backoff *exponentialBackoff
  29. // known $_SERVER key names
  30. knownVariableKeys map[string]*C.zend_string
  31. // the state handler
  32. state *threadStateHandler
  33. }
  34. func (thread *phpThread) getActiveRequest() *http.Request {
  35. if thread.workerRequest != nil {
  36. return thread.workerRequest
  37. }
  38. return thread.mainRequest
  39. }
  40. func (thread *phpThread) setInactive() {
  41. thread.scriptName = ""
  42. // TODO: handle this in a state machine
  43. if !thread.state.is(stateShuttingDown) {
  44. thread.state.set(stateInactive)
  45. }
  46. }
  47. func (thread *phpThread) setActive(
  48. onStartup func(*phpThread),
  49. beforeScriptExecution func(*phpThread),
  50. afterScriptExecution func(*phpThread, int),
  51. onShutdown func(*phpThread),
  52. ) {
  53. // to avoid race conditions, the thread sets its own hooks on startup
  54. thread.onStartup = func(thread *phpThread) {
  55. if thread.onShutdown != nil {
  56. thread.onShutdown(thread)
  57. }
  58. thread.onStartup = onStartup
  59. thread.beforeScriptExecution = beforeScriptExecution
  60. thread.onShutdown = onShutdown
  61. thread.afterScriptExecution = afterScriptExecution
  62. if thread.onStartup != nil {
  63. thread.onStartup(thread)
  64. }
  65. }
  66. thread.state.set(stateActive)
  67. }
  68. // Pin a string that is not null-terminated
  69. // PHP's zend_string may contain null-bytes
  70. func (thread *phpThread) pinString(s string) *C.char {
  71. sData := unsafe.StringData(s)
  72. thread.Pin(sData)
  73. return (*C.char)(unsafe.Pointer(sData))
  74. }
  75. // C strings must be null-terminated
  76. func (thread *phpThread) pinCString(s string) *C.char {
  77. return thread.pinString(s + "\x00")
  78. }
  79. //export go_frankenphp_on_thread_startup
  80. func go_frankenphp_on_thread_startup(threadIndex C.uintptr_t) {
  81. phpThreads[threadIndex].setInactive()
  82. }
  83. //export go_frankenphp_before_script_execution
  84. func go_frankenphp_before_script_execution(threadIndex C.uintptr_t) *C.char {
  85. thread := phpThreads[threadIndex]
  86. // if the state is inactive, wait for it to be active
  87. if thread.state.is(stateInactive) {
  88. thread.state.waitFor(stateActive, stateShuttingDown)
  89. }
  90. // returning nil signals the thread to stop
  91. if thread.state.is(stateShuttingDown) {
  92. return nil
  93. }
  94. // if the thread is not ready yet, set it up
  95. if !thread.state.is(stateReady) {
  96. thread.state.set(stateReady)
  97. if thread.onStartup != nil {
  98. thread.onStartup(thread)
  99. }
  100. }
  101. // execute a hook before the script is executed
  102. thread.beforeScriptExecution(thread)
  103. // return the name of the PHP script that should be executed
  104. return thread.pinCString(thread.scriptName)
  105. }
  106. //export go_frankenphp_after_script_execution
  107. func go_frankenphp_after_script_execution(threadIndex C.uintptr_t, exitStatus C.int) {
  108. thread := phpThreads[threadIndex]
  109. if exitStatus < 0 {
  110. panic(ScriptExecutionError)
  111. }
  112. if thread.afterScriptExecution != nil {
  113. thread.afterScriptExecution(thread, int(exitStatus))
  114. }
  115. thread.Unpin()
  116. }
  117. //export go_frankenphp_on_thread_shutdown
  118. func go_frankenphp_on_thread_shutdown(threadIndex C.uintptr_t) {
  119. thread := phpThreads[threadIndex]
  120. thread.Unpin()
  121. if thread.onShutdown != nil {
  122. thread.onShutdown(thread)
  123. }
  124. thread.state.set(stateDone)
  125. }