Sandbox.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. <?php
  2. namespace think\swoole;
  3. use Closure;
  4. use InvalidArgumentException;
  5. use ReflectionObject;
  6. use RuntimeException;
  7. use think\App;
  8. use think\Config;
  9. use think\Container;
  10. use think\Event;
  11. use think\Http;
  12. use think\swoole\concerns\ModifyProperty;
  13. use think\swoole\contract\ResetterInterface;
  14. use think\swoole\coroutine\Context;
  15. use think\swoole\resetters\ClearInstances;
  16. use think\swoole\resetters\ResetConfig;
  17. use think\swoole\resetters\ResetEvent;
  18. use think\swoole\resetters\ResetService;
  19. use Throwable;
  20. use think\swoole\App as SwooleApp;
  21. class Sandbox
  22. {
  23. use ModifyProperty;
  24. /**
  25. * The app containers in different coroutine environment.
  26. *
  27. * @var SwooleApp[]
  28. */
  29. protected $snapshots = [];
  30. /** @var SwooleApp */
  31. protected $app;
  32. /** @var Config */
  33. protected $config;
  34. /** @var Event */
  35. protected $event;
  36. /** @var ResetterInterface[] */
  37. protected $resetters = [];
  38. protected $services = [];
  39. public function __construct(Container $app)
  40. {
  41. $this->setBaseApp($app);
  42. $this->initialize();
  43. }
  44. public function setBaseApp(Container $app)
  45. {
  46. $this->app = $app;
  47. return $this;
  48. }
  49. public function getBaseApp()
  50. {
  51. return $this->app;
  52. }
  53. protected function initialize()
  54. {
  55. Container::setInstance(function () {
  56. return $this->getApplication();
  57. });
  58. $this->app->bind(Http::class, \think\swoole\Http::class);
  59. $this->setInitialConfig();
  60. $this->setInitialServices();
  61. $this->setInitialEvent();
  62. $this->setInitialResetters();
  63. return $this;
  64. }
  65. public function run(Closure $callable, $fd = null, $persistent = false)
  66. {
  67. $this->init($fd);
  68. try {
  69. $this->getApplication()->invoke($callable, [$this]);
  70. } catch (Throwable $e) {
  71. throw $e;
  72. } finally {
  73. $this->clear(!$persistent);
  74. }
  75. }
  76. public function init($fd = null)
  77. {
  78. if (!is_null($fd)) {
  79. Context::setData('_fd', $fd);
  80. }
  81. $app = $this->getApplication(true);
  82. $this->setInstance($app);
  83. $this->resetApp($app);
  84. }
  85. public function clear($snapshot = true)
  86. {
  87. if ($snapshot && $this->getSnapshot()) {
  88. unset($this->snapshots[$this->getSnapshotId()]);
  89. // 垃圾回收
  90. $divisor = $this->config->get('swoole.gc.divisor', 100);
  91. $probability = $this->config->get('swoole.gc.probability', 1);
  92. if (random_int(1, $divisor) <= $probability) {
  93. gc_collect_cycles();
  94. }
  95. }
  96. Context::clear();
  97. $this->setInstance($this->getBaseApp());
  98. }
  99. public function getApplication($init = false)
  100. {
  101. $snapshot = $this->getSnapshot();
  102. if ($snapshot instanceof Container) {
  103. return $snapshot;
  104. }
  105. if ($init) {
  106. $snapshot = clone $this->getBaseApp();
  107. $this->setSnapshot($snapshot);
  108. return $snapshot;
  109. }
  110. throw new InvalidArgumentException('The app object has not been initialized');
  111. }
  112. protected function getSnapshotId()
  113. {
  114. if ($fd = Context::getData('_fd')) {
  115. return 'fd_' . $fd;
  116. }
  117. return Context::getCoroutineId();
  118. }
  119. /**
  120. * Get current snapshot.
  121. * @return App|null
  122. */
  123. public function getSnapshot()
  124. {
  125. return $this->snapshots[$this->getSnapshotId()] ?? null;
  126. }
  127. public function setSnapshot(Container $snapshot)
  128. {
  129. $this->snapshots[$this->getSnapshotId()] = $snapshot;
  130. return $this;
  131. }
  132. public function setInstance(Container $app)
  133. {
  134. $app->instance('app', $app);
  135. $app->instance(Container::class, $app);
  136. $reflectObject = new ReflectionObject($app);
  137. $reflectProperty = $reflectObject->getProperty('services');
  138. $reflectProperty->setAccessible(true);
  139. $services = $reflectProperty->getValue($app);
  140. foreach ($services as $service) {
  141. $this->modifyProperty($service, $app);
  142. }
  143. }
  144. /**
  145. * Set initial config.
  146. */
  147. protected function setInitialConfig()
  148. {
  149. $this->config = clone $this->getBaseApp()->config;
  150. }
  151. protected function setInitialEvent()
  152. {
  153. $this->event = clone $this->getBaseApp()->event;
  154. }
  155. /**
  156. * Get config snapshot.
  157. */
  158. public function getConfig()
  159. {
  160. return $this->config;
  161. }
  162. public function getEvent()
  163. {
  164. return $this->event;
  165. }
  166. public function getServices()
  167. {
  168. return $this->services;
  169. }
  170. protected function setInitialServices()
  171. {
  172. $app = $this->getBaseApp();
  173. $services = $this->config->get('swoole.services', []);
  174. foreach ($services as $service) {
  175. if (class_exists($service) && !in_array($service, $this->services)) {
  176. $serviceObj = new $service($app);
  177. $this->services[$service] = $serviceObj;
  178. }
  179. }
  180. }
  181. /**
  182. * Initialize resetters.
  183. */
  184. protected function setInitialResetters()
  185. {
  186. $app = $this->getBaseApp();
  187. $resetters = [
  188. ClearInstances::class,
  189. ResetConfig::class,
  190. ResetEvent::class,
  191. ResetService::class,
  192. ];
  193. $resetters = array_merge($resetters, $this->config->get('swoole.resetters', []));
  194. foreach ($resetters as $resetter) {
  195. $resetterClass = $app->make($resetter);
  196. if (!$resetterClass instanceof ResetterInterface) {
  197. throw new RuntimeException("{$resetter} must implement " . ResetterInterface::class);
  198. }
  199. $this->resetters[$resetter] = $resetterClass;
  200. }
  201. }
  202. /**
  203. * Reset Application.
  204. *
  205. * @param Container $app
  206. */
  207. protected function resetApp(Container $app)
  208. {
  209. foreach ($this->resetters as $resetter) {
  210. $resetter->handle($app, $this);
  211. }
  212. }
  213. }