├── LICENSE ├── README.md ├── composer.json └── src ├── AtomicManager.php ├── ChannelLock.php ├── ChannelManger.php ├── Container.php ├── Context ├── ContextItemHandlerInterface.php ├── ContextManager.php └── Exception │ └── ModifyError.php ├── CoroutineRunner ├── Runner.php └── Task.php ├── CoroutineSingleTon.php ├── Csp.php ├── Di.php ├── Event.php ├── Invoker.php ├── MultiContainer.php ├── MultiEvent.php ├── Process ├── AbstractProcess.php ├── Config.php ├── Exception.php ├── Manager.php └── Socket │ ├── AbstractTcpProcess.php │ ├── AbstractUnixProcess.php │ ├── TcpProcessConfig.php │ └── UnixProcessConfig.php ├── ReadyScheduler.php ├── Singleton.php ├── TableManager.php ├── Timer.php └── WaitGroup.php /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## CoroutineRunner 2 | 3 | ``` 4 | use EasySwoole\Component\CoroutineRunner\Runner; 5 | use Swoole\Coroutine\Scheduler; 6 | use EasySwoole\Component\CoroutineRunner\Task; 7 | $scheduler = new Scheduler; 8 | $scheduler->add(function () { 9 | $runner = new Runner(4); 10 | $i = 10; 11 | while ($i){ 12 | $runner->addTask(new Task(function ()use($runner,$i){ 13 | var_dump("now is num.{$i} at time ".time()); 14 | \co::sleep(1); 15 | 16 | if($i == 5){ 17 | $runner->addTask(new Task(function (){ 18 | var_dump('this is task add in running'); 19 | })); 20 | } 21 | 22 | })); 23 | $i--; 24 | } 25 | $runner->start(); 26 | var_dump('task finish'); 27 | }); 28 | $scheduler->start(); 29 | 30 | ``` 31 | 32 | ## PoolInterface Example1 33 | 34 | ``` 35 | use EasySwoole\Component\Pool\PoolManager; 36 | use EasySwoole\Component\Pool\TraitObjectInvoker; 37 | use EasySwoole\Utility\Random; 38 | use EasySwoole\Component\Pool\AbstractPoolObject; 39 | use EasySwoole\Component\Pool\PoolObjectInterface; 40 | use EasySwoole\Component\Pool\AbstractPool; 41 | class test 42 | { 43 | public $id; 44 | 45 | function __construct() 46 | { 47 | $this->id = Random::character(8); 48 | } 49 | 50 | function fuck(){ 51 | var_dump('this is fuck at class:'.static::class.'@id:'.$this->id); 52 | } 53 | } 54 | 55 | class test2 extends test implements PoolObjectInterface 56 | { 57 | function objectRestore() 58 | { 59 | var_dump('this is objectRestore at class:'.static::class.'@id:'.$this->id); 60 | } 61 | 62 | function gc() 63 | { 64 | // TODO: Implement gc() method. 65 | } 66 | 67 | function beforeUse(): bool 68 | { 69 | // TODO: Implement beforeUse() method. 70 | return true; 71 | } 72 | } 73 | 74 | class testPool extends AbstractPool 75 | { 76 | 77 | protected function createObject() 78 | { 79 | // TODO: Implement createObject() method. 80 | return new test(); 81 | } 82 | } 83 | 84 | class testPool2 extends AbstractPool 85 | { 86 | 87 | protected function createObject() 88 | { 89 | // TODO: Implement createObject() method. 90 | return new test2(); 91 | } 92 | } 93 | 94 | 95 | 96 | class test3 extends test 97 | { 98 | use TraitObjectInvoker; 99 | } 100 | 101 | class test4 extends AbstractPoolObject 102 | { 103 | function finalFuck() 104 | { 105 | var_dump('final fuck'); 106 | } 107 | 108 | function objectRestore() 109 | { 110 | var_dump('final objectRestore'); 111 | } 112 | } 113 | 114 | //cli下关闭pool的自动定时检查 115 | PoolManager::getInstance()->getDefaultConfig()->setIntervalCheckTime(0); 116 | 117 | go(function (){ 118 | go(function (){ 119 | $object = PoolManager::getInstance()->getPool(test::class)->getObj(); 120 | $object->fuck(); 121 | PoolManager::getInstance()->getPool(test::class)->recycleObj($object); 122 | }); 123 | 124 | go(function (){ 125 | testPool::invoke(function (test $test){ 126 | $test->fuck(); 127 | }); 128 | }); 129 | 130 | go(function (){ 131 | testPool2::invoke(function (test2 $test){ 132 | $test->fuck(); 133 | }); 134 | }); 135 | 136 | go(function (){ 137 | test3::invoke(function (test3 $test3){ 138 | $test3->fuck(); 139 | }); 140 | }); 141 | 142 | go(function (){ 143 | $object = PoolManager::getInstance()->getPool(test4::class)->getObj(); 144 | $object->finalFuck(); 145 | PoolManager::getInstance()->getPool(test4::class)->recycleObj($object); 146 | }); 147 | }); 148 | 149 | ``` 150 | ## PoolInterface Example2 151 | ``` 152 | use EasySwoole\Component\Pool\PoolManager; 153 | use EasySwoole\Component\Pool\AbstractPool; 154 | use EasySwoole\Component\Pool\TraitInvoker; 155 | 156 | class TestPool extends AbstractPool{ 157 | function __construct(\EasySwoole\Component\Pool\PoolConf $conf) 158 | { 159 | var_dump('new TestPool'); 160 | parent::__construct($conf); 161 | } 162 | 163 | protected function createObject() 164 | { 165 | // TODO: Implement createObject() method. 166 | return new \stdClass(); 167 | } 168 | 169 | } 170 | 171 | class TestPool2 172 | { 173 | use TraitInvoker; 174 | function __construct() 175 | { 176 | var_dump('new TestPool2'); 177 | } 178 | 179 | function fuck() 180 | { 181 | var_dump('fuck'); 182 | } 183 | 184 | } 185 | 186 | go(function (){ 187 | PoolManager::getInstance()->registerAnonymous('test',function (){ 188 | return new SplFixedArray(); 189 | }); 190 | $pool = PoolManager::getInstance()->getPool(stdClass::class); 191 | $pool2 = PoolManager::getInstance()->getPool(\Redis::class); 192 | $pool3 = PoolManager::getInstance()->getPool('test'); 193 | $pool::invoke(function (stdClass $class){ 194 | var_dump($class); 195 | }); 196 | $pool2::invoke(function (\Redis $class){ 197 | var_dump($class); 198 | }); 199 | $pool3::invoke(function (SplFixedArray $array){ 200 | var_dump($array); 201 | }); 202 | 203 | TestPool::invoke(function (\stdClass $class){ 204 | // var_dump($class); 205 | }); 206 | 207 | $pool4 = PoolManager::getInstance()->getPool(TestPool::class); 208 | $pool4::invoke(function (\stdClass $class){ 209 | // var_dump($class); 210 | }); 211 | 212 | TestPool2::invoke(function ($class){ 213 | $class->fuck(); 214 | }); 215 | $pool5 = PoolManager::getInstance()->getPool(TestPool2::class); 216 | $pool5::invoke(function (\TestPool2 $class){ 217 | // $class->fuck(); 218 | }); 219 | 220 | }); 221 | ``` -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "easyswoole/component", 3 | "type": "library", 4 | "description": "easyswoole component", 5 | "keywords" : ["swoole", "framework", "async","easyswoole"], 6 | "homepage" : "https://www.easyswoole.com/", 7 | "license" : "Apache-2.0", 8 | "authors": [ 9 | { 10 | "name": "YF", 11 | "email": "291323003@qq.com" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=7.1.0", 16 | "ext-swoole":"*", 17 | "easyswoole/spl": "^2.0" 18 | }, 19 | "require-dev": { 20 | "easyswoole/phpunit": "^1.0", 21 | "easyswoole/swoole-ide-helper": "^1.2" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "EasySwoole\\Component\\": "src/", 26 | "EasySwoole\\Component\\Tests\\": "Tests/" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/AtomicManager.php: -------------------------------------------------------------------------------- 1 | list[$name])){ 26 | $a = new Atomic($int); 27 | $this->list[$name] = $a; 28 | } 29 | } 30 | 31 | function addLong($name,int $int = 0) 32 | { 33 | if(!isset($this->listForLong[$name])){ 34 | $a = new Long($int); 35 | $this->listForLong[$name] = $a; 36 | } 37 | } 38 | 39 | function getLong($name):?Long 40 | { 41 | if(isset($this->listForLong[$name])){ 42 | return $this->listForLong[$name]; 43 | }else{ 44 | return null; 45 | } 46 | } 47 | 48 | function get($name):?Atomic 49 | { 50 | if(isset($this->list[$name])){ 51 | return $this->list[$name]; 52 | }else{ 53 | return null; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/ChannelLock.php: -------------------------------------------------------------------------------- 1 | status[$cid])){ 20 | return true; 21 | } 22 | if(!isset($this->list[$lockName])){ 23 | $this->list[$lockName] = new Channel(1); 24 | } 25 | /** @var Channel $channel */ 26 | $channel = $this->list[$lockName]; 27 | $ret = $channel->push(1,$timeout); 28 | if($ret){ 29 | $this->status[$cid] = true; 30 | } 31 | return $ret; 32 | } 33 | 34 | function unlock(string $lockName,float $timeout = -1):bool 35 | { 36 | $cid = Coroutine::getCid(); 37 | if(!isset($this->status[$cid])){ 38 | return true; 39 | } 40 | if(!isset($this->list[$lockName])){ 41 | $this->list[$lockName] = new Channel(1); 42 | } 43 | /** @var Channel $channel */ 44 | $channel = $this->list[$lockName]; 45 | if($channel->isEmpty()){ 46 | unset($this->status[$cid]); 47 | return true; 48 | }else{ 49 | $ret = $channel->pop($timeout); 50 | if($ret){ 51 | unset($this->status[$cid]); 52 | } 53 | return $ret; 54 | } 55 | } 56 | 57 | function deferLock(string $lockName,float $timeout = -1):bool 58 | { 59 | $lock = $this->lock($lockName,$timeout); 60 | if($lock){ 61 | Coroutine::defer(function ()use($lockName){ 62 | $this->unlock($lockName); 63 | }); 64 | } 65 | return $lock; 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /src/ChannelManger.php: -------------------------------------------------------------------------------- 1 | list[$name])){ 22 | $chan = new Channel($size); 23 | $this->list[$name] = $chan; 24 | } 25 | } 26 | 27 | function get($name):?Channel 28 | { 29 | if(isset($this->list[$name])){ 30 | return $this->list[$name]; 31 | }else{ 32 | return null; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/Container.php: -------------------------------------------------------------------------------- 1 | allowKeys = $allowKeys; 20 | } 21 | 22 | function set($key, $item) 23 | { 24 | if(is_array($this->allowKeys) && !in_array($key,$this->allowKeys)){ 25 | return false; 26 | } 27 | $this->container[$key] = $item; 28 | return $this; 29 | } 30 | 31 | function delete($key) 32 | { 33 | if(isset($this->container[$key])){ 34 | unset($this->container[$key]); 35 | } 36 | return $this; 37 | } 38 | 39 | function get($key) 40 | { 41 | if(isset($this->container[$key])){ 42 | return $this->container[$key]; 43 | }else{ 44 | return null; 45 | } 46 | } 47 | 48 | function clear() 49 | { 50 | $this->container = []; 51 | } 52 | 53 | function all():array 54 | { 55 | return $this->container; 56 | } 57 | } -------------------------------------------------------------------------------- /src/Context/ContextItemHandlerInterface.php: -------------------------------------------------------------------------------- 1 | contextHandler[$key] = $handler; 29 | return $this; 30 | } 31 | 32 | public function set($key,$value,$cid = null):ContextManager 33 | { 34 | if(isset($this->contextHandler[$key])){ 35 | throw new ModifyError('key is already been register for context item handler'); 36 | } 37 | $cid = $this->getCid($cid); 38 | $this->context[$cid][$key] = $value; 39 | return $this; 40 | } 41 | 42 | public function get($key,$cid = null) 43 | { 44 | $cid = $this->getCid($cid); 45 | if(isset($this->context[$cid][$key])){ 46 | return $this->context[$cid][$key]; 47 | } 48 | if(isset($this->contextHandler[$key])){ 49 | /** @var ContextItemHandlerInterface $handler */ 50 | $handler = $this->contextHandler[$key]; 51 | $this->context[$cid][$key] = $handler->onContextCreate(); 52 | return $this->context[$cid][$key]; 53 | } 54 | return null; 55 | } 56 | 57 | public function unset($key,$cid = null) 58 | { 59 | $cid = $this->getCid($cid); 60 | if(isset($this->context[$cid][$key])){ 61 | if(isset($this->contextHandler[$key])){ 62 | /** @var ContextItemHandlerInterface $handler */ 63 | $handler = $this->contextHandler[$key]; 64 | $item = $this->context[$cid][$key]; 65 | unset($this->context[$cid][$key]); 66 | return $handler->onDestroy($item); 67 | } 68 | unset($this->context[$cid][$key]); 69 | return true; 70 | }else{ 71 | return false; 72 | } 73 | } 74 | 75 | public function destroy($cid = null) 76 | { 77 | $cid = $this->getCid($cid); 78 | if(isset($this->context[$cid])){ 79 | $data = $this->context[$cid]; 80 | foreach ($data as $key => $val){ 81 | $this->unset($key,$cid); 82 | } 83 | } 84 | unset($this->context[$cid]); 85 | } 86 | 87 | public function getCid($cid = null):int 88 | { 89 | if($cid === null){ 90 | $cid = Coroutine::getUid(); 91 | if(!isset($this->deferList[$cid]) && $cid > 0){ 92 | $this->deferList[$cid] = true; 93 | Coroutine::defer(function ()use($cid){ 94 | unset($this->deferList[$cid]); 95 | $this->destroy($cid); 96 | }); 97 | } 98 | return $cid; 99 | } 100 | return $cid; 101 | } 102 | 103 | public function destroyAll($force = false) 104 | { 105 | if($force){ 106 | $this->context = []; 107 | }else{ 108 | foreach ($this->context as $cid => $data){ 109 | $this->destroy($cid); 110 | } 111 | } 112 | } 113 | 114 | public function getContextArray($cid = null):?array 115 | { 116 | $cid = $this->getCid($cid); 117 | if(isset($this->context[$cid])){ 118 | return $this->context[$cid]; 119 | }else{ 120 | return null; 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /src/Context/Exception/ModifyError.php: -------------------------------------------------------------------------------- 1 | concurrency = $concurrency; 23 | $this->taskChannel = new Channel($taskChannelSize); 24 | } 25 | 26 | function setOnException(callable $call):Runner 27 | { 28 | $this->onException = $call; 29 | return $this; 30 | } 31 | 32 | function setOnLoop(callable $call):Runner 33 | { 34 | $this->onLoop = $call; 35 | return $this; 36 | } 37 | 38 | function status():array 39 | { 40 | return [ 41 | 'queueSize'=>$this->taskChannel->length(), 42 | 'concurrency'=>$this->concurrency, 43 | 'runningNum'=>$this->runningNum, 44 | 'isRunning'=>$this->isRunning 45 | ]; 46 | } 47 | 48 | function addTask(Task $task):Runner 49 | { 50 | $this->taskChannel->push($task); 51 | return $this; 52 | } 53 | 54 | function queueSize():int 55 | { 56 | return $this->taskChannel->length(); 57 | } 58 | 59 | function start(float $waitTime = 30) 60 | { 61 | if(!$this->isRunning){ 62 | $this->isRunning = true; 63 | $this->runningNum = 0; 64 | } 65 | if($waitTime <=0){ 66 | $waitTime = PHP_INT_MAX; 67 | } 68 | $start = time(); 69 | while ($waitTime > 0){ 70 | if(is_callable($this->onLoop)){ 71 | call_user_func($this->onLoop,$this); 72 | } 73 | if($this->runningNum <= $this->concurrency && !$this->taskChannel->isEmpty()){ 74 | $task = $this->taskChannel->pop(0.01); 75 | if($task instanceof Task){ 76 | Coroutine::create(function ()use($task){ 77 | $this->runningNum++; 78 | $ret = null; 79 | $task->setStartTime(microtime(true)); 80 | try{ 81 | $ret = call_user_func($task->getCall()); 82 | $task->setResult($ret); 83 | if($ret !== false && is_callable($task->getOnSuccess())){ 84 | call_user_func($task->getOnSuccess(),$task); 85 | }else if(is_callable($task->getOnFail())){ 86 | call_user_func($task->getOnFail(),$task); 87 | } 88 | }catch (\Throwable $throwable){ 89 | if(is_callable($this->onException)){ 90 | call_user_func($this->onException,$throwable,$task); 91 | }else{ 92 | throw $throwable; 93 | } 94 | }finally{ 95 | $this->runningNum--; 96 | } 97 | }); 98 | } 99 | }else{ 100 | if(time() - $start > $waitTime){ 101 | break; 102 | }else if($this->taskChannel->isEmpty() && $this->runningNum <= 0){ 103 | break; 104 | }else{ 105 | /* 106 | * 最小调度粒度为0.01 107 | */ 108 | Coroutine::sleep(0.01); 109 | } 110 | } 111 | } 112 | $this->isRunning = false; 113 | $this->runningNum = 0; 114 | } 115 | } -------------------------------------------------------------------------------- /src/CoroutineRunner/Task.php: -------------------------------------------------------------------------------- 1 | call = $call; 24 | } 25 | 26 | /** 27 | * @return int 28 | */ 29 | public function getTimeout(): int 30 | { 31 | return $this->timeout; 32 | } 33 | 34 | /** 35 | * @param int $timeout 36 | */ 37 | public function setTimeout(int $timeout): void 38 | { 39 | $this->timeout = $timeout; 40 | } 41 | 42 | /** 43 | * @return callable 44 | */ 45 | public function getCall(): callable 46 | { 47 | return $this->call; 48 | } 49 | 50 | /** 51 | * @param callable $call 52 | */ 53 | public function setCall(callable $call): void 54 | { 55 | $this->call = $call; 56 | } 57 | 58 | /** 59 | * @return callable 60 | */ 61 | public function getOnSuccess():? callable 62 | { 63 | return $this->onSuccess; 64 | } 65 | 66 | /** 67 | * @param callable $onSuccess 68 | */ 69 | public function setOnSuccess(callable $onSuccess): void 70 | { 71 | $this->onSuccess = $onSuccess; 72 | } 73 | 74 | /** 75 | * @return callable 76 | */ 77 | public function getOnFail():? callable 78 | { 79 | return $this->onFail; 80 | } 81 | 82 | /** 83 | * @param callable $onFail 84 | */ 85 | public function setOnFail(callable $onFail): void 86 | { 87 | $this->onFail = $onFail; 88 | } 89 | 90 | /** 91 | * @return float 92 | */ 93 | public function getStartTime(): float 94 | { 95 | return $this->startTime; 96 | } 97 | 98 | /** 99 | * @param float $startTime 100 | */ 101 | public function setStartTime(float $startTime): void 102 | { 103 | $this->startTime = $startTime; 104 | } 105 | 106 | /** 107 | * @return mixed 108 | */ 109 | public function getResult() 110 | { 111 | return $this->result; 112 | } 113 | 114 | /** 115 | * @param mixed $result 116 | */ 117 | public function setResult($result): void 118 | { 119 | $this->result = $result; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/CoroutineSingleTon.php: -------------------------------------------------------------------------------- 1 | 0){ 26 | Coroutine::defer(function ()use($cid){ 27 | unset(static::$instance[$cid]); 28 | }); 29 | } 30 | } 31 | return static::$instance[$cid]; 32 | } 33 | 34 | function destroy(int $cid = null) 35 | { 36 | if($cid === null){ 37 | $cid = Coroutine::getCid(); 38 | } 39 | unset(static::$instance[$cid]); 40 | } 41 | } -------------------------------------------------------------------------------- /src/Csp.php: -------------------------------------------------------------------------------- 1 | chan = new Channel($size); 20 | } 21 | 22 | function add($itemName,callable $call):Csp 23 | { 24 | $this->count = 0; 25 | $this->success = 0; 26 | $this->task[$itemName] = $call; 27 | return $this; 28 | } 29 | 30 | function successNum():int 31 | { 32 | return $this->success; 33 | } 34 | 35 | function exec(?float $timeout = 5) 36 | { 37 | if($timeout <= 0){ 38 | $timeout = PHP_INT_MAX; 39 | } 40 | $this->count = count($this->task); 41 | foreach ($this->task as $key => $call){ 42 | Coroutine::create(function ()use($key,$call){ 43 | $data = call_user_func($call); 44 | $this->chan->push([ 45 | 'key'=>$key, 46 | 'result'=>$data 47 | ]); 48 | }); 49 | } 50 | $result = []; 51 | $start = microtime(true); 52 | while($this->count > 0) 53 | { 54 | $temp = $this->chan->pop(1); 55 | if(is_array($temp)){ 56 | $key = $temp['key']; 57 | $result[$key] = $temp['result']; 58 | $this->count--; 59 | $this->success++; 60 | } 61 | if(microtime(true) - $start > $timeout){ 62 | break; 63 | } 64 | } 65 | return $result; 66 | } 67 | } -------------------------------------------------------------------------------- /src/Di.php: -------------------------------------------------------------------------------- 1 | container)){ 23 | $this->alias[$alias] = $key; 24 | return $this; 25 | }else{ 26 | throw new \InvalidArgumentException("can not alias a real key: {$alias}"); 27 | } 28 | } 29 | 30 | public function setOnKeyMiss(callable $call):Di 31 | { 32 | $this->onKeyMiss = $call; 33 | return $this; 34 | } 35 | 36 | public function deleteAlias($alias): Di 37 | { 38 | unset($this->alias[$alias]); 39 | return $this; 40 | } 41 | 42 | public function set($key, $obj,...$arg):void 43 | { 44 | /* 45 | * 注入的时候不做任何的类型检测与转换 46 | * 由于编程人员为问题,该注入资源并不一定会被用到 47 | */ 48 | $this->container[$key] = [ 49 | "obj"=>$obj, 50 | "params"=>$arg 51 | ]; 52 | } 53 | 54 | function delete($key):Di 55 | { 56 | unset($this->container[$key]); 57 | return $this; 58 | } 59 | 60 | function clear():Di 61 | { 62 | $this->container = []; 63 | return $this; 64 | } 65 | 66 | /** 67 | * @param $key 68 | * @return null 69 | * @throws \Throwable 70 | */ 71 | function get($key) 72 | { 73 | if(isset($this->alias[$key])){ 74 | $key = $this->alias[$key]; 75 | } 76 | if(isset($this->container[$key])){ 77 | $obj = $this->container[$key]['obj']; 78 | $params = $this->container[$key]['params']; 79 | if(is_object($obj) || is_callable($obj)){ 80 | return $obj; 81 | }else if(is_string($obj) && class_exists($obj)){ 82 | try{ 83 | $ref = new \ReflectionClass($obj); 84 | if(empty($params)){ 85 | $constructor = $ref->getConstructor(); 86 | if($constructor){ 87 | $list = $constructor->getParameters(); 88 | foreach ($list as $p){ 89 | $class = $p->getClass(); 90 | if($class){ 91 | $temp = $this->get($class->getName()); 92 | }else{ 93 | $temp = $this->get($p->getName()) ?? $p->getDefaultValue(); 94 | } 95 | $params[] = $temp; 96 | } 97 | } 98 | } 99 | $this->container[$key]['obj'] = $ref->newInstanceArgs($params); 100 | return $this->container[$key]['obj']; 101 | }catch (\Throwable $throwable){ 102 | throw $throwable; 103 | } 104 | }else{ 105 | return $obj; 106 | } 107 | }else{ 108 | if(is_callable($this->onKeyMiss)){ 109 | return call_user_func($this->onKeyMiss,$key); 110 | } 111 | return null; 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/Event.php: -------------------------------------------------------------------------------- 1 | get($event); 31 | if(is_callable($call)){ 32 | return call_user_func($call,...$args); 33 | }else{ 34 | return null; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/Invoker.php: -------------------------------------------------------------------------------- 1 | allowKeys = $allowKeys; 20 | } 21 | 22 | function add($key,$item) 23 | { 24 | if(is_array($this->allowKeys) && !in_array($key,$this->allowKeys)){ 25 | return false; 26 | } 27 | $this->container[$key][] = $item; 28 | return $this; 29 | } 30 | 31 | function set($key,$item) 32 | { 33 | if(is_array($this->allowKeys) && !in_array($key,$this->allowKeys)){ 34 | return false; 35 | } 36 | $this->container[$key] = [$item]; 37 | return $this; 38 | } 39 | 40 | function delete($key) 41 | { 42 | if(isset($this->container[$key])){ 43 | unset($this->container[$key]); 44 | } 45 | return $this; 46 | } 47 | 48 | function get($key):?array 49 | { 50 | if(isset($this->container[$key])){ 51 | return $this->container[$key]; 52 | }else{ 53 | return null; 54 | } 55 | } 56 | 57 | function all():array 58 | { 59 | return $this->container; 60 | } 61 | 62 | function clear() 63 | { 64 | $this->container = []; 65 | } 66 | } -------------------------------------------------------------------------------- /src/MultiEvent.php: -------------------------------------------------------------------------------- 1 | get($event); 43 | if(is_array($calls)){ 44 | foreach ($calls as $key => $call){ 45 | $res[$key] = call_user_func($call,...$args); 46 | } 47 | } 48 | return $res; 49 | } 50 | } -------------------------------------------------------------------------------- /src/Process/AbstractProcess.php: -------------------------------------------------------------------------------- 1 | config = $arg1; 37 | }else{ 38 | $this->config = new Config(); 39 | $this->config->setProcessName($arg1); 40 | $arg = array_shift($args); 41 | $this->config->setArg($arg); 42 | $redirectStdinStdout = (bool)array_shift($args) ?: false; 43 | $this->config->setRedirectStdinStdout($redirectStdinStdout); 44 | $pipeType = array_shift($args); 45 | $pipeType = $pipeType === null ? Config::PIPE_TYPE_SOCK_DGRAM : $pipeType; 46 | $this->config->setPipeType($pipeType); 47 | $bool = array_shift($args); 48 | if($bool === null){ 49 | $enableCoroutine = true; 50 | }else{ 51 | $enableCoroutine = (bool)$bool; 52 | } 53 | $this->config->setEnableCoroutine($enableCoroutine); 54 | } 55 | $this->swooleProcess = new Process([$this,'__start'],$this->config->isRedirectStdinStdout(),$this->config->getPipeType(),$this->config->isEnableCoroutine()); 56 | Manager::getInstance()->addProcess($this,false); 57 | } 58 | 59 | public function getProcess():Process 60 | { 61 | return $this->swooleProcess; 62 | } 63 | 64 | public function addTick($ms,callable $call):?int 65 | { 66 | return Timer::getInstance()->loop( 67 | $ms,$call 68 | ); 69 | } 70 | 71 | public function clearTick(int $timerId):?int 72 | { 73 | return Timer::getInstance()->clear($timerId); 74 | } 75 | 76 | public function delay($ms,callable $call):?int 77 | { 78 | return Timer::getInstance()->after($ms,$call); 79 | } 80 | 81 | /* 82 | * 服务启动后才能获得到pid 83 | */ 84 | public function getPid():?int 85 | { 86 | if(isset($this->swooleProcess->pid)){ 87 | return $this->swooleProcess->pid; 88 | }else{ 89 | return null; 90 | } 91 | } 92 | 93 | function __start(Process $process) 94 | { 95 | $table = Manager::getInstance()->getProcessTable(); 96 | $table->set($process->pid,[ 97 | 'pid'=>$process->pid, 98 | 'name'=>$this->config->getProcessName(), 99 | 'group'=>$this->config->getProcessGroup(), 100 | 'startUpTime'=>time(), 101 | "hash"=>spl_object_hash($this->getProcess()) 102 | ]); 103 | \Swoole\Timer::tick(1*1000,function ()use($table,$process){ 104 | $table->set($process->pid,[ 105 | 'memoryUsage'=>memory_get_usage(), 106 | 'memoryPeakUsage'=>memory_get_peak_usage(true) 107 | ]); 108 | }); 109 | 110 | $banOS = ['Darwin','CYGWIN','WINNT']; 111 | $canSetProcessName = true; 112 | foreach ($banOS as $os){ 113 | if (strpos(PHP_OS, $os) !== false) { 114 | $canSetProcessName = false; 115 | break; 116 | } 117 | } 118 | 119 | if($canSetProcessName && !empty($this->getProcessName())){ 120 | $process->name($this->getProcessName()); 121 | } 122 | swoole_event_add($this->swooleProcess->pipe, function(){ 123 | try{ 124 | $this->onPipeReadable($this->swooleProcess); 125 | }catch (\Throwable $throwable){ 126 | $this->onException($throwable); 127 | } 128 | }); 129 | Process::signal(SIGTERM,function ()use($process){ 130 | swoole_event_del($process->pipe); 131 | try{ 132 | $this->onSigTerm(); 133 | }catch (\Throwable $throwable){ 134 | $this->onException($throwable); 135 | } finally { 136 | \Swoole\Timer::clearAll(); 137 | Process::signal(SIGTERM, null); 138 | Event::exit(); 139 | } 140 | }); 141 | register_shutdown_function(function ()use($table,$process) { 142 | if($table){ 143 | $table->del($process->pid); 144 | } 145 | $schedule = new Scheduler(); 146 | $schedule->add(function (){ 147 | $channel = new Coroutine\Channel(1); 148 | Coroutine::create(function ()use($channel){ 149 | try{ 150 | $this->onShutDown(); 151 | }catch (\Throwable $throwable){ 152 | $this->onException($throwable); 153 | } 154 | $channel->push(1); 155 | }); 156 | $channel->pop($this->config->getMaxExitWaitTime()); 157 | \Swoole\Timer::clearAll(); 158 | Event::exit(); 159 | }); 160 | $schedule->start(); 161 | \Swoole\Timer::clearAll(); 162 | Event::exit(); 163 | }); 164 | try{ 165 | $this->run($this->config->getArg()); 166 | }catch (\Throwable $throwable){ 167 | $this->onException($throwable); 168 | } 169 | //加了 Event::wait() 在开启协程的时候,可能会crash, 170 | //swoole v5.1.3 新版本为了防止出现这个情况,所以加了判断 171 | //v4 v5的swoole 均有这个问题 172 | if(!$this->config->isEnableCoroutine()){ 173 | Event::wait(); 174 | } 175 | } 176 | 177 | public function getArg() 178 | { 179 | return $this->config->getArg(); 180 | } 181 | 182 | public function getProcessName() 183 | { 184 | return $this->config->getProcessName(); 185 | } 186 | 187 | public function getConfig():Config 188 | { 189 | return $this->config; 190 | } 191 | 192 | protected function onException(\Throwable $throwable,...$args){ 193 | throw $throwable; 194 | } 195 | 196 | protected abstract function run($arg); 197 | 198 | protected function onShutDown() 199 | { 200 | 201 | } 202 | 203 | protected function onSigTerm() 204 | { 205 | 206 | } 207 | 208 | protected function onPipeReadable(Process $process) 209 | { 210 | /* 211 | * 由于Swoole底层使用了epoll的LT模式,因此swoole_event_add添加的事件监听, 212 | * 在事件发生后回调函数中必须调用read方法读取socket中的数据,否则底层会持续触发事件回调。 213 | */ 214 | $process->read(); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/Process/Config.php: -------------------------------------------------------------------------------- 1 | processName; 30 | } 31 | 32 | /** 33 | * @param mixed $processName 34 | */ 35 | public function setProcessName($processName): void 36 | { 37 | $this->processName = $processName; 38 | } 39 | 40 | /** 41 | * @return mixed 42 | */ 43 | public function getArg() 44 | { 45 | return $this->arg; 46 | } 47 | 48 | /** 49 | * @param mixed $arg 50 | */ 51 | public function setArg($arg): void 52 | { 53 | $this->arg = $arg; 54 | } 55 | 56 | /** 57 | * @return bool 58 | */ 59 | public function isRedirectStdinStdout(): bool 60 | { 61 | return $this->redirectStdinStdout; 62 | } 63 | 64 | /** 65 | * @param bool $redirectStdinStdout 66 | */ 67 | public function setRedirectStdinStdout(bool $redirectStdinStdout): void 68 | { 69 | $this->redirectStdinStdout = $redirectStdinStdout; 70 | } 71 | 72 | /** 73 | * @return int 74 | */ 75 | public function getPipeType(): int 76 | { 77 | return $this->pipeType; 78 | } 79 | 80 | /** 81 | * @param int $pipeType 82 | */ 83 | public function setPipeType(int $pipeType): void 84 | { 85 | $this->pipeType = $pipeType; 86 | } 87 | 88 | /** 89 | * @return bool 90 | */ 91 | public function isEnableCoroutine(): bool 92 | { 93 | return $this->enableCoroutine; 94 | } 95 | 96 | /** 97 | * @param bool $enableCoroutine 98 | */ 99 | public function setEnableCoroutine(bool $enableCoroutine): void 100 | { 101 | $this->enableCoroutine = $enableCoroutine; 102 | } 103 | 104 | /** 105 | * @return int 106 | */ 107 | public function getMaxExitWaitTime(): int 108 | { 109 | return $this->maxExitWaitTime; 110 | } 111 | 112 | /** 113 | * @param int $maxExitWaitTime 114 | */ 115 | public function setMaxExitWaitTime(int $maxExitWaitTime): void 116 | { 117 | $this->maxExitWaitTime = $maxExitWaitTime; 118 | } 119 | 120 | /** 121 | * @return string 122 | */ 123 | public function getProcessGroup(): ?string 124 | { 125 | return $this->processGroup; 126 | } 127 | 128 | /** 129 | * @param string $processGroup 130 | */ 131 | public function setProcessGroup(string $processGroup): void 132 | { 133 | $this->processGroup = $processGroup; 134 | } 135 | } -------------------------------------------------------------------------------- /src/Process/Exception.php: -------------------------------------------------------------------------------- 1 | table = new Table(2048); 24 | $this->table->column('pid', Table::TYPE_INT, 8); 25 | $this->table->column('name', Table::TYPE_STRING, 50); 26 | $this->table->column('group', Table::TYPE_STRING, 50); 27 | $this->table->column('memoryUsage', Table::TYPE_INT, 8); 28 | $this->table->column('memoryPeakUsage', Table::TYPE_INT, 8); 29 | $this->table->column('startUpTime', Table::TYPE_INT, 8); 30 | $this->table->column('hash', Table::TYPE_STRING, 32); 31 | $this->table->create(); 32 | } 33 | 34 | public function getProcessTable(): Table 35 | { 36 | return $this->table; 37 | } 38 | 39 | public function getProcessByPid(int $pid): ?AbstractProcess 40 | { 41 | $info = $this->table->get($pid); 42 | if($info){ 43 | $hash = $info['hash']; 44 | if(isset($this->processList[$hash])){ 45 | return $this->processList[$hash]; 46 | } 47 | } 48 | return null; 49 | } 50 | 51 | public function getProcessByName(string $name): array 52 | { 53 | $ret = []; 54 | foreach ($this->processList as $process) { 55 | if ($process->getProcessName() === $name) { 56 | $ret[] = $process; 57 | } 58 | } 59 | 60 | return $ret; 61 | } 62 | 63 | public function getProcessByGroup(string $group): array 64 | { 65 | $ret = []; 66 | foreach ($this->processList as $process) { 67 | if ($process->getConfig()->getProcessGroup() === $group) { 68 | $ret[] = $process; 69 | } 70 | } 71 | 72 | return $ret; 73 | } 74 | 75 | public function kill($pidOrGroupName, $sig = SIGTERM): array 76 | { 77 | $list = []; 78 | if (is_numeric($pidOrGroupName)) { 79 | $info = $this->table->get($pidOrGroupName); 80 | if ($info) { 81 | $list[$pidOrGroupName] = $pidOrGroupName; 82 | } 83 | } else { 84 | foreach ($this->table as $key => $value) { 85 | if ($value['group'] == $pidOrGroupName) { 86 | $list[$key] = $value; 87 | } 88 | } 89 | } 90 | $this->clearPid($list); 91 | foreach ($list as $pid => $value) { 92 | Process::kill($pid, $sig); 93 | } 94 | return $list; 95 | } 96 | 97 | public function info($pidOrGroupName = null) 98 | { 99 | $list = []; 100 | if ($pidOrGroupName == null) { 101 | foreach ($this->table as $pid => $value) { 102 | $list[$pid] = $value; 103 | } 104 | } else if (is_numeric($pidOrGroupName)) { 105 | $info = $this->table->get($pidOrGroupName); 106 | if ($info) { 107 | $list[$pidOrGroupName] = $info; 108 | } 109 | } else { 110 | foreach ($this->table as $key => $value) { 111 | if ($value['group'] == $pidOrGroupName) { 112 | $list[$key] = $value; 113 | } 114 | } 115 | } 116 | 117 | $sort = array_column($list, 'group'); 118 | array_multisort($sort, SORT_DESC, $list); 119 | foreach ($list as $key => $value) { 120 | unset($list[$key]); 121 | $list[$value['pid']] = $value; 122 | } 123 | return $this->clearPid($list); 124 | } 125 | 126 | public function addProcess(AbstractProcess $process, bool $autoRegister = true): Manager 127 | { 128 | $hash = spl_object_hash($process->getProcess()); 129 | $this->autoRegister[$hash] = $autoRegister; 130 | $this->processList[$hash] = $process; 131 | return $this; 132 | } 133 | 134 | public function attachToServer(Server $server) 135 | { 136 | foreach ($this->processList as $hash => $process) { 137 | if ($this->autoRegister[$hash] === true) { 138 | $server->addProcess($process->getProcess()); 139 | } 140 | } 141 | } 142 | 143 | public function pidExist(int $pid) 144 | { 145 | return Process::kill($pid, 0); 146 | } 147 | 148 | protected function clearPid(array $list) 149 | { 150 | foreach ($list as $pid => $value) { 151 | if (!$this->pidExist($pid)) { 152 | $this->table->del($pid); 153 | unset($list[$pid]); 154 | } 155 | } 156 | return $list; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/Process/Socket/AbstractTcpProcess.php: -------------------------------------------------------------------------------- 1 | setEnableCoroutine(true); 17 | if(empty($config->getListenPort())){ 18 | throw new Exception("listen port empty at class ".static::class); 19 | } 20 | parent::__construct($config); 21 | } 22 | 23 | public function run($arg) 24 | { 25 | $socket = new Socket(AF_INET,SOCK_STREAM,0); 26 | $socket->setOption(SOL_SOCKET,SO_REUSEPORT,true); 27 | $socket->setOption(SOL_SOCKET,SO_REUSEADDR,true); 28 | $socket->setOption(SOL_SOCKET,SO_LINGER,$this->getConfig()->getLinger()); 29 | $ret = $socket->bind($this->getConfig()->getListenAddress(),$this->getConfig()->getListenPort()); 30 | if(!$ret){ 31 | throw new Exception(static::class." bind {$this->getConfig()->getListenAddress()}:{$this->getConfig()->getListenPort()} fail case ".$socket->errMsg); 32 | } 33 | $ret = $socket->listen(2048); 34 | if(!$ret){ 35 | throw new Exception(static::class." listen {$this->getConfig()->getListenAddress()}:{$this->getConfig()->getListenPort()} fail case ".$socket->errMsg); 36 | } 37 | while (1){ 38 | $client = $socket->accept(-1); 39 | if(!$client){ 40 | return; 41 | } 42 | if($this->getConfig()->isAsyncCallback()){ 43 | Coroutine::create(function ()use($client){ 44 | try{ 45 | $this->onAccept($client); 46 | }catch (\Throwable $throwable){ 47 | $this->onException($throwable,$client); 48 | } 49 | }); 50 | }else{ 51 | try{ 52 | $this->onAccept($client); 53 | }catch (\Throwable $throwable){ 54 | $this->onException($throwable,$client); 55 | } 56 | } 57 | } 58 | } 59 | 60 | abstract function onAccept(Socket $socket); 61 | } -------------------------------------------------------------------------------- /src/Process/Socket/AbstractUnixProcess.php: -------------------------------------------------------------------------------- 1 | setEnableCoroutine(true); 17 | if(empty($config->getSocketFile())){ 18 | throw new Exception("socket file is empty at class ".static::class); 19 | } 20 | parent::__construct($config); 21 | } 22 | 23 | public function run($arg) 24 | { 25 | if (file_exists($this->getConfig()->getSocketFile())) 26 | { 27 | unlink($this->getConfig()->getSocketFile()); 28 | } 29 | $socketServer = new Socket(AF_UNIX,SOCK_STREAM,0); 30 | $socketServer->setOption(SOL_SOCKET,SO_LINGER,$this->getConfig()->getLinger()); 31 | if(!$socketServer->bind($this->getConfig()->getSocketFile())){ 32 | throw new Exception(static::class.' bind '.$this->getConfig()->getSocketFile(). ' fail case '.$socketServer->errMsg); 33 | } 34 | if(!$socketServer->listen(2048)){ 35 | throw new Exception(static::class.' listen '.$this->getConfig()->getSocketFile(). ' fail case '.$socketServer->errMsg); 36 | } 37 | while (1){ 38 | $client = $socketServer->accept(-1); 39 | if(!$client){ 40 | return; 41 | } 42 | if($this->getConfig()->isAsyncCallback()){ 43 | Coroutine::create(function ()use($client){ 44 | try{ 45 | $this->onAccept($client); 46 | }catch (\Throwable $throwable){ 47 | $this->onException($throwable,$client); 48 | } 49 | }); 50 | }else{ 51 | try{ 52 | $this->onAccept($client); 53 | }catch (\Throwable $throwable){ 54 | $this->onException($throwable,$client); 55 | } 56 | } 57 | } 58 | } 59 | 60 | abstract function onAccept(Socket $socket); 61 | } -------------------------------------------------------------------------------- /src/Process/Socket/TcpProcessConfig.php: -------------------------------------------------------------------------------- 1 | 0, 'l_onoff' => 0]; 15 | 16 | /** 17 | * @return int[] 18 | */ 19 | public function getLinger(): array 20 | { 21 | return $this->linger; 22 | } 23 | 24 | /** 25 | * @param int[] $linger 26 | */ 27 | public function setLinger(array $linger): void 28 | { 29 | $this->linger = $linger; 30 | } 31 | 32 | /** 33 | * @return string 34 | */ 35 | public function getListenAddress(): string 36 | { 37 | return $this->listenAddress; 38 | } 39 | 40 | /** 41 | * @param string $listenAddress 42 | */ 43 | public function setListenAddress(string $listenAddress): void 44 | { 45 | $this->listenAddress = $listenAddress; 46 | } 47 | 48 | /** 49 | * @return mixed 50 | */ 51 | public function getListenPort() 52 | { 53 | return $this->listenPort; 54 | } 55 | 56 | /** 57 | * @param mixed $listenPort 58 | */ 59 | public function setListenPort($listenPort): void 60 | { 61 | $this->listenPort = $listenPort; 62 | } 63 | /** 64 | * @return bool 65 | */ 66 | public function isAsyncCallback(): bool 67 | { 68 | return $this->asyncCallback; 69 | } 70 | 71 | /** 72 | * @param bool $asyncCallback 73 | */ 74 | public function setAsyncCallback(bool $asyncCallback): void 75 | { 76 | $this->asyncCallback = $asyncCallback; 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /src/Process/Socket/UnixProcessConfig.php: -------------------------------------------------------------------------------- 1 | 0, 'l_onoff' => 0]; 14 | 15 | /** 16 | * @return int[] 17 | */ 18 | public function getLinger(): array 19 | { 20 | return $this->linger; 21 | } 22 | 23 | /** 24 | * @param int[] $linger 25 | */ 26 | public function setLinger(array $linger): void 27 | { 28 | $this->linger = $linger; 29 | } 30 | 31 | /** 32 | * @return mixed 33 | */ 34 | public function getSocketFile() 35 | { 36 | return $this->socketFile; 37 | } 38 | 39 | /** 40 | * @param mixed $socketFile 41 | */ 42 | public function setSocketFile($socketFile): void 43 | { 44 | $this->socketFile = $socketFile; 45 | } 46 | 47 | /** 48 | * @return bool 49 | */ 50 | public function isAsyncCallback(): bool 51 | { 52 | return $this->asyncCallback; 53 | } 54 | 55 | /** 56 | * @param bool $asyncCallback 57 | */ 58 | public function setAsyncCallback(bool $asyncCallback): void 59 | { 60 | $this->asyncCallback = $asyncCallback; 61 | } 62 | } -------------------------------------------------------------------------------- /src/ReadyScheduler.php: -------------------------------------------------------------------------------- 1 | table = new Table(2048); 22 | $this->table->column('status',Table::TYPE_INT,1); 23 | $this->table->create(); 24 | } 25 | 26 | function addItem(string $key,int $status = self::STATUS_UNREADY):ReadyScheduler 27 | { 28 | $this->table->set($key,[ 29 | 'status'=>$status 30 | ]); 31 | return $this; 32 | } 33 | 34 | function status(string $key):?int 35 | { 36 | $ret = $this->table->get($key); 37 | if($ret){ 38 | return $ret['status']; 39 | }else{ 40 | return null; 41 | } 42 | } 43 | 44 | function ready(string $key,bool $force = false):ReadyScheduler 45 | { 46 | if($force){ 47 | $this->table->set($key,[ 48 | 'status'=>self::STATUS_READY 49 | ]); 50 | }else{ 51 | $this->table->incr($key,'status',1); 52 | } 53 | return $this; 54 | } 55 | 56 | function unready(string $key,bool $force = false):ReadyScheduler 57 | { 58 | if($force){ 59 | $this->table->set($key,[ 60 | 'status'=>self::STATUS_UNREADY 61 | ]); 62 | }else{ 63 | $this->table->decr($key,'status',1); 64 | } 65 | return $this; 66 | } 67 | 68 | function restore(string $key,?int $status):ReadyScheduler 69 | { 70 | $this->table->set($key,[ 71 | 'status'=>$status 72 | ]); 73 | return $this; 74 | } 75 | 76 | function waitReady($keys,float $time = 3.0):bool 77 | { 78 | if(!is_array($keys)){ 79 | $keys = [$keys]; 80 | }else if(empty($keys)){ 81 | return true; 82 | } 83 | while (1){ 84 | foreach ($keys as $key => $item){ 85 | if($this->status($item) >= self::STATUS_READY){ 86 | unset($keys[$key]); 87 | } 88 | if(count($keys) == 0){ 89 | return true; 90 | } 91 | if($time > 0){ 92 | $time = $time - 0.01; 93 | Coroutine::sleep(0.01); 94 | }else{ 95 | return false; 96 | } 97 | } 98 | } 99 | } 100 | 101 | function waitAnyReady(array $keys,float $timeout = 3.0):bool 102 | { 103 | if(empty($keys)){ 104 | return true; 105 | } 106 | while (1){ 107 | foreach ($keys as $key){ 108 | if($this->status($key) >= self::STATUS_READY){ 109 | return true; 110 | } 111 | } 112 | if($timeout > 0){ 113 | $timeout = $timeout - 0.01; 114 | Coroutine::sleep(0.01); 115 | }else{ 116 | return false; 117 | } 118 | } 119 | } 120 | 121 | } -------------------------------------------------------------------------------- /src/Singleton.php: -------------------------------------------------------------------------------- 1 | ['type'=>Table::TYPE_STRING,'size'=>1]] 24 | * @param int $size 25 | */ 26 | public function add($name,array $columns,$size = 1024):void 27 | { 28 | if(!isset($this->list[$name])){ 29 | $table = new Table($size); 30 | foreach ($columns as $column => $item){ 31 | $table->column($column,$item['type'],$item['size']); 32 | } 33 | $table->create(); 34 | $this->list[$name] = $table; 35 | } 36 | } 37 | 38 | public function get($name):?Table 39 | { 40 | if(isset($this->list[$name])){ 41 | return $this->list[$name]; 42 | }else{ 43 | return null; 44 | } 45 | } 46 | 47 | public function del($name) 48 | { 49 | if(isset($this->list[$name])){ 50 | $this->list[$name]->destroy(); 51 | unset($this->list[$name]); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Timer.php: -------------------------------------------------------------------------------- 1 | timerMap[md5($name)] = $id; 23 | } 24 | return $id; 25 | } 26 | 27 | function clear($timerIdOrName): bool 28 | { 29 | $tid = null; 30 | if(is_numeric($timerIdOrName)){ 31 | $tid = $timerIdOrName; 32 | }else if(isset($this->timerMap[md5($timerIdOrName)])){ 33 | $tid = $this->timerMap[md5($timerIdOrName)]; 34 | unset($this->timerMap[md5($timerIdOrName)]); 35 | } 36 | if($tid && SWTimer::info($tid)){ 37 | SWTimer::clear($tid); 38 | return true; 39 | } 40 | return false; 41 | } 42 | 43 | function clearAll(): bool 44 | { 45 | $this->timerMap = []; 46 | SWTimer::clearAll(); 47 | return true; 48 | } 49 | 50 | function after(int $ms, callable $callback, ...$params): int 51 | { 52 | return SWTimer::after($ms, $callback, ...$params); 53 | } 54 | 55 | function list():array 56 | { 57 | return SWTimer::list(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/WaitGroup.php: -------------------------------------------------------------------------------- 1 | size = $size; 21 | $this->reset(); 22 | } 23 | 24 | public function add(?callable $func = null) 25 | { 26 | $this->count++; 27 | if($func){ 28 | Coroutine::create(function ()use($func){ 29 | try{ 30 | call_user_func($func); 31 | }catch (\Throwable $throwable){ 32 | throw $throwable; 33 | }finally { 34 | $this->done(); 35 | } 36 | }); 37 | } 38 | } 39 | 40 | function successNum():int 41 | { 42 | return $this->success; 43 | } 44 | 45 | public function done() 46 | { 47 | $this->channel->push(1); 48 | } 49 | 50 | public function wait(?float $timeout = 15) 51 | { 52 | if($timeout <= 0){ 53 | $timeout = PHP_INT_MAX; 54 | } 55 | $this->success = 0; 56 | $left = $timeout; 57 | while(($this->count > 0) && ($left > 0)) 58 | { 59 | $start = round(microtime(true),3); 60 | if($this->channel->pop($left) === 1) 61 | { 62 | $this->count--; 63 | $this->success++; 64 | } 65 | $left = $left - (round(microtime(true),3) - $start); 66 | } 67 | } 68 | 69 | 70 | function reset() 71 | { 72 | $this->close(); 73 | $this->count = 0; 74 | $this->success = 0; 75 | $this->channel = new Channel($this->size); 76 | } 77 | 78 | function close() 79 | { 80 | if($this->channel){ 81 | $this->channel->close(); 82 | $this->channel = null; 83 | } 84 | } 85 | 86 | function __destruct() 87 | { 88 | $this->close(); 89 | } 90 | } --------------------------------------------------------------------------------