├── .github ├── CODEOWNERS ├── FUNDING.yml ├── boring-cyborg.yml ├── dependabot.yml ├── settings.yml └── workflows │ ├── ci.yml │ ├── craft-release.yaml │ └── set-milestone-on-pr.yaml ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── src ├── Factory │ ├── CpuCoreCountFixed.php │ ├── CpuCoreCountFlexible.php │ ├── Dummy.php │ ├── Fixed.php │ └── Flexible.php ├── Info.php ├── Launcher │ ├── ClassName.php │ └── Process.php ├── LauncherInterface.php ├── Manager │ ├── Fixed.php │ └── Flexible.php ├── ManagerInterface.php ├── MessengerCollectionInterface.php ├── Options.php ├── Pool │ ├── Dummy.php │ ├── Fixed.php │ └── Flexible.php ├── PoolFactoryInterface.php ├── PoolInfoInterface.php ├── PoolInterface.php ├── PoolUtilizerInterface.php ├── ProcessCollection │ ├── ArrayList.php │ └── Single.php ├── ProcessCollectionInterface.php ├── Queue │ ├── Memory.php │ └── Overflow.php ├── QueueInterface.php ├── StrategyInterface.php ├── Worker.php ├── WorkerInterface.php ├── functions.php └── functions_include.php └── tests ├── Factory ├── CpuCoreCountFixedTest.php ├── CpuCoreCountFlexibleTest.php ├── DummyTest.php ├── FixedTest.php └── FlexibleTest.php ├── FunctionsTest.php ├── InfoTest.php ├── Launcher └── ClassNameTest.php ├── Manager ├── FixedTest.php └── FlexibleTest.php ├── OptionsTest.php ├── Pool ├── DummyTest.php ├── FixedTest.php └── FlexibleTest.php ├── ProcessCollection ├── ArrayListTest.php └── SingleTest.php ├── Queue ├── MemoryTest.php ├── OverflowTest.php └── QueueTestTrait.php ├── TestCase.php └── WorkerTest.php /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @WyriHaximus 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: WyriHaximus -------------------------------------------------------------------------------- /.github/boring-cyborg.yml: -------------------------------------------------------------------------------- 1 | labelPRBasedOnFilePath: 2 | "Documentation 📚": 3 | - README.md 4 | - CONTRIBUTING.md 5 | "Dependencies 📦": 6 | - Dockerfile* 7 | - composer.* 8 | - package.json 9 | - package-lock.json 10 | - yarn.lock 11 | "Docker 🐳": 12 | - Dockerfile* 13 | - .docker/**/* 14 | "Image 🖼": 15 | - "**/*.gif" 16 | - "**/*.jpg" 17 | - "**/*.jpeg" 18 | - "**/*.png" 19 | - "**/*.webp" 20 | "CSS 👩‍🎨": 21 | - "**/*.css" 22 | "HTML 👷‍♀️": 23 | - "**/*.htm" 24 | - "**/*.html" 25 | "NEON 🦹‍♂️": 26 | - "**/*.neon" 27 | "MarkDown 📝": 28 | - "**/*.md" 29 | "YAML 🍄": 30 | - "**/*.yml" 31 | - "**/*.yaml" 32 | "JSON 👨‍💼": 33 | - "**/*.json" 34 | "Go 🐹": 35 | - "**/*.go" 36 | "JavaScript 🦏": 37 | - "**/*.js" 38 | - package.json 39 | - package-lock.json 40 | - yarn.lock 41 | "PHP 🐘": 42 | - "**/*.php" 43 | - composer.* 44 | "Configuration ⚙": 45 | - .github/* 46 | "CI 🚧": 47 | - .github/workflows/* 48 | - .scrutinizer.yml 49 | "Templates 🌲": 50 | - "**/*.twig" 51 | - "**/*.tpl" 52 | "Helm ☸": 53 | - .helm/**/* 54 | "Tests 🧪": 55 | - tests/**/* 56 | "Source 🔮": 57 | - src/**/* 58 | 59 | labelerFlags: 60 | labelOnPRUpdates: true 61 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "composer" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | labels: 8 | - "Dependencies 📦" 9 | - "PHP 🐘" 10 | versioning-strategy: "widen" 11 | open-pull-requests-limit: 1 12 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | repository: 2 | private: false 3 | has_issues: true 4 | has_wiki: false 5 | has_downloads: true 6 | default_branch: master 7 | allow_squash_merge: false 8 | allow_merge_commit: true 9 | allow_rebase_merge: false 10 | 11 | # Labels: define labels for Issues and Pull Requests 12 | labels: 13 | - name: "Dependencies 📦" 14 | color: 0025ff 15 | description: "Pull requests that update a dependency file" 16 | - name: "Image 🖼" 17 | color: 00ffff 18 | - name: "HTML 👷‍♀️" 19 | color: ffffff 20 | - name: "CSS 👩‍🎨" 21 | color: b3b3b3 22 | - name: "JavaScript 🦏" 23 | color: ffff00 24 | - name: "Go 🐹" 25 | color: 00ADD8 26 | - name: "JSON 👨‍💼" 27 | color: 00ADD8 28 | - name: "NEON 🦹‍♂️" 29 | color: CE3262 30 | - name: "MarkDown 📝" 31 | color: 000000 32 | - name: "YAML 🍄" 33 | color: ff1aff 34 | - name: "Templates 🌲" 35 | color: 009933 36 | - name: "Helm ☸" 37 | color: 091C84 38 | - name: "Tests 🧪" 39 | color: ffe6e6 40 | - name: "Source 🔮" 41 | color: e6ffe6 42 | - name: "Configuration ⚙" 43 | color: b3b3cc 44 | - name: "PHP 🐘" 45 | color: 8892BF 46 | description: "Hypertext Pre Processor" 47 | - name: "Docker 🐳" 48 | color: 0db7ed 49 | description: "Pull requests that relate to Docker" 50 | - name: "CI 🚧" 51 | color: ffff00 52 | - name: "Feature 🏗" 53 | color: 66ff99 54 | - name: "Documentation 📚" 55 | color: 6666ff 56 | - name: "Security 🕵️‍♀️" 57 | color: ff0000 58 | - name: "Hacktoberfest 🎃" 59 | color: 152347 60 | - name: "Bug 🐞" 61 | color: d73a4a 62 | description: "Something isn't working" 63 | oldname: bug 64 | - name: "Duplicate ♊" 65 | color: cfd3d7 66 | description: "This issue or pull request already exists" 67 | oldname: duplicate 68 | - name: "Enhancement ✨" 69 | color: a2eeef 70 | description: "New feature or request" 71 | oldname: enhancement 72 | - name: "Good First Issue" 73 | color: 7057ff 74 | description: "Good for newcomers" 75 | oldname: "good first issue" 76 | - name: "Help Wanted" 77 | color: 008672 78 | description: "Extra attention is needed" 79 | oldname: "help wanted" 80 | - name: Invalid 81 | color: e4e669 82 | description: "This doesn't seem right" 83 | oldname: invalid 84 | - name: "Question ❓" 85 | color: d876e3 86 | description: "Further information is requested" 87 | oldname: question 88 | - name: "Will not be fixed 🛑" 89 | color: ffffff 90 | description: "This will not be worked on" 91 | oldname: wontfix 92 | - name: "Sponsor Request ❤️" 93 | color: fedbf0 94 | description: "Issue/PR opened by sponsor" 95 | 96 | branches: 97 | - name: master 98 | protection: 99 | required_pull_request_reviews: 100 | required_approving_review_count: 1 101 | dismiss_stale_reviews: true 102 | require_code_owner_reviews: true 103 | # Required. Require status checks to pass before merging. Set to null to disable 104 | required_status_checks: 105 | # Required. Require branches to be up to date before merging. 106 | strict: true 107 | # Required. The list of status checks to require in order to merge into this branch 108 | contexts: [] 109 | # Required. Enforce all configured restrictions for administrators. Set to true to enforce required status checks for repository administrators. Set to null to disable. 110 | enforce_admins: true 111 | # Required. Restrict who can push to this branch. Team and user restrictions are only available for organization-owned repositories. Set to null to disable. 112 | restrictions: 113 | apps: [] 114 | users: [] 115 | teams: [] 116 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | pull_request: 5 | jobs: 6 | supported-versions-matrix: 7 | name: Supported Versions Matrix 8 | runs-on: ubuntu-latest 9 | outputs: 10 | version: ${{ steps.supported-versions-matrix.outputs.version }} 11 | steps: 12 | - uses: actions/checkout@v1 13 | - id: supported-versions-matrix 14 | uses: WyriHaximus/github-action-composer-php-versions-in-range@v1 15 | test: 16 | name: Run Tests on PHP ${{ matrix.php }} 17 | runs-on: ubuntu-latest 18 | needs: 19 | - supported-versions-matrix 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | php: ${{ fromJson(needs.supported-versions-matrix.outputs.version) }} 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v2 27 | - name: Setup PHP, extensions and composer with shivammathur/setup-php 28 | uses: shivammathur/setup-php@v2 29 | with: 30 | php-version: ${{ matrix.php }} 31 | coverage: xdebug, pcov 32 | - name: Get composer cache directory 33 | id: composer-cache 34 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 35 | - name: Cache dependencies 36 | uses: actions/cache@v2 37 | with: 38 | path: ${{ steps.composer-cache.outputs.dir }} 39 | key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.*') }} 40 | restore-keys: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.*') }} 41 | - name: Install Composer dependencies 42 | run: | 43 | composer config --unset platform.php 44 | rm composer.lock 45 | composer install --no-progress --no-interaction --optimize-autoloader --ansi 46 | - name: Test 47 | run: | 48 | ./vendor/bin/phpunit --coverage-text --debug 49 | benchmark: 50 | name: Run Benchmark on PHP ${{ matrix.php }} 51 | runs-on: ubuntu-latest 52 | needs: 53 | - supported-versions-matrix 54 | strategy: 55 | fail-fast: false 56 | matrix: 57 | php: ${{ fromJson(needs.supported-versions-matrix.outputs.version) }} 58 | steps: 59 | - name: Checkout 60 | uses: actions/checkout@v2 61 | - name: Setup PHP, extensions and composer with shivammathur/setup-php 62 | uses: shivammathur/setup-php@v2 63 | with: 64 | php-version: ${{ matrix.php }} 65 | coverage: xdebug, pcov 66 | - name: Get composer cache directory 67 | id: composer-cache 68 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 69 | - name: Cache dependencies 70 | uses: actions/cache@v2 71 | with: 72 | path: ${{ steps.composer-cache.outputs.dir }} 73 | key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.*') }} 74 | restore-keys: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.*') }} 75 | - name: Install Composer dependencies 76 | run: | 77 | composer config --unset platform.php 78 | rm composer.lock 79 | composer install --no-progress --no-interaction --optimize-autoloader --ansi 80 | - name: Benchmark 81 | run: | 82 | php benchmark/memory.php 83 | example: 84 | name: Run Examples on PHP ${{ matrix.php }} 85 | runs-on: ubuntu-latest 86 | needs: 87 | - supported-versions-matrix 88 | strategy: 89 | fail-fast: false 90 | matrix: 91 | php: ${{ fromJson(needs.supported-versions-matrix.outputs.version) }} 92 | steps: 93 | - name: Checkout 94 | uses: actions/checkout@v2 95 | - name: Setup PHP, extensions and composer with shivammathur/setup-php 96 | uses: shivammathur/setup-php@v2 97 | with: 98 | php-version: ${{ matrix.php }} 99 | coverage: xdebug, pcov 100 | - name: Get composer cache directory 101 | id: composer-cache 102 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 103 | - name: Cache dependencies 104 | uses: actions/cache@v2 105 | with: 106 | path: ${{ steps.composer-cache.outputs.dir }} 107 | key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.*') }} 108 | restore-keys: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.*') }} 109 | - name: Install Composer dependencies 110 | run: | 111 | composer config --unset platform.php 112 | rm composer.lock 113 | composer install --no-progress --no-interaction --optimize-autoloader --ansi 114 | - name: Example 115 | run: | 116 | php examples/return-class-messaging/ping.php 117 | -------------------------------------------------------------------------------- /.github/workflows/craft-release.yaml: -------------------------------------------------------------------------------- 1 | name: Create Release 2 | env: 3 | MILESTONE: ${{ github.event.milestone.title }} 4 | on: 5 | milestone: 6 | types: 7 | - closed 8 | jobs: 9 | generate-changelog: 10 | name: Generate Changelog 11 | runs-on: ubuntu-latest 12 | outputs: 13 | changelog: ${{ steps.changelog.outputs.changelog }} 14 | steps: 15 | - name: Generate changelog 16 | uses: WyriHaximus/github-action-jwage-changelog-generator@v1 17 | id: changelog 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | with: 21 | milestone: ${{ env.MILESTONE }} 22 | - name: Show changelog 23 | run: echo "${CHANGELOG}" 24 | env: 25 | CHANGELOG: ${{ steps.changelog.outputs.changelog }} 26 | create-release: 27 | name: Create Release 28 | needs: 29 | - generate-changelog 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v1 33 | env: 34 | CHANGELOG: ${{ needs.generate-changelog.outputs.changelog }} 35 | - run: | 36 | echo -e "${MILESTONE_DESCRIPTION}\r\n\r\n${CHANGELOG}" > release-${{ env.MILESTONE }}-release-message.md 37 | cat release-${{ env.MILESTONE }}-release-message.md 38 | release_message=$(cat release-${{ env.MILESTONE }}-release-message.md) 39 | release_message="${release_message//'%'/'%25'}" 40 | release_message="${release_message//$'\n'/'%0A'}" 41 | release_message="${release_message//$'\r'/'%0D'}" 42 | echo "::set-output name=release_message::$release_message" 43 | id: releasemessage 44 | env: 45 | MILESTONE_DESCRIPTION: ${{ github.event.milestone.description }} 46 | CHANGELOG: ${{ needs.generate-changelog.outputs.changelog }} 47 | - name: Create Reference Release with Changelog 48 | uses: fleskesvor/create-release@feature/support-target-commitish 49 | env: 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 51 | with: 52 | tag_name: ${{ env.MILESTONE }} 53 | release_name: ${{ env.MILESTONE }} 54 | body: ${{ steps.releasemessage.outputs.release_message }} 55 | draft: false 56 | prerelease: false 57 | -------------------------------------------------------------------------------- /.github/workflows/set-milestone-on-pr.yaml: -------------------------------------------------------------------------------- 1 | name: Set Milestone 2 | on: 3 | pull_request: 4 | types: 5 | - assigned 6 | - opened 7 | - synchronize 8 | - reopened 9 | - edited 10 | - ready_for_review 11 | - review_requested 12 | jobs: 13 | set-milestone: 14 | name: Set Milestone 15 | if: github.event.pull_request.milestone == null 16 | runs-on: ubuntu-latest 17 | outputs: 18 | check: ${{ steps.generate-checks-strategy.outputs.check }} 19 | steps: 20 | - uses: actions/checkout@v1 21 | - name: 'Get Previous tag' 22 | id: previoustag 23 | uses: "WyriHaximus/github-action-get-previous-tag@v1" 24 | env: 25 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 26 | - name: 'Get next versions' 27 | id: semvers 28 | uses: "WyriHaximus/github-action-next-semvers@v1" 29 | with: 30 | version: ${{ steps.previoustag.outputs.tag }} 31 | - name: 'Decide which version fits this PR' 32 | id: decidedversion 33 | run: | 34 | if [ "$(jq '.sender.id' -r ${GITHUB_EVENT_PATH})" = "49699333" ]; then 35 | printf "::set-output name=version::%s" "${PATCH}" 36 | exit 0 37 | fi 38 | 39 | composer install --no-progress --ansi --no-interaction --prefer-dist -o -q 40 | 41 | if ! (./vendor/bin/roave-backward-compatibility-check); then 42 | printf "::set-output name=version::%s" "${MAJOR}" 43 | exit 0 44 | fi 45 | 46 | printf "::set-output name=version::%s" "${MINOR}" 47 | env: 48 | MAJOR: ${{ steps.semvers.outputs.major }} 49 | MINOR: ${{ steps.semvers.outputs.minor }} 50 | PATCH: ${{ steps.semvers.outputs.patch }} 51 | - name: 'Get Milestones' 52 | uses: "WyriHaximus/github-action-get-milestones@master" 53 | id: milestones 54 | env: 55 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 56 | - run: printf "::set-output name=number::%s" $(printenv MILESTONES | jq --arg MILESTONE $(printenv MILESTONE) '.[] | select(.title == $MILESTONE) | .number') 57 | id: querymilestone 58 | env: 59 | MILESTONES: ${{ steps.milestones.outputs.milestones }} 60 | MILESTONE: ${{ steps.decidedversion.outputs.version }} 61 | - name: 'Create Milestone' 62 | if: steps.querymilestone.outputs.number == '' 63 | id: createmilestone 64 | uses: "WyriHaximus/github-action-create-milestone@0.1.0" 65 | with: 66 | title: ${{ steps.decidedversion.outputs.version }} 67 | env: 68 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 69 | - name: 'Select found or created Milestone' 70 | id: selectmilestone 71 | run: | 72 | if [ $(echo ${QUERY_NUMBER} | wc -c) -eq 1 ] ; then 73 | printf "::set-output name=number::%s" "${CREATED_NUMBER}" 74 | exit 0 75 | fi 76 | 77 | printf "::set-output name=number::%s" "${QUERY_NUMBER}" 78 | env: 79 | CREATED_NUMBER: ${{ steps.createmilestone.outputs.number }} 80 | QUERY_NUMBER: ${{ steps.querymilestone.outputs.number }} 81 | - name: 'Set Milestone' 82 | uses: "WyriHaximus/github-action-set-milestone@master" 83 | with: 84 | issue_number: ${{ github.event.pull_request.number }} 85 | milestone_number: ${{ steps.selectmilestone.outputs.number }} 86 | env: 87 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 88 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Cees-Jan Kiewiet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pool wyrihaximus/react-child-process-messenger processes 2 | 3 | [![Linux Build Status](https://travis-ci.org/WyriHaximus/reactphp-child-process-pool.png)](https://travis-ci.org/WyriHaximus/reactphp-child-process-pool) 4 | [![Latest Stable Version](https://poser.pugx.org/WyriHaximus/react-child-process-pool/v/stable.png)](https://packagist.org/packages/WyriHaximus/react-child-process-pool) 5 | [![Total Downloads](https://poser.pugx.org/wyrihaximus/react-child-process-pool/downloads.png)](https://packagist.org/packages/wyrihaximus/react-child-process-pool) 6 | [![Code Coverage](https://scrutinizer-ci.com/g/WyriHaximus/reactphp-child-process-pool/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/WyriHaximus/react-child-process-pool/?branch=master) 7 | [![License](https://poser.pugx.org/wyrihaximus/react-child-process-pool/license.png)](https://packagist.org/packages/wyrihaximus/react-child-process-pool) 8 | [![PHP 7 ready](http://php7ready.timesplinter.ch/WyriHaximus/reactphp-child-process-pool/badge.svg)](https://travis-ci.org/WyriHaximus/reactphp-child-process-pool) 9 | 10 | ## Installation ## 11 | 12 | To install via [Composer](http://getcomposer.org/), use the command below, it will automatically detect the latest version and bind it with `~`. 13 | 14 | ``` 15 | composer require wyrihaximus/react-child-process-pool 16 | ``` 17 | 18 | ## Pools ## 19 | 20 | * `Dummy` - Meant for testing, doesn't do anything but complies to it's contract 21 | * `Fixed` - Spawns a given fixed amount of workers 22 | * `Flexible` - Spawns workers as a needed basis, given a minimum and maximum it will spawn within those values 23 | 24 | ## Usage ## 25 | 26 | This package pools [`wyrihaximus/react-child-process-messenger`](https://github.com/WyriHaximus/reactphp-child-process-messenger), for basic messaging please see that package for details how to use it. 27 | 28 | ## Creating a pool ## 29 | 30 | This package ships with a set factories, which create different pools. (All the options in the following examples are the default options.) 31 | 32 | ### Dummy ### 33 | 34 | Creates a `Dummy` pool: 35 | 36 | ```php 37 | $loop = EventLoopFactory::create(); 38 | 39 | Dummy::createFromClass(ReturnChild::class, $loop)->then(function (PoolInterface $pool) { 40 | // Now you have a Dummy pool, which does absolutely nothing 41 | }); 42 | ``` 43 | 44 | ### Fixed ### 45 | 46 | Creates a `Fixed` pool: 47 | 48 | ```php 49 | $loop = EventLoopFactory::create(); 50 | $options = [ 51 | Options::SIZE => 5, 52 | ]; 53 | Fixed::createFromClass(ReturnChild::class, $loop, $options)->then(function (PoolInterface $pool) { 54 | // You now have a pull with 5 always running child processes 55 | }); 56 | ``` 57 | 58 | ### Flexible ### 59 | 60 | Creates a `Flexible` pool: 61 | 62 | ```php 63 | $loop = EventLoopFactory::create(); 64 | $options = [ 65 | Options::MIN_SIZE => 0, 66 | Options::MAX_SIZE => 5, 67 | Options::TTL => 0, 68 | ]; 69 | Flexible::createFromClass(ReturnChild::class, $loop, $options)->then(function (PoolInterface $pool) { 70 | // You now have a pool that spawns no child processes on start. 71 | // But when you call rpc a new child process will be started for 72 | // as long as the pool has work in the queue. With a maximum of five. 73 | }); 74 | ``` 75 | 76 | ### CpuCoreCountFixed ### 77 | 78 | Creates a `Fixed` pool with size set to the number of CPU cores: 79 | 80 | ```php 81 | $loop = EventLoopFactory::create(); 82 | 83 | CpuCoreCountFlexible::createFromClass(ReturnChild::class, $loop)->then(function (PoolInterface $pool) { 84 | // You now have a Fixed pool with a child process assigned to each CPU core. 85 | }); 86 | ``` 87 | 88 | ### CpuCoreCountFlexible ### 89 | 90 | The following example will creates a flexible pool with max size set to the number of CPU cores. Where the `create` method requires you to give it a `React\ChildProcess\Process`. The `createFromClass` lets you pass a classname of a class implementing [`WyriHaximus\React\ChildProcess\Messenger\ChildInterface`](https://github.com/WyriHaximus/reactphp-child-process-messenger/blob/master/src/ChildInterface.php) that will be used as the worker in the client. Take a look at [`WyriHaximus\React\ChildProcess\Messenger\ReturnChild`](https://github.com/WyriHaximus/reactphp-child-process-messenger/blob/master/src/ReturnChild.php) to see how that works. 91 | 92 | ```php 93 | $loop = EventLoopFactory::create(); 94 | 95 | CpuCoreCountFlexible::createFromClass(ReturnChild::class, $loop)->then(function (PoolInterface $pool) { 96 | // You now have a Fixed pool with a child process assigned to each CPU core, 97 | // which, just like the Flexible pool, will only run when there is something 98 | // in the queue. 99 | }); 100 | ``` 101 | 102 | ## License ## 103 | 104 | Copyright 2017 [Cees-Jan Kiewiet](http://wyrihaximus.net/) 105 | 106 | Permission is hereby granted, free of charge, to any person 107 | obtaining a copy of this software and associated documentation 108 | files (the "Software"), to deal in the Software without 109 | restriction, including without limitation the rights to use, 110 | copy, modify, merge, publish, distribute, sublicense, and/or sell 111 | copies of the Software, and to permit persons to whom the 112 | Software is furnished to do so, subject to the following 113 | conditions: 114 | 115 | The above copyright notice and this permission notice shall be 116 | included in all copies or substantial portions of the Software. 117 | 118 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 119 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 120 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 121 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 122 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 123 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 124 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 125 | OTHER DEALINGS IN THE SOFTWARE. 126 | 127 | 128 | ### Contributors beyond the commit log 129 | * Gabi Davila - Helping test if my github token will be secure for pull requests on AppVeyor 130 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wyrihaximus/react-child-process-pool", 3 | "description": "Pool wyrihaximus/react-child-process-messenger processes", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Cees-Jan Kiewiet", 8 | "email": "ceesjank@gmail.com" 9 | } 10 | ], 11 | "require": { 12 | "php": "^8.0 || ^7.0 || ^5.4", 13 | "evenement/evenement": "^3.0 || ^2.0", 14 | "react/event-loop": "^1.1", 15 | "wyrihaximus/cpu-core-detector": "^2 || ^1.0.2", 16 | "wyrihaximus/file-descriptors": "^2 || ^1.0 || ^0.1", 17 | "wyrihaximus/react-child-process-messenger": "^4 || ^3 || ^2.10.1", 18 | "wyrihaximus/ticking-promise": "^2 || ^1.5" 19 | }, 20 | "require-dev": { 21 | "clue/block-react": "^1.3", 22 | "phake/phake": "^2.2.1", 23 | "phpunit/phpunit": "^4.8.35||^5.0||^9.5", 24 | "squizlabs/php_codesniffer": "^3.3.2", 25 | "vectorface/dunit": "~2.0" 26 | }, 27 | "suggest" : { 28 | "wyrihaximus/react-child-process-pool-redis-queue": "Redis RPC queue" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "WyriHaximus\\React\\ChildProcess\\Pool\\": "src/" 33 | }, 34 | "files": ["src/functions_include.php"] 35 | }, 36 | "autoload-dev": { 37 | "psr-4": { 38 | "WyriHaximus\\React\\Tests\\ChildProcess\\Pool\\": "tests/" 39 | } 40 | }, 41 | "config": { 42 | "sort-packages": true, 43 | "platform": { 44 | "php": "5.4.33" 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Factory/CpuCoreCountFixed.php: -------------------------------------------------------------------------------- 1 | 25, 30 | ]; 31 | 32 | /** 33 | * @param ChildProcess $process 34 | * @param LoopInterface $loop 35 | * @param array $options 36 | * @return PromiseInterface 37 | */ 38 | public static function create(ChildProcess $childProcess, LoopInterface $loop, array $options = []) 39 | { 40 | $options = array_merge(self::$defaultOptions, $options); 41 | try { 42 | if (!isset($options[Options::FD_LISTER])) { 43 | $options[Options::FD_LISTER] = Factory::create(); 44 | } 45 | } catch (NoCompatibleListerException $exception) { 46 | // Do nothing, platform unsupported 47 | } 48 | return \WyriHaximus\React\ChildProcess\Pool\detectCoreCount( 49 | $loop, 50 | $options 51 | )->then(function ($coreCount) use ($childProcess, $loop, $options) { 52 | $options[Options::SIZE] = $coreCount; 53 | $processes = []; 54 | for ($i = 0; $i < $coreCount; $i++) { 55 | $processes[] = \WyriHaximus\React\ChildProcess\Pool\rebuildProcess( 56 | $i, 57 | $childProcess 58 | )->then(function (ChildProcess $process) { 59 | return \React\Promise\resolve(new Process($process)); 60 | }); 61 | } 62 | 63 | return \React\Promise\all($processes)->then(function ($processes) use ($loop, $options) { 64 | return \React\Promise\resolve(new Fixed(new ArrayList($processes), $loop, $options)); 65 | }); 66 | }); 67 | } 68 | 69 | /** 70 | * @param string $class 71 | * @param LoopInterface $loop 72 | * @param array $options 73 | * @return PromiseInterface 74 | */ 75 | public static function createFromClass($class, LoopInterface $loop, array $options = []) 76 | { 77 | $options = array_merge(self::$defaultOptions, $options); 78 | try { 79 | if (!isset($options[Options::FD_LISTER])) { 80 | $options[Options::FD_LISTER] = Factory::create(); 81 | } 82 | } catch (NoCompatibleListerException $exception) { 83 | // Do nothing, platform unsupported 84 | } 85 | return \WyriHaximus\React\ChildProcess\Pool\detectCoreCount( 86 | $loop, 87 | $options 88 | )->then(function ($coreCount) use ($class, $loop, $options) { 89 | $options[Options::SIZE] = $coreCount; 90 | $processes = []; 91 | for ($i = 0; $i < $coreCount; $i++) { 92 | $processes[] = Resolver::resolve($i, '%s')->then(function ($command) use ($class, $options) { 93 | $classNameOptions = [ 94 | 'cmdTemplate' => $command, 95 | ]; 96 | 97 | if (isset($options[Options::FD_LISTER])) { 98 | $classNameOptions[Options::FD_LISTER] = $options[Options::FD_LISTER]; 99 | } 100 | 101 | return \React\Promise\resolve(new ClassName($class, $classNameOptions)); 102 | }); 103 | } 104 | 105 | return \React\Promise\all($processes)->then(function ($processes) use ($loop, $options) { 106 | return \React\Promise\resolve(new Fixed(new ArrayList($processes), $loop, $options)); 107 | }); 108 | }); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Factory/CpuCoreCountFlexible.php: -------------------------------------------------------------------------------- 1 | 0, 30 | Options::MAX_SIZE => 1, 31 | Options::TTL => 0, 32 | ]; 33 | 34 | /** 35 | * @param ChildProcess $process 36 | * @param LoopInterface $loop 37 | * @param array $options 38 | * @return PromiseInterface 39 | */ 40 | public static function create(ChildProcess $childProcess, LoopInterface $loop, array $options = []) 41 | { 42 | $options = array_merge(self::$defaultOptions, $options); 43 | try { 44 | if (!isset($options[Options::FD_LISTER])) { 45 | $options[Options::FD_LISTER] = Factory::create(); 46 | } 47 | } catch (NoCompatibleListerException $exception) { 48 | // Do nothing, platform unsupported 49 | } 50 | return \WyriHaximus\React\ChildProcess\Pool\detectCoreCount( 51 | $loop, 52 | $options 53 | )->then(function ($coreCount) use ($childProcess, $loop, $options) { 54 | $options[Options::MAX_SIZE] = $coreCount; 55 | $processes = []; 56 | for ($i = 0; $i < $coreCount; $i++) { 57 | $processes[] = \WyriHaximus\React\ChildProcess\Pool\rebuildProcess( 58 | $i, 59 | $childProcess 60 | )->then(function (ChildProcess $process) { 61 | return \React\Promise\resolve(new Process($process)); 62 | }); 63 | } 64 | 65 | return \React\Promise\all($processes)->then(function ($processes) use ($loop, $options) { 66 | return \React\Promise\resolve(new Flexible(new ArrayList($processes), $loop, $options)); 67 | }); 68 | }); 69 | } 70 | 71 | /** 72 | * @param string $class 73 | * @param LoopInterface $loop 74 | * @param array $options 75 | * @return PromiseInterface 76 | */ 77 | public static function createFromClass($class, LoopInterface $loop, array $options = []) 78 | { 79 | $options = array_merge(self::$defaultOptions, $options); 80 | try { 81 | if (!isset($options[Options::FD_LISTER])) { 82 | $options[Options::FD_LISTER] = Factory::create(); 83 | } 84 | } catch (NoCompatibleListerException $exception) { 85 | // Do nothing, platform unsupported 86 | } 87 | return \WyriHaximus\React\ChildProcess\Pool\detectCoreCount( 88 | $loop, 89 | $options 90 | )->then(function ($coreCount) use ($class, $loop, $options) { 91 | $options[Options::MAX_SIZE] = $coreCount; 92 | $processes = []; 93 | for ($i = 0; $i < $coreCount; $i++) { 94 | $processes[] = Resolver::resolve($i, '%s')->then(function ($command) use ($class, $options) { 95 | $classNameOptions = [ 96 | 'cmdTemplate' => $command, 97 | ]; 98 | 99 | if (isset($options[Options::FD_LISTER])) { 100 | $classNameOptions[Options::FD_LISTER] = $options[Options::FD_LISTER]; 101 | } 102 | 103 | return \React\Promise\resolve(new ClassName($class, $classNameOptions)); 104 | }); 105 | } 106 | 107 | return \React\Promise\all($processes)->then(function ($processes) use ($loop, $options) { 108 | return \React\Promise\resolve(new Flexible(new ArrayList($processes), $loop, $options)); 109 | }); 110 | }); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/Factory/Dummy.php: -------------------------------------------------------------------------------- 1 | 5, 24 | ]; 25 | 26 | /** 27 | * @param ChildProcess $process 28 | * @param LoopInterface $loop 29 | * @param array $options 30 | * @return PromiseInterface 31 | */ 32 | public static function create(ChildProcess $process, LoopInterface $loop, array $options = []) 33 | { 34 | $options = array_merge(self::$defaultOptions, $options); 35 | try { 36 | if (!isset($options[Options::FD_LISTER])) { 37 | $options[Options::FD_LISTER] = Factory::create(); 38 | } 39 | } catch (NoCompatibleListerException $exception) { 40 | // Do nothing, platform unsupported 41 | } 42 | return \React\Promise\resolve(new FixedPool(new Single(new Process($process)), $loop, $options)); 43 | } 44 | 45 | /** 46 | * @param string $class 47 | * @param LoopInterface $loop 48 | * @param array $options 49 | * @return PromiseInterface 50 | */ 51 | public static function createFromClass($class, LoopInterface $loop, array $options = []) 52 | { 53 | $options = array_merge(self::$defaultOptions, $options); 54 | try { 55 | if (!isset($options[Options::FD_LISTER])) { 56 | $options[Options::FD_LISTER] = Factory::create(); 57 | } 58 | } catch (NoCompatibleListerException $exception) { 59 | // Do nothing, platform unsupported 60 | } 61 | return \React\Promise\resolve(new FixedPool(new Single(new ClassName($class)), $loop, $options)); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Factory/Flexible.php: -------------------------------------------------------------------------------- 1 | 0, 24 | Options::MAX_SIZE => 5, 25 | Options::TTL => 0, 26 | ]; 27 | 28 | /** 29 | * @param ChildProcess $process 30 | * @param LoopInterface $loop 31 | * @param array $options 32 | * @return PromiseInterface 33 | */ 34 | public static function create(ChildProcess $process, LoopInterface $loop, array $options = []) 35 | { 36 | $options = array_merge(self::$defaultOptions, $options); 37 | try { 38 | if (!isset($options[Options::FD_LISTER])) { 39 | $options[Options::FD_LISTER] = Factory::create(); 40 | } 41 | } catch (NoCompatibleListerException $exception) { 42 | // Do nothing, platform unsupported 43 | } 44 | return \React\Promise\resolve(new FlexiblePool(new Single(new Process($process)), $loop, $options)); 45 | } 46 | 47 | /** 48 | * @param string $class 49 | * @param LoopInterface $loop 50 | * @param array $options 51 | * @return PromiseInterface 52 | */ 53 | public static function createFromClass($class, LoopInterface $loop, array $options = []) 54 | { 55 | $options = array_merge(self::$defaultOptions, $options); 56 | try { 57 | if (!isset($options[Options::FD_LISTER])) { 58 | $options[Options::FD_LISTER] = Factory::create(); 59 | } 60 | } catch (NoCompatibleListerException $exception) { 61 | // Do nothing, platform unsupported 62 | } 63 | return \React\Promise\resolve(new FlexiblePool(new Single(new ClassName($class)), $loop, $options)); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Info.php: -------------------------------------------------------------------------------- 1 | className = $className; 28 | $this->overrideOptions = $overrideOptions; 29 | } 30 | 31 | /** 32 | * @param LoopInterface $loop 33 | * @param array $options 34 | * @return PromiseInterface 35 | * @throws \Exception 36 | */ 37 | public function __invoke(LoopInterface $loop, array $options) 38 | { 39 | $options = array_merge($options, $this->overrideOptions); 40 | return Factory::parentFromClass($this->className, $loop, $options); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Launcher/Process.php: -------------------------------------------------------------------------------- 1 | process = $process; 24 | } 25 | 26 | /** 27 | * @param LoopInterface $loop 28 | * @param array $options 29 | * @return PromiseInterface 30 | * @throws \Exception 31 | */ 32 | public function __invoke(LoopInterface $loop, array $options) 33 | { 34 | return Factory::parent(clone $this->process, $loop, $options); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/LauncherInterface.php: -------------------------------------------------------------------------------- 1 | loop = $loop; 34 | $processCollection->rewind(); 35 | for ($i = 0; $i < $options[Options::SIZE]; $i++) { 36 | $this->spawn($processCollection, $options); 37 | } 38 | } 39 | 40 | protected function spawn($processCollection, $options) 41 | { 42 | $workerDone = function (WorkerInterface $worker) { 43 | $this->workerAvailable($worker); 44 | }; 45 | $current = $processCollection->current(); 46 | $promise = $this->spawnAndGetMessenger($current, $options); 47 | $promise->then(function (MessengerInterface $messenger) use ($workerDone) { 48 | $worker = new Worker($messenger); 49 | $this->workers[] = $worker; 50 | $worker->on('done', $workerDone); 51 | $worker->on('message', function ($message) { 52 | $this->emit('message', [$message]); 53 | }); 54 | $worker->on('error', function ($error) { 55 | $this->emit('error', [$error]); 56 | }); 57 | $this->workerAvailable($worker); 58 | }); 59 | 60 | $processCollection->next(); 61 | if (!$processCollection->valid()) { 62 | $processCollection->rewind(); 63 | } 64 | } 65 | 66 | protected function spawnAndGetMessenger(callable $current, $options) 67 | { 68 | return $current($this->loop, $options)->then(function ($timeoutOrMessenger) use ($current, $options) { 69 | if ($timeoutOrMessenger instanceof Messenger) { 70 | return \React\Promise\resolve($timeoutOrMessenger); 71 | } 72 | 73 | return $this->spawnAndGetMessenger($current, $options); 74 | }); 75 | } 76 | 77 | protected function workerAvailable(WorkerInterface $worker) 78 | { 79 | $this->emit('ready', [$worker]); 80 | } 81 | 82 | public function ping() 83 | { 84 | foreach ($this->workers as $worker) { 85 | if (!$worker->isBusy()) { 86 | $this->workerAvailable($worker); 87 | } 88 | } 89 | } 90 | 91 | public function message(Message $message) 92 | { 93 | foreach ($this->workers as $worker) { 94 | $worker->message($message); 95 | } 96 | } 97 | 98 | public function terminate() 99 | { 100 | $promises = []; 101 | 102 | foreach ($this->workers as $worker) { 103 | $promises[] = $worker->terminate(); 104 | } 105 | 106 | return \React\Promise\all($promises); 107 | } 108 | 109 | public function info() 110 | { 111 | $count = count($this->workers); 112 | $busy = 0; 113 | foreach ($this->workers as $worker) { 114 | if ($worker->isBusy()) { 115 | $busy++; 116 | } 117 | } 118 | return [ 119 | Info::TOTAL => $count, 120 | Info::BUSY => $busy, 121 | Info::IDLE => $count - $busy, 122 | ]; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/Manager/Flexible.php: -------------------------------------------------------------------------------- 1 | 0, 45 | Options::MAX_SIZE => 4, 46 | ]; 47 | 48 | /** 49 | * @var int 50 | */ 51 | protected $startingProcesses = 0; 52 | 53 | public function __construct(ProcessCollectionInterface $processCollection, LoopInterface $loop, array $options = []) 54 | { 55 | $this->processCollection = $processCollection; 56 | $this->loop = $loop; 57 | $this->options = array_merge($this->defaultOptions, $options); 58 | 59 | for ($i = 0; $i < $this->options[Options::MIN_SIZE]; $i++) { 60 | $this->spawn(); 61 | } 62 | } 63 | 64 | protected function workerAvailable(WorkerInterface $worker) 65 | { 66 | $this->emit('ready', [$worker]); 67 | } 68 | 69 | protected function spawn() 70 | { 71 | $this->startingProcesses++; 72 | $current = $this->processCollection->current(); 73 | $promise = $this->spawnAndGetMessenger($current); 74 | $promise->done(function (MessengerInterface $messenger) { 75 | $worker = new Worker($messenger); 76 | $this->workers[] = $worker; 77 | $worker->on('done', function (WorkerInterface $worker) { 78 | $this->workerAvailable($worker); 79 | }); 80 | $worker->on('terminating', function (WorkerInterface $worker) { 81 | foreach ($this->workers as $key => $value) { 82 | if ($worker === $value) { 83 | unset($this->workers[$key]); 84 | break; 85 | } 86 | } 87 | }); 88 | $worker->on('message', function ($message) { 89 | $this->emit('message', [$message]); 90 | }); 91 | $worker->on('error', function ($error) { 92 | $this->emit('error', [$error]); 93 | }); 94 | $this->workerAvailable($worker); 95 | $this->startingProcesses--; 96 | }, function () { 97 | $this->ping(); 98 | }); 99 | 100 | $this->processCollection->next(); 101 | if (!$this->processCollection->valid()) { 102 | $this->processCollection->rewind(); 103 | } 104 | } 105 | 106 | protected function spawnAndGetMessenger(callable $current) 107 | { 108 | return $current($this->loop, $this->options)->then(function ($timeoutOrMessenger) use ($current) { 109 | if ($timeoutOrMessenger instanceof Messenger) { 110 | return \React\Promise\resolve($timeoutOrMessenger); 111 | } 112 | 113 | return $this->spawnAndGetMessenger($current); 114 | }); 115 | } 116 | 117 | public function ping() 118 | { 119 | foreach ($this->workers as $worker) { 120 | if (!$worker->isBusy()) { 121 | $this->workerAvailable($worker); 122 | return; 123 | } 124 | } 125 | 126 | if (count($this->workers) + $this->startingProcesses < $this->options[Options::MIN_SIZE]) { 127 | $this->spawn(); 128 | return; 129 | } 130 | 131 | if (count($this->workers) + $this->startingProcesses < $this->options[Options::MAX_SIZE]) { 132 | $this->spawn(); 133 | } 134 | } 135 | 136 | public function message(Message $message) 137 | { 138 | foreach ($this->workers as $worker) { 139 | $worker->message($message); 140 | } 141 | } 142 | 143 | public function terminate() 144 | { 145 | $promises = []; 146 | 147 | foreach ($this->workers as $worker) { 148 | $promises[] = $worker->terminate(); 149 | } 150 | 151 | return \React\Promise\all($promises); 152 | } 153 | 154 | public function info() 155 | { 156 | $count = count($this->workers); 157 | $busy = 0; 158 | foreach ($this->workers as $worker) { 159 | if ($worker->isBusy()) { 160 | $busy++; 161 | } 162 | } 163 | return [ 164 | 'total' => $count, 165 | 'busy' => $busy, 166 | 'idle' => $count - $busy, 167 | ]; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/ManagerInterface.php: -------------------------------------------------------------------------------- 1 | loop = $loop; 55 | $this->options = $options; 56 | $this->queue = \WyriHaximus\React\ChildProcess\Pool\getQueue( 57 | $this->options, 58 | 'WyriHaximus\React\ChildProcess\Pool\Queue\Memory', 59 | $loop 60 | ); 61 | $this->manager = \WyriHaximus\React\ChildProcess\Pool\getManager( 62 | $this->options, 63 | $processCollection, 64 | 'WyriHaximus\React\ChildProcess\Pool\Manager\Fixed', 65 | $loop 66 | ); 67 | $this->manager->on('ready', function (WorkerInterface $worker) { 68 | $this->emit('worker', [$worker]); 69 | if ($this->queue->count() === 0) { 70 | return; 71 | } 72 | 73 | \React\Promise\resolve($this->queue->dequeue())->then(function (Rpc $message) use ($worker) { 74 | $hash = spl_object_hash($message); 75 | $this->deferreds[$hash]->resolve($worker->rpc($message)); 76 | unset($this->deferreds[$hash]); 77 | }); 78 | }); 79 | $this->manager->on('message', function ($message) { 80 | $this->emit('message', [$message]); 81 | }); 82 | $this->manager->on('error', function ($error) { 83 | $this->emit('error', [$error]); 84 | }); 85 | } 86 | 87 | /** 88 | * @param Rpc $message 89 | * @return PromiseInterface 90 | */ 91 | public function rpc(Rpc $message) 92 | { 93 | $hash = spl_object_hash($message); 94 | $deferred = new Deferred(); 95 | $this->deferreds[$hash] = $deferred; 96 | $this->queue->enqueue($message); 97 | $this->manager->ping(); 98 | return $deferred->promise(); 99 | } 100 | 101 | /** 102 | * @param Message $message 103 | */ 104 | public function message(Message $message) 105 | { 106 | $this->manager->message($message); 107 | } 108 | 109 | /** 110 | * @param Message|null $message 111 | * @param int $timeout 112 | * @param null $signal 113 | * @return PromiseInterface 114 | */ 115 | public function terminate(Message $message = null, $timeout = 5, $signal = null) 116 | { 117 | if ($message !== null) { 118 | $this->message($message); 119 | } 120 | 121 | return \WyriHaximus\React\timedPromise($this->loop, $timeout)->then(function () { 122 | $this->manager->removeAllListeners(); 123 | return $this->manager->terminate(); 124 | }); 125 | } 126 | 127 | /** 128 | * @return array 129 | */ 130 | public function info() 131 | { 132 | $workers = $this->manager->info(); 133 | return [ 134 | Info::BUSY => $workers[Info::BUSY], 135 | Info::CALLS => $this->queue->count(), 136 | Info::IDLE => $workers[Info::IDLE], 137 | Info::SIZE => $workers[Info::TOTAL], 138 | ]; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/Pool/Flexible.php: -------------------------------------------------------------------------------- 1 | loop = $loop; 58 | $this->options = $options; 59 | $this->queue = \WyriHaximus\React\ChildProcess\Pool\getQueue( 60 | $this->options, 61 | 'WyriHaximus\React\ChildProcess\Pool\Queue\Memory', 62 | $loop 63 | ); 64 | $this->manager = \WyriHaximus\React\ChildProcess\Pool\getManager( 65 | $this->options, 66 | $processCollection, 67 | 'WyriHaximus\React\ChildProcess\Pool\Manager\Flexible', 68 | $loop 69 | ); 70 | $this->manager->on('ready', function (WorkerInterface $worker) { 71 | if ($this->queue->count() === 0) { 72 | $this->ttl($worker); 73 | return; 74 | } 75 | 76 | $this->emit('worker', [$worker]); 77 | 78 | \React\Promise\resolve($this->queue->dequeue())->then(function (Rpc $message) use ($worker) { 79 | $hash = spl_object_hash($message); 80 | $this->deferreds[$hash]->resolve($worker->rpc($message)); 81 | unset($this->deferreds[$hash]); 82 | }); 83 | }); 84 | $this->manager->on('message', function ($message) { 85 | $this->emit('message', [$message]); 86 | }); 87 | $this->manager->on('error', function ($error) { 88 | $this->emit('error', [$error]); 89 | }); 90 | } 91 | 92 | /** 93 | * @param WorkerInterface $worker 94 | */ 95 | protected function ttl(WorkerInterface $worker) 96 | { 97 | $stop = time() + (int)$this->options[Options::TTL]; 98 | $this->loop->addPeriodicTimer(0.1, function ($timer) use ($worker, $stop) { 99 | if ($worker->isBusy()) { 100 | $this->loop->cancelTimer($timer); 101 | return; 102 | } 103 | 104 | if ($this->queue->count() > 0) { 105 | $this->loop->cancelTimer($timer); 106 | $this->manager->ping(); 107 | return; 108 | } 109 | 110 | if ($stop <= time()) { 111 | $this->loop->cancelTimer($timer); 112 | $worker->terminate(); 113 | } 114 | }); 115 | } 116 | 117 | /** 118 | * @param Rpc $message 119 | * @return PromiseInterface 120 | */ 121 | public function rpc(Rpc $message) 122 | { 123 | $hash = spl_object_hash($message); 124 | $deferred = new Deferred(); 125 | $this->deferreds[$hash] = $deferred; 126 | $this->queue->enqueue($message); 127 | $this->manager->ping(); 128 | return $deferred->promise(); 129 | } 130 | 131 | /** 132 | * @param Message $message 133 | */ 134 | public function message(Message $message) 135 | { 136 | $this->manager->message($message); 137 | } 138 | 139 | /** 140 | * @param Message|null $message 141 | * @param int $timeout 142 | * @param null $signal 143 | * @return PromiseInterface 144 | */ 145 | public function terminate(Message $message = null, $timeout = 5, $signal = null) 146 | { 147 | if ($message !== null) { 148 | $this->message($message); 149 | } 150 | 151 | return \WyriHaximus\React\timedPromise($this->loop, $timeout)->then(function () { 152 | $this->manager->removeAllListeners(); 153 | return $this->manager->terminate(); 154 | }); 155 | } 156 | 157 | /** 158 | * @return array 159 | */ 160 | public function info() 161 | { 162 | $workers = $this->manager->info(); 163 | return [ 164 | Info::BUSY => $workers[Info::BUSY], 165 | Info::CALLS => $this->queue->count(), 166 | Info::IDLE => $workers[Info::IDLE], 167 | Info::SIZE => $workers[Info::TOTAL], 168 | ]; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/PoolFactoryInterface.php: -------------------------------------------------------------------------------- 1 | callables = new ArrayIterator($callables); 18 | } 19 | 20 | #[\ReturnTypeWillChange] 21 | public function current() 22 | { 23 | return $this->callables->current(); 24 | } 25 | 26 | #[\ReturnTypeWillChange] 27 | public function key() 28 | { 29 | return $this->callables->key(); 30 | } 31 | 32 | #[\ReturnTypeWillChange] 33 | public function next() 34 | { 35 | $this->callables->next(); 36 | } 37 | 38 | #[\ReturnTypeWillChange] 39 | public function rewind() 40 | { 41 | $this->callables->rewind(); 42 | } 43 | 44 | #[\ReturnTypeWillChange] 45 | public function valid() 46 | { 47 | return $this->callables->valid(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/ProcessCollection/Single.php: -------------------------------------------------------------------------------- 1 | callable = $callable; 14 | } 15 | 16 | #[\ReturnTypeWillChange] 17 | public function current() 18 | { 19 | return $this->callable; 20 | } 21 | 22 | #[\ReturnTypeWillChange] 23 | public function key() 24 | { 25 | return 0; 26 | } 27 | 28 | #[\ReturnTypeWillChange] 29 | public function next() 30 | { 31 | return false; 32 | } 33 | 34 | #[\ReturnTypeWillChange] 35 | public function rewind() 36 | { 37 | // Do nothing 38 | } 39 | 40 | #[\ReturnTypeWillChange] 41 | public function valid() 42 | { 43 | return true; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/ProcessCollectionInterface.php: -------------------------------------------------------------------------------- 1 | queue = new \SplQueue(); 18 | } 19 | 20 | /** 21 | * @param Rpc $rpc 22 | */ 23 | public function enqueue(Rpc $rpc) 24 | { 25 | $this->queue->enqueue($rpc); 26 | } 27 | 28 | /** 29 | * @return Rpc 30 | */ 31 | public function dequeue() 32 | { 33 | return $this->queue->dequeue(); 34 | } 35 | 36 | /** 37 | * @return int 38 | */ 39 | public function count() 40 | { 41 | return $this->queue->count(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Queue/Overflow.php: -------------------------------------------------------------------------------- 1 | memory = new Memory(); 35 | $this->queue = $queue; 36 | $this->threshold = $threshold; 37 | } 38 | 39 | public function enqueue(Rpc $rpc) 40 | { 41 | if ($this->memory->count() < $this->threshold) { 42 | return $this->memory->enqueue($rpc); 43 | } 44 | 45 | return $this->queue->enqueue($rpc); 46 | } 47 | 48 | /** 49 | * @return PromiseInterface|Rpc 50 | */ 51 | public function dequeue() 52 | { 53 | $promise = $this->requeue(); 54 | 55 | if ($this->memory->count() > 0) { 56 | return $this->memory->dequeue(); 57 | } 58 | 59 | return $promise->then(function () { 60 | return \React\Promise\resolve($this->memory->dequeue()); 61 | }); 62 | } 63 | 64 | /** 65 | * @return PromiseInterface 66 | */ 67 | protected function requeue() 68 | { 69 | if ($this->queue->count() === 0) { 70 | return new FulfilledPromise(); 71 | } 72 | 73 | return \React\Promise\resolve($this->queue->dequeue())->then(function (Rpc $rpc) { 74 | return \React\Promise\resolve($this->memory->enqueue($rpc)); 75 | }); 76 | } 77 | 78 | /** 79 | * @return int 80 | */ 81 | public function count() 82 | { 83 | return $this->memory->count() + $this->queue->count(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/QueueInterface.php: -------------------------------------------------------------------------------- 1 | messenger = $messenger; 31 | $this->messenger->on('message', function ($message) { 32 | $this->emit('message', [$message]); 33 | }); 34 | $this->messenger->on('error', function ($error) { 35 | $this->emit('error', [$error]); 36 | }); 37 | } 38 | 39 | /** 40 | * @param Rpc $rpc 41 | * @return PromiseInterface 42 | */ 43 | public function rpc(Rpc $rpc) 44 | { 45 | $this->busy = true; 46 | return $this->messenger->rpc($rpc)->always(function () { 47 | $this->busy = false; 48 | $this->emit('done', [$this]); 49 | }); 50 | } 51 | 52 | /** 53 | * @param Message $message 54 | */ 55 | public function message(Message $message) 56 | { 57 | $this->messenger->message($message); 58 | } 59 | 60 | /** 61 | * @return bool 62 | */ 63 | public function isBusy() 64 | { 65 | return $this->busy; 66 | } 67 | 68 | /* 69 | * @return PromiseInterface 70 | */ 71 | public function terminate() 72 | { 73 | $this->busy = true; 74 | $this->emit('terminating', [$this]); 75 | $this->messenger->removeAllListeners(); 76 | return $this->messenger->softTerminate(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/WorkerInterface.php: -------------------------------------------------------------------------------- 1 | then(function ($cmd) use ($childProcess) { 96 | $finalOption = []; 97 | try { 98 | $finalOption = getProcessPropertyValue('options', $childProcess); 99 | } catch (\ReflectionException $re) { 100 | $finalOption = getProcessPropertyValue('fds', $childProcess); 101 | } 102 | return \React\Promise\resolve(new Process( 103 | $cmd, 104 | getProcessPropertyValue('cwd', $childProcess), 105 | getProcessPropertyValue('env', $childProcess), 106 | $finalOption 107 | )); 108 | }); 109 | } 110 | 111 | /** 112 | * @param string $property 113 | * @param object $childProcess 114 | * @return mixed 115 | */ 116 | function getProcessPropertyValue($property, $childProcess) 117 | { 118 | $reflectionProperty = (new \ReflectionClass($childProcess))->getProperty($property); 119 | $reflectionProperty->setAccessible(true); 120 | return $reflectionProperty->getValue($childProcess); 121 | } 122 | -------------------------------------------------------------------------------- /src/functions_include.php: -------------------------------------------------------------------------------- 1 | createProcess(); 32 | $loop = Phake::mock('React\EventLoop\LoopInterface'); 33 | $poolPromise = CpuCoreCountFixed::create($process, $loop, [ 34 | Options::DETECTOR => function ($loop) { 35 | $promise = new FulfilledPromise(); 36 | $affinity = Phake::mock('WyriHaximus\CpuCoreDetector\Core\AffinityInterface'); 37 | Phake::when($affinity)->execute($this->isType('integer'), $this->isType('string'))->thenReturn($promise); 38 | Resolver::setAffinity($affinity); 39 | return new FulfilledPromise(4); 40 | }, 41 | ]); 42 | 43 | $this->assertInstanceOf('React\Promise\PromiseInterface', $poolPromise); 44 | $promiseHasResolved = false; 45 | $poolPromise->then(function ($pool) use (&$promiseHasResolved) { 46 | $this->assertInstanceOf('WyriHaximus\React\ChildProcess\Pool\Pool\Fixed', $pool); 47 | $this->assertInstanceOf('WyriHaximus\React\ChildProcess\Pool\PoolInterface', $pool); 48 | $promiseHasResolved = true; 49 | }); 50 | $this->assertTrue($promiseHasResolved); 51 | } 52 | 53 | public function testCreateFromClass() 54 | { 55 | $loop = Phake::mock('React\EventLoop\LoopInterface'); 56 | $poolPromise = CpuCoreCountFixed::createFromClass('stdClass', $loop, [ 57 | Options::DETECTOR => function ($loop) { 58 | $promise = new FulfilledPromise(); 59 | $affinity = Phake::mock('WyriHaximus\CpuCoreDetector\Core\AffinityInterface'); 60 | Phake::when($affinity)->execute($this->isType('integer'), $this->isType('string'))->thenReturn($promise); 61 | Resolver::setAffinity($affinity); 62 | return new FulfilledPromise(4); 63 | }, 64 | ]); 65 | 66 | $this->assertInstanceOf('React\Promise\PromiseInterface', $poolPromise); 67 | $promiseHasResolved = false; 68 | $poolPromise->then(null, function ($exception) use (&$promiseHasResolved) { 69 | $this->assertInstanceOf('Exception', $exception); 70 | $this->assertSame('Given class doesn\'t implement ChildInterface', $exception->getMessage()); 71 | $promiseHasResolved = true; 72 | }); 73 | $this->assertTrue($promiseHasResolved); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/Factory/CpuCoreCountFlexibleTest.php: -------------------------------------------------------------------------------- 1 | createProcess(); 32 | $loop = Phake::mock('React\EventLoop\LoopInterface'); 33 | $poolPromise = CpuCoreCountFlexible::create($process, $loop, [ 34 | Options::DETECTOR => function ($loop) { 35 | $promise = new FulfilledPromise(); 36 | $affinity = Phake::mock('WyriHaximus\CpuCoreDetector\Core\AffinityInterface'); 37 | Phake::when($affinity)->execute($this->isType('integer'), $this->isType('string'))->thenReturn($promise); 38 | Resolver::setAffinity($affinity); 39 | return new FulfilledPromise(4); 40 | }, 41 | ]); 42 | 43 | $this->assertInstanceOf('React\Promise\PromiseInterface', $poolPromise); 44 | $promiseHasResolved = false; 45 | $poolPromise->then(function ($pool) use (&$promiseHasResolved) { 46 | $this->assertInstanceOf('WyriHaximus\React\ChildProcess\Pool\Pool\Flexible', $pool); 47 | $this->assertInstanceOf('WyriHaximus\React\ChildProcess\Pool\PoolInterface', $pool); 48 | $promiseHasResolved = true; 49 | }); 50 | $this->assertTrue($promiseHasResolved); 51 | } 52 | 53 | public function testCreateFromClass() 54 | { 55 | $loop = Phake::mock('React\EventLoop\LoopInterface'); 56 | $poolPromise = CpuCoreCountFlexible::createFromClass('stdClass', $loop, [ 57 | Options::DETECTOR => function ($loop) { 58 | $promise = new FulfilledPromise(); 59 | $affinity = Phake::mock('WyriHaximus\CpuCoreDetector\Core\AffinityInterface'); 60 | Phake::when($affinity)->execute($this->isType('integer'), $this->isType('string'))->thenReturn($promise); 61 | Resolver::setAffinity($affinity); 62 | return new FulfilledPromise(4); 63 | }, 64 | Options::MIN_SIZE => 1, 65 | Options::MAX_SIZE => 1, 66 | ]); 67 | 68 | $this->assertInstanceOf('React\Promise\PromiseInterface', $poolPromise); 69 | $promiseHasResolved = false; 70 | $poolPromise->then(null, function ($exception) use (&$promiseHasResolved) { 71 | $this->assertInstanceOf('Exception', $exception); 72 | $this->assertSame('Given class doesn\'t implement ChildInterface', $exception->getMessage()); 73 | $promiseHasResolved = true; 74 | }); 75 | $this->assertTrue($promiseHasResolved); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/Factory/DummyTest.php: -------------------------------------------------------------------------------- 1 | then(function ($pool) use (&$promiseHasResolved) { 30 | $this->assertInstanceOf('WyriHaximus\React\ChildProcess\Pool\Pool\Dummy', $pool); 31 | $this->assertInstanceOf('WyriHaximus\React\ChildProcess\Pool\PoolInterface', $pool); 32 | $promiseHasResolved = true; 33 | }); 34 | $this->assertTrue($promiseHasResolved); 35 | } 36 | 37 | public function testCreateFromClass() 38 | { 39 | $loop = Phake::mock('React\EventLoop\LoopInterface'); 40 | $poolPromise = Dummy::createFromClass('stdClass', $loop); 41 | 42 | $promiseHasResolved = false; 43 | $poolPromise->then(function ($pool) use (&$promiseHasResolved) { 44 | $this->assertInstanceOf('WyriHaximus\React\ChildProcess\Pool\Pool\Dummy', $pool); 45 | $this->assertInstanceOf('WyriHaximus\React\ChildProcess\Pool\PoolInterface', $pool); 46 | $promiseHasResolved = true; 47 | }); 48 | $this->assertTrue($promiseHasResolved); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/Factory/FixedTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('React\Promise\PromiseInterface', $poolPromise); 17 | $promiseHasResolved = false; 18 | $poolPromise->then(function ($pool) use (&$promiseHasResolved) { 19 | $this->assertInstanceOf('WyriHaximus\React\ChildProcess\Pool\Pool\Fixed', $pool); 20 | $this->assertInstanceOf('WyriHaximus\React\ChildProcess\Pool\PoolInterface', $pool); 21 | $promiseHasResolved = true; 22 | }); 23 | $this->assertTrue($promiseHasResolved); 24 | } 25 | 26 | /** 27 | * @expectedException \Exception 28 | * @expectedExceptionMessage Given class doesn't implement ChildInterface 29 | */ 30 | public function testCreateFromClass() 31 | { 32 | $loop = Phake::mock('React\EventLoop\LoopInterface'); 33 | Fixed::createFromClass('stdClass', $loop); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Factory/FlexibleTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('React\Promise\PromiseInterface', $poolPromise); 18 | $promiseHasResolved = false; 19 | $poolPromise->then(function ($pool) use (&$promiseHasResolved) { 20 | $this->assertInstanceOf('WyriHaximus\React\ChildProcess\Pool\Pool\Flexible', $pool); 21 | $this->assertInstanceOf('WyriHaximus\React\ChildProcess\Pool\PoolInterface', $pool); 22 | $promiseHasResolved = true; 23 | }); 24 | $this->assertTrue($promiseHasResolved); 25 | } 26 | 27 | /** 28 | * @expectedException \Exception 29 | * @expectedExceptionMessage Given class doesn't implement ChildInterface 30 | */ 31 | public function testCreateFromClass() 32 | { 33 | $loop = Phake::mock('React\EventLoop\LoopInterface'); 34 | Flexible::createFromClass('stdClass', $loop, [ 35 | Options::MIN_SIZE => 1, 36 | Options::MAX_SIZE => 1, 37 | ]); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/FunctionsTest.php: -------------------------------------------------------------------------------- 1 | 'stdClass', 33 | ], 34 | Options::MANAGER, 35 | 'stdClass', 36 | 'foo.bar', 37 | 'stdClass', 38 | ]; 39 | 40 | $r[] = [ 41 | [ 42 | Options::MANAGER => 'WyriHaximus\React\ChildProcess\Pool\Manager\Fixed', 43 | ], 44 | Options::MANAGER, 45 | 'WyriHaximus\React\ChildProcess\Pool\ManagerInterface', 46 | 'foo.bar', 47 | 'WyriHaximus\React\ChildProcess\Pool\Manager\Fixed', 48 | ]; 49 | 50 | return $r; 51 | } 52 | 53 | /** 54 | * @dataProvider provideGetClassNameFromOptionOrDefault 55 | */ 56 | public function testGetClassNameFromOptionOrDefault($options, $key, $instanceOf, $fallback, $output) 57 | { 58 | $this->assertSame( 59 | $output, 60 | \WyriHaximus\React\ChildProcess\Pool\getClassNameFromOptionOrDefault( 61 | $options, 62 | $key, 63 | $instanceOf, 64 | $fallback 65 | ) 66 | ); 67 | } 68 | 69 | public function providerGetQueue() 70 | { 71 | $r = []; 72 | 73 | $r[] = [ 74 | [], 75 | 'WyriHaximus\React\ChildProcess\Pool\Queue\Memory', 76 | Factory::create(), 77 | new Memory(), 78 | ]; 79 | 80 | $r[] = [ 81 | [ 82 | Options::QUEUE => new Memory(), 83 | ], 84 | 'WyriHaximus\React\ChildProcess\Pool\Queue\Memory', 85 | Factory::create(), 86 | new Memory(), 87 | ]; 88 | 89 | $r[] = [ 90 | [ 91 | Options::QUEUE => 'WyriHaximus\React\ChildProcess\Pool\Queue\Memory', 92 | ], 93 | 'WyriHaximus\React\ChildProcess\Pool\Queue\Memory', 94 | Factory::create(), 95 | new Memory(), 96 | ]; 97 | 98 | $mock = Phake::mock('WyriHaximus\React\ChildProcess\Pool\QueueInterface'); 99 | $r[] = [ 100 | [ 101 | Options::QUEUE => $mock, 102 | ], 103 | 'WyriHaximus\React\ChildProcess\Pool\Queue\Memory', 104 | Factory::create(), 105 | $mock, 106 | ]; 107 | 108 | return $r; 109 | } 110 | 111 | /** 112 | * @dataProvider providerGetQueue 113 | */ 114 | public function testGetQueue($options, $default, $loop, $output) 115 | { 116 | $this->assertEquals( 117 | $output, 118 | \WyriHaximus\React\ChildProcess\Pool\getQueue( 119 | $options, 120 | $default, 121 | $loop 122 | ) 123 | ); 124 | } 125 | 126 | public function providerGetManager() 127 | { 128 | $r = []; 129 | 130 | $r[] = [ 131 | [ 132 | Options::SIZE => 0, 133 | ], 134 | Phake::mock('WyriHaximus\React\ChildProcess\Pool\ProcessCollectionInterface'), 135 | 'WyriHaximus\React\ChildProcess\Pool\Manager\Fixed', 136 | Factory::create(), 137 | new Fixed( 138 | Phake::mock('WyriHaximus\React\ChildProcess\Pool\ProcessCollectionInterface'), 139 | Factory::create(), 140 | [ 141 | Options::SIZE => 0, 142 | ] 143 | ), 144 | ]; 145 | 146 | $mock = Phake::mock('WyriHaximus\React\ChildProcess\Pool\ManagerInterface'); 147 | $r[] = [ 148 | [ 149 | Options::MANAGER => $mock, 150 | Options::SIZE => 0, 151 | ], 152 | Phake::mock('WyriHaximus\React\ChildProcess\Pool\ProcessCollectionInterface'), 153 | 'WyriHaximus\React\ChildProcess\Pool\Queue\Memory', 154 | Factory::create(), 155 | $mock, 156 | ]; 157 | 158 | $processCollection = Phake::mock('WyriHaximus\React\ChildProcess\Pool\ProcessCollectionInterface'); 159 | $r[] = [ 160 | [ 161 | Options::MANAGER => new Flexible( 162 | $processCollection, 163 | Factory::create(), 164 | [ 165 | Options::MIN_SIZE => 0, 166 | Options::MAX_SIZE => 0, 167 | ] 168 | ), 169 | Options::SIZE => 0, 170 | ], 171 | Phake::mock('WyriHaximus\React\ChildProcess\Pool\ProcessCollectionInterface'), 172 | 'WyriHaximus\React\ChildProcess\Pool\Queue\Memory', 173 | Factory::create(), 174 | new Flexible( 175 | $processCollection, 176 | Factory::create(), 177 | [ 178 | Options::MIN_SIZE => 0, 179 | Options::MAX_SIZE => 0, 180 | ] 181 | ), 182 | ]; 183 | 184 | return $r; 185 | } 186 | 187 | /** 188 | * @dataProvider providerGetManager 189 | */ 190 | public function testGetManager($options, $processCollection, $default, $loop, $output) 191 | { 192 | $this->assertEquals( 193 | $output, 194 | \WyriHaximus\React\ChildProcess\Pool\getManager( 195 | $options, 196 | $processCollection, 197 | $default, 198 | $loop 199 | ) 200 | ); 201 | } 202 | 203 | public function testDetectCoreCount() 204 | { 205 | $loop = Factory::create(); 206 | 207 | $promise = \WyriHaximus\React\ChildProcess\Pool\detectCoreCount( 208 | $loop, 209 | [] 210 | ); 211 | $count = \Clue\React\Block\await($promise, $loop, 10); 212 | 213 | $this->assertInternalType('integer', $count); 214 | } 215 | 216 | public function testDetectCoreCountCustomDetector() 217 | { 218 | $loop = Factory::create(); 219 | $this->assertSame( 220 | 128, 221 | \WyriHaximus\React\ChildProcess\Pool\detectCoreCount( 222 | $loop, 223 | [ 224 | Options::DETECTOR => function (LoopInterface $passedLoop) use ($loop) 225 | { 226 | $this->assertSame($passedLoop, $loop); 227 | return 128; 228 | } 229 | ] 230 | ) 231 | ); 232 | } 233 | 234 | public function testRebuildProcessAndGetProcessPropertyValue() 235 | { 236 | $promise = new FulfilledPromise('taskset -c 13 a'); 237 | $affinity = Phake::mock('WyriHaximus\CpuCoreDetector\Core\AffinityInterface'); 238 | Phake::when($affinity)->execute(13, 'a')->thenReturn($promise); 239 | Resolver::setAffinity($affinity); 240 | $process = new Process( 241 | 'a', 242 | 'b', 243 | [ 244 | 'c' 245 | ], 246 | [ 247 | 'd' 248 | ] 249 | ); 250 | 251 | $optionName = 'fds'; 252 | try { 253 | \WyriHaximus\React\ChildProcess\Pool\getProcessPropertyValue('options', $process); 254 | $optionName = 'options'; 255 | } catch (\ReflectionException $re) { 256 | // 257 | } 258 | 259 | $promiseResolved = false; 260 | \WyriHaximus\React\ChildProcess\Pool\rebuildProcess( 261 | 13, 262 | $process 263 | )->then(function ($rebuildProcess) use (&$promiseResolved, $optionName) { 264 | $this->assertSame('taskset -c 13 a', \WyriHaximus\React\ChildProcess\Pool\getProcessPropertyValue('cmd', $rebuildProcess)); 265 | $this->assertSame('b', \WyriHaximus\React\ChildProcess\Pool\getProcessPropertyValue('cwd', $rebuildProcess)); 266 | $this->assertSame( 267 | [ 268 | 'c', 269 | ], 270 | \WyriHaximus\React\ChildProcess\Pool\getProcessPropertyValue('env', $rebuildProcess) 271 | ); 272 | $this->assertSame( 273 | [ 274 | 'd', 275 | ], 276 | \WyriHaximus\React\ChildProcess\Pool\getProcessPropertyValue($optionName, $rebuildProcess) 277 | ); 278 | $promiseResolved = true; 279 | }); 280 | $this->assertTrue($promiseResolved); 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /tests/InfoTest.php: -------------------------------------------------------------------------------- 1 | assertSame(0, count(get_class_methods('WyriHaximus\React\ChildProcess\Pool\Info'))); 12 | } 13 | 14 | public function testNoVars() 15 | { 16 | $this->assertSame(0, count(get_class_vars('WyriHaximus\React\ChildProcess\Pool\Info'))); 17 | } 18 | 19 | public function testConstants() 20 | { 21 | foreach ((new ReflectionClass('WyriHaximus\React\ChildProcess\Pool\Info'))->getConstants() as $constant) { 22 | $this->assertInternalType('string', $constant); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Launcher/ClassNameTest.php: -------------------------------------------------------------------------------- 1 | processCollection = Phake::mock('WyriHaximus\React\ChildProcess\Pool\ProcessCollectionInterface'); 41 | $this->loop = Phake::mock('React\EventLoop\LoopInterface'); 42 | } 43 | 44 | protected function noWorkers() 45 | { 46 | Phake::when($this->processCollection)->current()->thenReturnCallback(function () { 47 | return function () { 48 | return new RejectedPromise(); 49 | }; 50 | }); 51 | } 52 | 53 | protected function createManager() 54 | { 55 | $this->manager = new Fixed($this->processCollection, $this->loop, [ 56 | Options::SIZE => 1, 57 | ]); 58 | } 59 | 60 | public function tearDown() 61 | { 62 | $this->manager = null; 63 | $this->loop = null; 64 | $this->processCollection = null; 65 | 66 | parent::tearDown(); 67 | } 68 | 69 | public function testInfoAtStart() 70 | { 71 | $this->noWorkers(); 72 | $this->createManager(); 73 | 74 | $this->assertSame([ 75 | Info::TOTAL => 0, 76 | Info::BUSY => 0, 77 | Info::IDLE => 0, 78 | ], $this->manager->info()); 79 | } 80 | 81 | public function _testPingEmit() 82 | { 83 | Phake::when($this->processCollection)->current()->thenReturnCallback(function () { 84 | return function () { 85 | return new FulfilledPromise(Phake::mock('WyriHaximus\React\ChildProcess\Messenger\MessengerInterface')); 86 | }; 87 | }); 88 | 89 | $this->createManager(); 90 | 91 | $called = false; 92 | $this->manager->once('ready', function (WorkerInterface $worker) use (&$called) { 93 | $this->assertInstanceOf('WyriHaximus\React\ChildProcess\Pool\WorkerInterface', $worker); 94 | $called = true; 95 | }); 96 | $this->manager->ping(); 97 | 98 | $this->assertTrue($called); 99 | } 100 | 101 | public function _testRpc() 102 | { 103 | $rpc = Factory::rpc('foo', ['bar']); 104 | $workerDeferred = new Deferred(); 105 | $rpcDeferred = new Deferred(); 106 | $worker = null; 107 | $messenger = Phake::mock('WyriHaximus\React\ChildProcess\Messenger\MessengerInterface'); 108 | Phake::when($messenger)->rpc($rpc)->thenReturn($rpcDeferred->promise()); 109 | 110 | Phake::when($this->processCollection)->current()->thenReturnCallback(function () use ($workerDeferred) { 111 | return function () use ($workerDeferred) { 112 | return $workerDeferred->promise(); 113 | }; 114 | }); 115 | 116 | $this->createManager(); 117 | 118 | $this->manager->once('ready', function (WorkerInterface $workerInstance) use (&$worker) { 119 | $worker = $workerInstance; 120 | }); 121 | 122 | $workerDeferred->resolve($messenger); 123 | 124 | $this->assertSame([ 125 | Info::TOTAL => 1, 126 | Info::BUSY => 0, 127 | Info::IDLE => 1, 128 | ], $this->manager->info()); 129 | 130 | $worker->rpc($rpc); 131 | 132 | $this->assertSame([ 133 | Info::TOTAL => 1, 134 | Info::BUSY => 1, 135 | Info::IDLE => 0, 136 | ], $this->manager->info()); 137 | 138 | $rpcDeferred->resolve(); 139 | 140 | $this->assertSame([ 141 | Info::TOTAL => 1, 142 | Info::BUSY => 0, 143 | Info::IDLE => 1, 144 | ], $this->manager->info()); 145 | } 146 | 147 | public function testTerminate() 148 | { 149 | $workerDeferred = new Deferred(); 150 | $worker = null; 151 | $messenger = Phake::mock('WyriHaximus\React\ChildProcess\Messenger\MessengerInterface'); 152 | 153 | Phake::when($this->processCollection)->current()->thenReturnCallback(function () use ($workerDeferred) { 154 | return function () use ($workerDeferred) { 155 | return $workerDeferred->promise(); 156 | }; 157 | }); 158 | 159 | $this->createManager(); 160 | 161 | $this->manager->once('ready', function (WorkerInterface $workerInstance) use (&$worker) { 162 | $worker = $workerInstance; 163 | }); 164 | 165 | $workerDeferred->resolve($messenger); 166 | 167 | $this->assertSame([ 168 | Info::TOTAL => 1, 169 | Info::BUSY => 0, 170 | Info::IDLE => 1, 171 | ], $this->manager->info()); 172 | 173 | $emittedTerminate = false; 174 | $worker->on('terminating', function ($worker) use (&$emittedTerminate) { 175 | $this->assertInstanceOf('WyriHaximus\React\ChildProcess\Pool\WorkerInterface', $worker); 176 | $emittedTerminate = true; 177 | }); 178 | 179 | $this->manager->terminate(); 180 | 181 | $this->assertSame([ 182 | Info::TOTAL => 1, 183 | Info::BUSY => 1, 184 | Info::IDLE => 0, 185 | ], $this->manager->info()); 186 | 187 | $this->assertTrue($emittedTerminate); 188 | Phake::verify($messenger)->softTerminate(); 189 | } 190 | 191 | public function _testMessage() 192 | { 193 | $message = Factory::message(['bar']); 194 | $workerDeferred = new Deferred(); 195 | $worker = null; 196 | $messenger = Phake::mock('WyriHaximus\React\ChildProcess\Messenger\MessengerInterface'); 197 | 198 | Phake::when($this->processCollection)->current()->thenReturnCallback(function () use ($workerDeferred) { 199 | return function () use ($workerDeferred) { 200 | return $workerDeferred->promise(); 201 | }; 202 | }); 203 | 204 | $this->createManager(); 205 | 206 | $this->manager->once('ready', function (WorkerInterface $workerInstance) use (&$worker) { 207 | $worker = $workerInstance; 208 | }); 209 | 210 | $workerDeferred->resolve($messenger); 211 | $this->manager->message($message); 212 | 213 | Phake::verify($messenger)->message($message); 214 | } 215 | 216 | public function testErrorFromSpawning() 217 | { 218 | $message = Factory::message(['bar']); 219 | $workerDeferredA = new Deferred(); 220 | $workerDeferredB = new Deferred(); 221 | /** @var Deferred[] $workerDeferreds */ 222 | $workerDeferreds = [$workerDeferredA, $workerDeferredB]; 223 | $worker = null; 224 | $messenger = Phake::mock('WyriHaximus\React\ChildProcess\Messenger\MessengerInterface'); 225 | 226 | Phake::when($this->processCollection)->current()->thenReturnCallback(function () use (&$workerDeferreds) { 227 | return function () use (&$workerDeferreds) { 228 | return array_shift($workerDeferreds)->promise(); 229 | }; 230 | }); 231 | 232 | $this->createManager(); 233 | 234 | $this->manager->once('ready', function (WorkerInterface $workerInstance) use (&$worker) { 235 | $worker = $workerInstance; 236 | }); 237 | 238 | $workerDeferredA->resolve(new TimeoutException(0.13)); 239 | $workerDeferredB->resolve($messenger); 240 | $this->manager->message($message); 241 | 242 | Phake::verify($messenger)->message($message); 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /tests/Manager/FlexibleTest.php: -------------------------------------------------------------------------------- 1 | processCollection = Phake::mock('WyriHaximus\React\ChildProcess\Pool\ProcessCollectionInterface'); 41 | $this->loop = Phake::mock('React\EventLoop\LoopInterface'); 42 | } 43 | 44 | protected function noWorkers() 45 | { 46 | Phake::when($this->processCollection)->current()->thenReturnCallback(function () { 47 | return function () { 48 | return new RejectedPromise(); 49 | }; 50 | }); 51 | } 52 | 53 | protected function createManager() 54 | { 55 | $this->manager = new Flexible($this->processCollection, $this->loop, [ 56 | Options::MIN_SIZE => 1, 57 | ]); 58 | } 59 | 60 | public function tearDown() 61 | { 62 | $this->manager = null; 63 | $this->loop = null; 64 | $this->processCollection = null; 65 | 66 | parent::tearDown(); 67 | } 68 | 69 | public function testInfoAtStart() 70 | { 71 | $this->noWorkers(); 72 | $this->createManager(); 73 | 74 | $this->assertSame([ 75 | Info::TOTAL => 0, 76 | Info::BUSY => 0, 77 | Info::IDLE => 0, 78 | ], $this->manager->info()); 79 | } 80 | 81 | public function testPingEmit() 82 | { 83 | Phake::when($this->processCollection)->current()->thenReturnCallback(function () { 84 | return function () { 85 | return new FulfilledPromise(Phake::mock('WyriHaximus\React\ChildProcess\Messenger\MessengerInterface')); 86 | }; 87 | }); 88 | 89 | $this->createManager(); 90 | 91 | $called = false; 92 | $this->manager->once('ready', function (WorkerInterface $worker) use (&$called) { 93 | $this->assertInstanceOf('WyriHaximus\React\ChildProcess\Pool\WorkerInterface', $worker); 94 | $called = true; 95 | }); 96 | $this->manager->ping(); 97 | 98 | $this->assertTrue($called); 99 | } 100 | 101 | public function testRpc() 102 | { 103 | $rpc = Factory::rpc('foo', ['bar']); 104 | $workerDeferred = new Deferred(); 105 | $rpcDeferred = new Deferred(); 106 | $worker = null; 107 | $messenger = Phake::mock('WyriHaximus\React\ChildProcess\Messenger\MessengerInterface'); 108 | Phake::when($messenger)->rpc($rpc)->thenReturn($rpcDeferred->promise()); 109 | 110 | Phake::when($this->processCollection)->current()->thenReturnCallback(function () use ($workerDeferred) { 111 | return function () use ($workerDeferred) { 112 | return $workerDeferred->promise(); 113 | }; 114 | }); 115 | 116 | $this->createManager(); 117 | 118 | $this->manager->once('ready', function (WorkerInterface $workerInstance) use (&$worker) { 119 | $worker = $workerInstance; 120 | }); 121 | 122 | $workerDeferred->resolve($messenger); 123 | 124 | $this->assertSame([ 125 | Info::TOTAL => 1, 126 | Info::BUSY => 0, 127 | Info::IDLE => 1, 128 | ], $this->manager->info()); 129 | 130 | $this->manager->ping(); 131 | 132 | $this->assertSame([ 133 | Info::TOTAL => 1, 134 | Info::BUSY => 0, 135 | Info::IDLE => 1, 136 | ], $this->manager->info()); 137 | 138 | $worker->rpc($rpc); 139 | 140 | $this->assertSame([ 141 | Info::TOTAL => 1, 142 | Info::BUSY => 1, 143 | Info::IDLE => 0, 144 | ], $this->manager->info()); 145 | 146 | $rpcDeferred->resolve(); 147 | 148 | $this->assertSame([ 149 | Info::TOTAL => 1, 150 | Info::BUSY => 0, 151 | Info::IDLE => 1, 152 | ], $this->manager->info()); 153 | } 154 | 155 | public function testTerminate() 156 | { 157 | $workerDeferred = new Deferred(); 158 | $worker = null; 159 | $messenger = Phake::mock('WyriHaximus\React\ChildProcess\Messenger\MessengerInterface'); 160 | 161 | Phake::when($this->processCollection)->current()->thenReturnCallback(function () use ($workerDeferred) { 162 | return function () use ($workerDeferred) { 163 | return $workerDeferred->promise(); 164 | }; 165 | }); 166 | 167 | $this->createManager(); 168 | 169 | $this->manager->once('ready', function (WorkerInterface $workerInstance) use (&$worker) { 170 | $worker = $workerInstance; 171 | }); 172 | 173 | $workerDeferred->resolve($messenger); 174 | 175 | $this->assertSame([ 176 | Info::TOTAL => 1, 177 | Info::BUSY => 0, 178 | Info::IDLE => 1, 179 | ], $this->manager->info()); 180 | 181 | $this->manager->ping(); 182 | 183 | $this->assertSame([ 184 | Info::TOTAL => 1, 185 | Info::BUSY => 0, 186 | Info::IDLE => 1, 187 | ], $this->manager->info()); 188 | 189 | $emittedTerminate = false; 190 | $worker->on('terminating', function ($worker) use (&$emittedTerminate) { 191 | $this->assertInstanceOf('WyriHaximus\React\ChildProcess\Pool\WorkerInterface', $worker); 192 | $emittedTerminate = true; 193 | }); 194 | 195 | $this->manager->terminate(); 196 | 197 | $this->assertSame([ 198 | Info::TOTAL => 0, 199 | Info::BUSY => 0, 200 | Info::IDLE => 0, 201 | ], $this->manager->info()); 202 | 203 | $this->assertTrue($emittedTerminate); 204 | Phake::verify($messenger)->softTerminate(); 205 | } 206 | 207 | public function testPingWorkerAvailable() 208 | { 209 | $rpc = Factory::rpc('foo', ['bar']); 210 | $messenger = Phake::mock('WyriHaximus\React\ChildProcess\Messenger\MessengerInterface'); 211 | Phake::when($messenger)->rpc($rpc)->thenReturn((new Deferred())->promise()); 212 | $loop = Phake::mock('React\EventLoop\LoopInterface'); 213 | $processCollection = Phake::mock('WyriHaximus\React\ChildProcess\Pool\ProcessCollectionInterface'); 214 | Phake::when($processCollection)->next()->thenReturn(true); 215 | Phake::when($processCollection)->current()->thenReturn(function () use ($messenger) { 216 | return \React\Promise\resolve($messenger); 217 | }); 218 | $manager = Phake::partialMock('WyriHaximus\React\ChildProcess\Pool\Manager\Flexible', $processCollection, $loop, [ 219 | Options::MIN_SIZE => 1, 220 | Options::MAX_SIZE => 2, 221 | ]); 222 | Phake::when($manager)->spawn()->thenCallParent(); 223 | Phake::when($manager)->emit($this->isType('string'), $this->isType('array'))->thenCallParent(); 224 | Phake::when($manager)->once($this->isType('string'), $this->isType('callable'))->thenCallParent(); 225 | 226 | $manager->ping(); 227 | 228 | $manager->once('ready', function ($worker) use ($rpc) { 229 | $worker->terminate(); 230 | }); 231 | 232 | $manager->ping(); 233 | $manager->once('ready', function ($worker) use ($rpc) { 234 | $worker->rpc($rpc); 235 | }); 236 | $manager->ping(); 237 | 238 | $manager->once('ready', function ($worker) use ($rpc) { 239 | $worker->rpc($rpc); 240 | }); 241 | $manager->ping(); 242 | 243 | //Phake::verify($manager, Phake::times(2))->spawn(); 244 | } 245 | 246 | public function testMessage() 247 | { 248 | $message = Factory::message(['bar']); 249 | $workerDeferred = new Deferred(); 250 | $worker = null; 251 | $messenger = Phake::mock('WyriHaximus\React\ChildProcess\Messenger\MessengerInterface'); 252 | 253 | Phake::when($this->processCollection)->current()->thenReturnCallback(function () use ($workerDeferred) { 254 | return function () use ($workerDeferred) { 255 | return $workerDeferred->promise(); 256 | }; 257 | }); 258 | 259 | $this->createManager(); 260 | 261 | $this->manager->once('ready', function (WorkerInterface $workerInstance) use (&$worker) { 262 | $worker = $workerInstance; 263 | }); 264 | 265 | $workerDeferred->resolve($messenger); 266 | $this->manager->message($message); 267 | 268 | Phake::verify($messenger)->message($message); 269 | } 270 | 271 | public function testErrorFromSpawning() 272 | { 273 | $message = Factory::message(['bar']); 274 | $workerDeferredA = new Deferred(); 275 | $workerDeferredB = new Deferred(); 276 | /** @var Deferred[] $workerDeferreds */ 277 | $workerDeferreds = [$workerDeferredA, $workerDeferredB]; 278 | $worker = null; 279 | $messenger = Phake::mock('WyriHaximus\React\ChildProcess\Messenger\MessengerInterface'); 280 | 281 | Phake::when($this->processCollection)->current()->thenReturnCallback(function () use (&$workerDeferreds) { 282 | return function () use (&$workerDeferreds) { 283 | return array_shift($workerDeferreds)->promise(); 284 | }; 285 | }); 286 | 287 | $this->createManager(); 288 | 289 | $this->manager->once('ready', function (WorkerInterface $workerInstance) use (&$worker) { 290 | $worker = $workerInstance; 291 | }); 292 | 293 | $workerDeferredA->resolve(new TimeoutException(0.13)); 294 | $workerDeferredB->resolve($messenger); 295 | $this->manager->message($message); 296 | 297 | Phake::verify($messenger)->message($message); 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /tests/OptionsTest.php: -------------------------------------------------------------------------------- 1 | assertSame(0, count(get_class_methods('WyriHaximus\React\ChildProcess\Pool\Options'))); 12 | } 13 | 14 | public function testNoVars() 15 | { 16 | $this->assertSame(0, count(get_class_vars('WyriHaximus\React\ChildProcess\Pool\Options'))); 17 | } 18 | 19 | public function testConstants() 20 | { 21 | foreach ((new ReflectionClass('WyriHaximus\React\ChildProcess\Pool\Options'))->getConstants() as $constant) { 22 | $this->assertInternalType('string', $constant); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Pool/DummyTest.php: -------------------------------------------------------------------------------- 1 | then(function ($pool) use (&$promiseHasResolved) { 19 | $pool->message(Factory::message([])); 20 | $pool->terminate(Factory::message([])); 21 | 22 | $this->assertInstanceOf('React\Promise\FulfilledPromise', $pool->rpc(Factory::rpc('abc', ['def']))); 23 | $this->assertSame([], $pool->info()); 24 | $promiseHasResolved = true; 25 | }); 26 | $this->assertTrue($promiseHasResolved); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Pool/FixedTest.php: -------------------------------------------------------------------------------- 1 | ping()->thenReturn(true); 18 | 19 | $queue = Phake::mock('WyriHaximus\React\ChildProcess\Pool\QueueInterface'); 20 | $poolInstance = null; 21 | 22 | $process = Phake::mock('React\ChildProcess\Process'); 23 | $loop = Phake::mock('React\EventLoop\LoopInterface'); 24 | $poolPromise = Fixed::create($process, $loop, [ 25 | Options::MANAGER => $manager, 26 | Options::QUEUE => $queue, 27 | ]); 28 | $this->assertInstanceOf('React\Promise\PromiseInterface', $poolPromise); 29 | $promiseHasResolved = false; 30 | $poolPromise->then(function ($pool) use (&$promiseHasResolved, &$poolInstance) { 31 | $promiseHasResolved = true; 32 | $poolInstance = $pool; 33 | }); 34 | $this->assertTrue($promiseHasResolved); 35 | 36 | $this->assertInstanceOf('React\Promise\PromiseInterface', $poolInstance->rpc(Factory::rpc('foo', ['bar']))); 37 | 38 | Phake::verify($manager)->ping(); 39 | } 40 | 41 | public function testInfo() 42 | { 43 | $manager = Phake::mock('WyriHaximus\React\ChildProcess\Pool\ManagerInterface'); 44 | Phake::when($manager)->info()->thenReturn([ 45 | Info::TOTAL => 1, 46 | Info::IDLE => 2, 47 | Info::BUSY => 3, 48 | ]); 49 | 50 | $queue = Phake::mock('WyriHaximus\React\ChildProcess\Pool\QueueInterface'); 51 | Phake::when($queue)->count()->thenReturn(4); 52 | 53 | $poolInstance = null; 54 | 55 | $process = Phake::mock('React\ChildProcess\Process'); 56 | $loop = Phake::mock('React\EventLoop\LoopInterface'); 57 | $poolPromise = Fixed::create($process, $loop, [ 58 | Options::MANAGER => $manager, 59 | Options::QUEUE => $queue, 60 | ]); 61 | $this->assertInstanceOf('React\Promise\PromiseInterface', $poolPromise); 62 | $promiseHasResolved = false; 63 | $poolPromise->then(function ($pool) use (&$promiseHasResolved, &$poolInstance) { 64 | $promiseHasResolved = true; 65 | $poolInstance = $pool; 66 | }); 67 | $this->assertTrue($promiseHasResolved); 68 | 69 | $this->assertSame([ 70 | Info::BUSY => 3, 71 | Info::CALLS => 4, 72 | Info::IDLE => 2, 73 | Info::SIZE => 1, 74 | ], $poolInstance->info()); 75 | } 76 | 77 | public function testTerminate() 78 | { 79 | $manager = Phake::mock('WyriHaximus\React\ChildProcess\Pool\ManagerInterface'); 80 | Phake::when($manager)->info()->thenReturn([ 81 | Info::TOTAL => 1, 82 | Info::IDLE => 2, 83 | Info::BUSY => 3, 84 | ]); 85 | 86 | $queue = Phake::mock('WyriHaximus\React\ChildProcess\Pool\QueueInterface'); 87 | Phake::when($queue)->count()->thenReturn(4); 88 | 89 | $poolInstance = null; 90 | 91 | $process = Phake::mock('React\ChildProcess\Process'); 92 | $loop = Phake::mock('React\EventLoop\LoopInterface'); 93 | Phake::when($loop)->addTimer($this->isType('integer'), $this->isType('callable'))->thenReturnCallback(function ($interval, $function) { 94 | $function(); 95 | }); 96 | $poolPromise = Fixed::create($process, $loop, [ 97 | Options::MANAGER => $manager, 98 | Options::QUEUE => $queue, 99 | ]); 100 | $this->assertInstanceOf('React\Promise\PromiseInterface', $poolPromise); 101 | $promiseHasResolved = false; 102 | $poolPromise->then(function ($pool) use (&$promiseHasResolved, &$poolInstance) { 103 | $promiseHasResolved = true; 104 | $poolInstance = $pool; 105 | }); 106 | $this->assertTrue($promiseHasResolved); 107 | 108 | $poolInstance->terminate(Factory::message(['foo' => 'bar'])); 109 | 110 | Phake::verify($manager)->terminate(); 111 | } 112 | 113 | public function testManagerReady() 114 | { 115 | $function = null; 116 | 117 | $message = Factory::rpc('beer', ['foo' => 'bar']); 118 | $worker = Phake::mock('WyriHaximus\React\ChildProcess\Pool\WorkerInterface'); 119 | $queue = Phake::mock('WyriHaximus\React\ChildProcess\Pool\QueueInterface'); 120 | $manager = Phake::mock('WyriHaximus\React\ChildProcess\Pool\ManagerInterface'); 121 | 122 | Phake::when($queue)->count()->thenReturn(4); 123 | Phake::when($queue)->dequeue()->thenReturn($message); 124 | Phake::when($manager)->on($this->isType('string'), $this->isType('callable'))->thenReturnCallback(function ($event, $passedFunction) use (&$function) { 125 | $function = $passedFunction; 126 | }); 127 | 128 | $poolInstance = null; 129 | 130 | $process = Phake::mock('React\ChildProcess\Process'); 131 | $loop = Phake::mock('React\EventLoop\LoopInterface'); 132 | Fixed::create($process, $loop, [ 133 | Options::MANAGER => $manager, 134 | Options::QUEUE => $queue, 135 | ])->then(function ($pool) use ($message) { 136 | $pool->rpc($message); 137 | }); 138 | 139 | $function($worker); 140 | 141 | // Phake::verify($worker)->rpc($message); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /tests/Pool/FlexibleTest.php: -------------------------------------------------------------------------------- 1 | ping()->thenReturn(true); 18 | 19 | $queue = Phake::mock('WyriHaximus\React\ChildProcess\Pool\QueueInterface'); 20 | $poolInstance = null; 21 | 22 | $process = Phake::mock('React\ChildProcess\Process'); 23 | $loop = Phake::mock('React\EventLoop\LoopInterface'); 24 | $poolPromise = Flexible::create($process, $loop, [ 25 | Options::MANAGER => $manager, 26 | Options::QUEUE => $queue, 27 | ]); 28 | $this->assertInstanceOf('React\Promise\PromiseInterface', $poolPromise); 29 | $promiseHasResolved = false; 30 | $poolPromise->then(function ($pool) use (&$promiseHasResolved, &$poolInstance) { 31 | $promiseHasResolved = true; 32 | $poolInstance = $pool; 33 | }); 34 | $this->assertTrue($promiseHasResolved); 35 | 36 | $this->assertInstanceOf('React\Promise\PromiseInterface', $poolInstance->rpc(Factory::rpc('foo', ['bar']))); 37 | 38 | Phake::verify($manager)->ping(); 39 | } 40 | 41 | public function testInfo() 42 | { 43 | $manager = Phake::mock('WyriHaximus\React\ChildProcess\Pool\ManagerInterface'); 44 | Phake::when($manager)->info()->thenReturn([ 45 | Info::TOTAL => 1, 46 | Info::IDLE => 2, 47 | Info::BUSY => 3, 48 | ]); 49 | 50 | $queue = Phake::mock('WyriHaximus\React\ChildProcess\Pool\QueueInterface'); 51 | Phake::when($queue)->count()->thenReturn(4); 52 | 53 | $poolInstance = null; 54 | 55 | $process = Phake::mock('React\ChildProcess\Process'); 56 | $loop = Phake::mock('React\EventLoop\LoopInterface'); 57 | $poolPromise = Flexible::create($process, $loop, [ 58 | Options::MANAGER => $manager, 59 | Options::QUEUE => $queue, 60 | ]); 61 | $this->assertInstanceOf('React\Promise\PromiseInterface', $poolPromise); 62 | $promiseHasResolved = false; 63 | $poolPromise->then(function ($pool) use (&$promiseHasResolved, &$poolInstance) { 64 | $promiseHasResolved = true; 65 | $poolInstance = $pool; 66 | }); 67 | $this->assertTrue($promiseHasResolved); 68 | 69 | $this->assertSame([ 70 | Info::BUSY => 3, 71 | Info::CALLS => 4, 72 | Info::IDLE => 2, 73 | Info::SIZE => 1, 74 | ], $poolInstance->info()); 75 | } 76 | 77 | public function testTerminate() 78 | { 79 | $manager = Phake::mock('WyriHaximus\React\ChildProcess\Pool\ManagerInterface'); 80 | Phake::when($manager)->info()->thenReturn([ 81 | Info::TOTAL => 1, 82 | Info::IDLE => 2, 83 | Info::BUSY => 3, 84 | ]); 85 | 86 | $queue = Phake::mock('WyriHaximus\React\ChildProcess\Pool\QueueInterface'); 87 | Phake::when($queue)->count()->thenReturn(4); 88 | 89 | $poolInstance = null; 90 | 91 | $process = Phake::mock('React\ChildProcess\Process'); 92 | $loop = Phake::mock('React\EventLoop\LoopInterface'); 93 | Phake::when($loop)->addTimer($this->isType('integer'), $this->isType('callable'))->thenReturnCallback(function ($interval, $function) { 94 | $function(); 95 | }); 96 | $poolPromise = Flexible::create($process, $loop, [ 97 | Options::MANAGER => $manager, 98 | Options::QUEUE => $queue, 99 | ]); 100 | $this->assertInstanceOf('React\Promise\PromiseInterface', $poolPromise); 101 | $promiseHasResolved = false; 102 | $poolPromise->then(function ($pool) use (&$promiseHasResolved, &$poolInstance) { 103 | $promiseHasResolved = true; 104 | $poolInstance = $pool; 105 | }); 106 | $this->assertTrue($promiseHasResolved); 107 | 108 | $poolInstance->terminate(Factory::message(['foo' => 'bar'])); 109 | 110 | Phake::verify($manager)->terminate(); 111 | } 112 | 113 | public function testManagerReady() 114 | { 115 | $function = null; 116 | 117 | $message = Factory::rpc('beer', ['foo' => 'bar']); 118 | $worker = Phake::mock('WyriHaximus\React\ChildProcess\Pool\WorkerInterface'); 119 | $queue = Phake::mock('WyriHaximus\React\ChildProcess\Pool\QueueInterface'); 120 | $manager = Phake::mock('WyriHaximus\React\ChildProcess\Pool\ManagerInterface'); 121 | 122 | Phake::when($queue)->count()->thenReturn(4); 123 | Phake::when($queue)->dequeue()->thenReturn($message); 124 | Phake::when($manager)->on($this->isType('string'), $this->isType('callable'))->thenReturnCallback(function ($event, $passedFunction) use (&$function) { 125 | $function = $passedFunction; 126 | }); 127 | 128 | $poolInstance = null; 129 | 130 | $process = Phake::mock('React\ChildProcess\Process'); 131 | $loop = Phake::mock('React\EventLoop\LoopInterface'); 132 | Flexible::create($process, $loop, [ 133 | Options::MANAGER => $manager, 134 | Options::QUEUE => $queue, 135 | ])->then(function ($pool) use ($message) { 136 | $pool->rpc($message); 137 | }); 138 | 139 | $function($worker); 140 | 141 | // Phake::verify($worker)->rpc($message); 142 | } 143 | 144 | public function testManagerReadyQueueEmpty() 145 | { 146 | $message = Factory::rpc('beer', ['foo' => 'bar']); 147 | $worker = Phake::mock('WyriHaximus\React\ChildProcess\Pool\WorkerInterface'); 148 | $queue = Phake::mock('WyriHaximus\React\ChildProcess\Pool\QueueInterface'); 149 | $manager = Phake::mock('WyriHaximus\React\ChildProcess\Pool\ManagerInterface'); 150 | 151 | Phake::when($worker)->isBusy()->thenReturn(false); 152 | Phake::when($queue)->count()->thenReturn(0); 153 | Phake::when($queue)->dequeue()->thenReturn($message); 154 | Phake::when($manager)->on($this->isType('string'), $this->isType('callable'))->thenReturnCallback(function ($event, $function) use ($worker) { 155 | $function($worker); 156 | }); 157 | 158 | $poolInstance = null; 159 | 160 | $process = Phake::mock('React\ChildProcess\Process'); 161 | $loop = Phake::mock('React\EventLoop\LoopInterface'); 162 | Phake::when($loop)->addPeriodicTimer($this->isType('float'), $this->isType('callable'))->thenReturnCallback(function ($interval, $function) { 163 | $function(Phake::mock('React\EventLoop\TimerInterface')); 164 | }); 165 | Flexible::create($process, $loop, [ 166 | Options::MANAGER => $manager, 167 | Options::QUEUE => $queue, 168 | Options::TTL => 0, 169 | ])->then(function ($pool) use ($message) { 170 | $pool->rpc($message); 171 | }); 172 | 173 | Phake::verify($worker)->terminate(); 174 | } 175 | 176 | public function testManagerReadyQueueEmptyTtl() 177 | { 178 | $message = Factory::rpc('beer', ['foo' => 'bar']); 179 | $worker = Phake::mock('WyriHaximus\React\ChildProcess\Pool\WorkerInterface'); 180 | $queue = Phake::mock('WyriHaximus\React\ChildProcess\Pool\QueueInterface'); 181 | $manager = Phake::mock('WyriHaximus\React\ChildProcess\Pool\ManagerInterface'); 182 | 183 | Phake::when($worker)->isBusy()->thenReturn(false); 184 | $i = 0; 185 | Phake::when($queue)->count()->thenReturnCallback(function () use (&$i) { 186 | return $i++; 187 | }); 188 | Phake::when($queue)->dequeue()->thenReturn($message); 189 | Phake::when($manager)->on($this->isType('string'), $this->isType('callable'))->thenReturnCallback(function ($event, $function) use ($worker) { 190 | $function($worker); 191 | }); 192 | 193 | $poolInstance = null; 194 | 195 | $process = Phake::mock('React\ChildProcess\Process'); 196 | $loop = Phake::mock('React\EventLoop\LoopInterface'); 197 | Phake::when($loop)->addPeriodicTimer($this->isType('float'), $this->isType('callable'))->thenReturnCallback(function ($interval, $function) { 198 | $function(Phake::mock('React\EventLoop\TimerInterface')); 199 | }); 200 | Flexible::create($process, $loop, [ 201 | Options::MANAGER => $manager, 202 | Options::QUEUE => $queue, 203 | Options::TTL => 1, 204 | ])->then(function ($pool) use ($message) { 205 | $pool->rpc($message); 206 | }); 207 | 208 | Phake::verify($manager, Phake::times(2))->ping(); 209 | } 210 | 211 | public function testManagerReadyQueueEmptyIsBusy() 212 | { 213 | $message = Factory::rpc('beer', ['foo' => 'bar']); 214 | $worker = Phake::mock('WyriHaximus\React\ChildProcess\Pool\WorkerInterface'); 215 | $queue = Phake::mock('WyriHaximus\React\ChildProcess\Pool\QueueInterface'); 216 | $manager = Phake::mock('WyriHaximus\React\ChildProcess\Pool\ManagerInterface'); 217 | $timer = Phake::mock('React\EventLoop\TimerInterface'); 218 | 219 | Phake::when($worker)->isBusy()->thenReturn(true); 220 | Phake::when($queue)->count()->thenReturn(0); 221 | Phake::when($queue)->dequeue()->thenReturn($message); 222 | Phake::when($manager)->on($this->isType('string'), $this->isType('callable'))->thenReturnCallback(function ($event, $function) use ($worker) { 223 | $function($worker); 224 | }); 225 | 226 | $poolInstance = null; 227 | 228 | $process = Phake::mock('React\ChildProcess\Process'); 229 | $loop = Phake::mock('React\EventLoop\LoopInterface'); 230 | Phake::when($loop)->addPeriodicTimer($this->isType('float'), $this->isType('callable'))->thenReturnCallback(function ($interval, $function) use ($timer) { 231 | $function($timer); 232 | }); 233 | Flexible::create($process, $loop, [ 234 | Options::MANAGER => $manager, 235 | Options::QUEUE => $queue, 236 | Options::TTL => 0, 237 | ])->then(function ($pool) use ($message) { 238 | $pool->rpc($message); 239 | }); 240 | 241 | Phake::verify($loop)->cancelTimer($timer); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /tests/ProcessCollection/ArrayListTest.php: -------------------------------------------------------------------------------- 1 | assertSame($i, $arrayList->key()); 29 | $c = $arrayList->current(); 30 | $c(); 31 | $arrayList->next(); 32 | } 33 | 34 | $this->assertTrue($calledCallable); 35 | $this->assertSame($i, $calledCallableCount); 36 | 37 | $this->assertFalse($arrayList->valid()); 38 | $this->assertSame(null, $arrayList->key()); 39 | 40 | $arrayList->rewind(); 41 | 42 | $this->assertTrue($arrayList->valid()); 43 | $this->assertSame(0, $arrayList->key()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/ProcessCollection/SingleTest.php: -------------------------------------------------------------------------------- 1 | current(); 22 | $c(); 23 | } 24 | 25 | $this->assertTrue($calledCallable); 26 | $this->assertSame($i, $calledCallableCount); 27 | 28 | $this->assertFalse($single->next()); 29 | $this->assertTrue($single->valid()); 30 | $this->assertSame(0, $single->key()); 31 | 32 | $single->rewind(); 33 | 34 | $this->assertFalse($single->next()); 35 | $this->assertTrue($single->valid()); 36 | $this->assertSame(0, $single->key()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Queue/MemoryTest.php: -------------------------------------------------------------------------------- 1 | count()->thenReturnCallback(function () use ($memory) { 27 | return $memory->count(); 28 | }); 29 | Phake::when($queue)->dequeue()->thenReturnCallback(function () use ($memory) { 30 | return new FulfilledPromise($memory->dequeue()); 31 | }); 32 | Phake::when($queue)->enqueue($this->isInstanceOf('WyriHaximus\React\ChildProcess\Messenger\Messages\Rpc'))->thenReturnCallback(function (Rpc $rpc) use ($memory) { 33 | return new FulfilledPromise($memory->enqueue($rpc)); 34 | }); 35 | $overflow = new Overflow($queue, 1); 36 | $rpc0 = Factory::rpc('a', ['b']); 37 | $rpc1 = Factory::rpc('c', ['d']); 38 | $this->assertSame(0, $memory->count()); 39 | $this->assertSame(0, $overflow->count()); 40 | $overflow->enqueue($rpc0); 41 | $this->assertSame(0, $memory->count()); 42 | $this->assertSame(1, $overflow->count()); 43 | $overflow->enqueue($rpc1); 44 | $this->assertSame(1, $memory->count()); 45 | $this->assertSame(2, $overflow->count()); 46 | $this->assertSame(json_encode($rpc0), json_encode($this->dequeue($overflow->dequeue()))); 47 | $this->assertSame(0, $memory->count()); 48 | $this->assertSame(1, $overflow->count()); 49 | $this->assertSame(json_encode($rpc1), json_encode($this->dequeue($overflow->dequeue()))); 50 | $this->assertSame(0, $memory->count()); 51 | $this->assertSame(0, $overflow->count()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/Queue/QueueTestTrait.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('WyriHaximus\React\ChildProcess\Pool\QueueInterface', $this->getQueue()); 21 | } 22 | 23 | public function queueProvider() 24 | { 25 | $queue = $this->getQueue(); 26 | return array_pad([], 25, [ 27 | $queue, 28 | ]); 29 | } 30 | 31 | /** 32 | * @dataProvider queueProvider 33 | */ 34 | public function testOperations(QueueInterface $queue) 35 | { 36 | $rpc0 = Factory::rpc('a', ['b']); 37 | $rpc1 = Factory::rpc('c', ['d']); 38 | $rpc2 = Factory::rpc('e', ['f']); 39 | $this->assertSame(0, $queue->count()); 40 | $queue->enqueue($rpc0); 41 | $this->assertSame(1, $queue->count()); 42 | $queue->enqueue($rpc1); 43 | $this->assertSame(2, $queue->count()); 44 | $queue->enqueue($rpc2); 45 | $this->assertSame(3, $queue->count()); 46 | $this->assertSame(json_encode($rpc0), json_encode($this->dequeue($queue->dequeue()))); 47 | $this->assertSame(2, $queue->count()); 48 | $this->assertSame(json_encode($rpc1), json_encode($this->dequeue($queue->dequeue()))); 49 | $this->assertSame(1, $queue->count()); 50 | $this->assertSame(json_encode($rpc2), json_encode($this->dequeue($queue->dequeue()))); 51 | $this->assertSame(0, $queue->count()); 52 | } 53 | 54 | protected function dequeue($item) 55 | { 56 | return Block\await(Promise\resolve($item), LoopFactory::create(), 5); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | assertFalse($worker->isBusy()); 19 | 20 | $calledCallback = false; 21 | $worker->on('terminating', function (Worker $passedWorker) use (&$calledCallback, $worker) { 22 | $this->assertSame($worker, $passedWorker); 23 | $calledCallback = true; 24 | }); 25 | $worker->terminate(); 26 | $this->assertTrue($worker->isBusy()); 27 | $this->assertTrue($calledCallback); 28 | Phake::verify($messenger)->softTerminate(); 29 | } 30 | 31 | public function testRpc() 32 | { 33 | $deferred = new Deferred(); 34 | $rpc = Factory::rpc('t', []); 35 | $messenger = Phake::mock('WyriHaximus\React\ChildProcess\Messenger\MessengerInterface'); 36 | Phake::when($messenger)->rpc($rpc)->thenReturn($deferred->promise()); 37 | $worker = new Worker($messenger); 38 | 39 | $this->assertFalse($worker->isBusy()); 40 | $worker->rpc($rpc); 41 | $this->assertTrue($worker->isBusy()); 42 | $deferred->resolve(); 43 | $this->assertFalse($worker->isBusy()); 44 | Phake::verify($messenger)->rpc($rpc); 45 | } 46 | 47 | public function testMessage() 48 | { 49 | $message = Factory::message(['t']); 50 | $messenger = Phake::mock('WyriHaximus\React\ChildProcess\Messenger\MessengerInterface'); 51 | $worker = new Worker($messenger); 52 | 53 | $worker->message($message); 54 | Phake::verify($messenger)->message($message); 55 | } 56 | } 57 | --------------------------------------------------------------------------------