├── .github ├── FUNDING.yml ├── linters │ └── .markdown-lint.yml └── workflows │ ├── add-discuss-during-sync.yml │ ├── announce-a-release.yml │ ├── breakage-against-ponyc-latest.yml │ ├── changelog-bot.yml │ ├── generate-documentation.yml │ ├── lint-action-workflows.yml │ ├── pr.yml │ ├── prepare-for-a-release.yml │ ├── release-notes-reminder.yml │ ├── release-notes.yml │ ├── release.yml │ └── remove-discuss-during-sync.yml ├── .markdownlintignore ├── .release-notes ├── 0.1.1.md ├── 0.2.0.md ├── 0.2.1.md └── next-release.md ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── RELEASE_PROCESS.md ├── STYLE_GUIDE.md ├── VERSION ├── corral.json ├── examples └── spl4 │ └── main.pony ├── lock.json └── reactive_streams ├── broadcast.pony ├── errors.pony ├── managedpublisher.pony ├── processor.pony ├── publisher.pony ├── subscriber.pony ├── subscribermanager.pony ├── subscription.pony ├── test.pony └── unicast.pony /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: ponyc 2 | -------------------------------------------------------------------------------- /.github/linters/.markdown-lint.yml: -------------------------------------------------------------------------------- 1 | { 2 | "MD013": false 3 | } 4 | -------------------------------------------------------------------------------- /.github/workflows/add-discuss-during-sync.yml: -------------------------------------------------------------------------------- 1 | name: Add discuss during sync label 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | - reopened 8 | issue_comment: 9 | types: 10 | - created 11 | pull_request_target: 12 | types: 13 | - opened 14 | - edited 15 | - ready_for_review 16 | - reopened 17 | pull_request_review: 18 | types: 19 | - submitted 20 | 21 | permissions: 22 | pull-requests: write 23 | 24 | jobs: 25 | add-label: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Add "discuss during sync" label to active GH entity 29 | uses: andymckay/labeler@467347716a3bdbca7f277cb6cd5fa9c5205c5412 30 | with: 31 | repo-token: ${{ secrets.PONYLANG_MAIN_API_TOKEN }} 32 | add-labels: "discuss during sync" 33 | -------------------------------------------------------------------------------- /.github/workflows/announce-a-release.yml: -------------------------------------------------------------------------------- 1 | name: Announce a release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'announce-[0-9]+.[0-9]+.[0-9]+' 7 | 8 | concurrency: announce-a-release 9 | 10 | jobs: 11 | announce: 12 | name: Announcements 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout main 16 | uses: actions/checkout@v4.1.1 17 | with: 18 | ref: "main" 19 | token: ${{ secrets.RELEASE_TOKEN }} 20 | - name: Release notes 21 | uses: docker://ghcr.io/ponylang/release-bot-action:0.6.3 22 | with: 23 | entrypoint: publish-release-notes-to-github 24 | env: 25 | RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} 26 | - name: Zulip 27 | uses: docker://ghcr.io/ponylang/release-bot-action:0.6.3 28 | with: 29 | entrypoint: send-announcement-to-pony-zulip 30 | env: 31 | ZULIP_API_KEY: ${{ secrets.ZULIP_RELEASE_API_KEY }} 32 | ZULIP_EMAIL: ${{ secrets.ZULIP_RELEASE_EMAIL }} 33 | - name: Last Week in Pony 34 | uses: docker://ghcr.io/ponylang/release-bot-action:0.6.3 35 | with: 36 | entrypoint: add-announcement-to-last-week-in-pony 37 | env: 38 | RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} 39 | 40 | post-announcement: 41 | name: Tasks to run after the release has been announced 42 | needs: 43 | - announce 44 | runs-on: ubuntu-latest 45 | steps: 46 | - name: Checkout main 47 | uses: actions/checkout@v4.1.1 48 | with: 49 | ref: "main" 50 | token: ${{ secrets.RELEASE_TOKEN }} 51 | - name: Rotate release notes 52 | uses: docker://ghcr.io/ponylang/release-bot-action:0.6.3 53 | with: 54 | entrypoint: rotate-release-notes 55 | env: 56 | GIT_USER_NAME: "Ponylang Main Bot" 57 | GIT_USER_EMAIL: "ponylang.main@gmail.com" 58 | - name: Delete announcement trigger tag 59 | uses: docker://ghcr.io/ponylang/release-bot-action:0.6.3 60 | with: 61 | entrypoint: delete-announcement-tag 62 | env: 63 | GIT_USER_NAME: "Ponylang Main Bot" 64 | GIT_USER_EMAIL: "ponylang.main@gmail.com" 65 | -------------------------------------------------------------------------------- /.github/workflows/breakage-against-ponyc-latest.yml: -------------------------------------------------------------------------------- 1 | name: ponyc update breakage test 2 | 3 | on: 4 | repository_dispatch: 5 | types: [shared-docker-linux-builders-updated] 6 | 7 | jobs: 8 | vs-ponyc-latest: 9 | name: Test against ponyc main 10 | runs-on: ubuntu-latest 11 | container: 12 | image: ghcr.io/ponylang/shared-docker-ci-x86-64-unknown-linux-builder:release 13 | steps: 14 | - uses: actions/checkout@v4.1.1 15 | - name: Test 16 | run: make test 17 | - name: Send alert on failure 18 | if: ${{ failure() }} 19 | uses: zulip/github-actions-zulip/send-message@e4c8f27c732ba9bd98ac6be0583096dea82feea5 20 | with: 21 | api-key: ${{ secrets.ZULIP_SCHEDULED_JOB_FAILURE_API_KEY }} 22 | email: ${{ secrets.ZULIP_SCHEDULED_JOB_FAILURE_EMAIL }} 23 | organization-url: 'https://ponylang.zulipchat.com/' 24 | to: notifications 25 | type: stream 26 | topic: ${{ github.repository }} scheduled job failure 27 | content: ${{ github.server_url}}/${{ github.repository }}/actions/runs/${{ github.run_id }} failed. 28 | -------------------------------------------------------------------------------- /.github/workflows/changelog-bot.yml: -------------------------------------------------------------------------------- 1 | name: Changelog Bot 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | tags-ignore: 8 | - '**' 9 | paths-ignore: 10 | - CHANGELOG.md 11 | 12 | permissions: 13 | packages: read 14 | pull-requests: read 15 | contents: write 16 | 17 | jobs: 18 | changelog-bot: 19 | runs-on: ubuntu-latest 20 | name: Update CHANGELOG.md 21 | steps: 22 | - name: Update Changelog 23 | uses: docker://ghcr.io/ponylang/changelog-bot-action:0.3.7 24 | with: 25 | GIT_USER_NAME: "Ponylang Main Bot" 26 | GIT_USER_EMAIL: "ponylang.main@gmail.com" 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | - name: Send alert on failure 30 | if: ${{ failure() }} 31 | uses: zulip/github-actions-zulip/send-message@e4c8f27c732ba9bd98ac6be0583096dea82feea5 32 | with: 33 | api-key: ${{ secrets.ZULIP_SCHEDULED_JOB_FAILURE_API_KEY }} 34 | email: ${{ secrets.ZULIP_SCHEDULED_JOB_FAILURE_EMAIL }} 35 | organization-url: 'https://ponylang.zulipchat.com/' 36 | to: notifications 37 | type: stream 38 | topic: ${{ github.repository }} unattended job failure 39 | content: ${{ github.server_url}}/${{ github.repository }}/actions/runs/${{ github.run_id }} failed. 40 | -------------------------------------------------------------------------------- /.github/workflows/generate-documentation.yml: -------------------------------------------------------------------------------- 1 | name: Manually generate documentation 2 | 3 | on: 4 | workflow_dispatch 5 | 6 | permissions: 7 | contents: read 8 | pages: write 9 | id-token: write 10 | packages: read 11 | 12 | concurrency: 13 | group: "update-documentation" 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | generate-documentation: 18 | name: Generate documentation for release 19 | environment: 20 | name: github-pages 21 | url: ${{ steps.deployment.outputs.page_url }} 22 | runs-on: ubuntu-latest 23 | container: 24 | image: ghcr.io/ponylang/library-documentation-action-v2-insiders:release 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v4.1.1 28 | - name: Generate documentation 29 | run: /entrypoint.py 30 | env: 31 | INPUT_SITE_URL: "https://ponylang.github.io/reactive_streams/" 32 | INPUT_LIBRARY_NAME: "reactive_streams" 33 | INPUT_DOCS_BUILD_DIR: "build/reactive_streams-docs" 34 | - name: Setup Pages 35 | uses: actions/configure-pages@v5 36 | - name: Upload artifact 37 | uses: actions/upload-pages-artifact@v3 38 | with: 39 | path: 'build/reactive_streams-docs/site/' 40 | - name: Deploy to GitHub Pages 41 | id: deployment 42 | uses: actions/deploy-pages@v4 43 | -------------------------------------------------------------------------------- /.github/workflows/lint-action-workflows.yml: -------------------------------------------------------------------------------- 1 | name: Lint GitHub Action Workflows 2 | 3 | on: pull_request 4 | 5 | concurrency: 6 | group: lint-actions-${{ github.ref }} 7 | cancel-in-progress: true 8 | 9 | permissions: 10 | packages: read 11 | 12 | jobs: 13 | lint: 14 | name: Lint 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4.1.1 19 | - name: Check workflow files 20 | uses: docker://ghcr.io/ponylang/shared-docker-ci-actionlint:20250119 21 | with: 22 | args: -color 23 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: PR 2 | 3 | on: pull_request 4 | 5 | concurrency: 6 | group: pr-${{ github.ref }} 7 | cancel-in-progress: true 8 | 9 | jobs: 10 | superlinter: 11 | name: Lint bash, docker, markdown, and yaml 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4.1.1 15 | - name: Lint codebase 16 | uses: docker://github/super-linter:v3.8.3 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | VALIDATE_ALL_CODEBASE: true 20 | VALIDATE_BASH: true 21 | VALIDATE_DOCKERFILE: true 22 | VALIDATE_MD: true 23 | VALIDATE_YAML: true 24 | 25 | verify-changelog: 26 | name: Verify CHANGELOG is valid 27 | runs-on: ubuntu-latest 28 | container: 29 | image: ghcr.io/ponylang/changelog-tool:release 30 | steps: 31 | - uses: actions/checkout@v4.1.1 32 | - name: Verify CHANGELOG 33 | run: changelog-tool verify 34 | 35 | vs-ponyc-release: 36 | name: Test against recent ponyc release 37 | runs-on: ubuntu-latest 38 | container: 39 | image: ghcr.io/ponylang/shared-docker-ci-x86-64-unknown-linux-builder:release 40 | steps: 41 | - uses: actions/checkout@v4.1.1 42 | - name: Test 43 | run: make test 44 | -------------------------------------------------------------------------------- /.github/workflows/prepare-for-a-release.yml: -------------------------------------------------------------------------------- 1 | name: Prepare for a release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'release-[0-9]+.[0-9]+.[0-9]+' 7 | 8 | concurrency: prepare-for-a-release 9 | 10 | jobs: 11 | # all tasks that need to be done before we add an X.Y.Z tag 12 | # should be done as a step in the pre-tagging job. 13 | # think of it like this... if when you later checkout the tag for a release, 14 | # should the change be there? if yes, do it here. 15 | pre-tagging: 16 | name: Tasks run before tagging the release 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout main 20 | uses: actions/checkout@v4.1.1 21 | with: 22 | ref: "main" 23 | token: ${{ secrets.RELEASE_TOKEN }} 24 | - name: Update CHANGELOG.md 25 | uses: docker://ghcr.io/ponylang/release-bot-action:0.6.3 26 | with: 27 | entrypoint: update-changelog-for-release 28 | env: 29 | GIT_USER_NAME: "Ponylang Main Bot" 30 | GIT_USER_EMAIL: "ponylang.main@gmail.com" 31 | - name: Update VERSION 32 | uses: docker://ghcr.io/ponylang/release-bot-action:0.6.3 33 | with: 34 | entrypoint: update-version-in-VERSION 35 | env: 36 | GIT_USER_NAME: "Ponylang Main Bot" 37 | GIT_USER_EMAIL: "ponylang.main@gmail.com" 38 | - name: Update version in README 39 | uses: docker://ghcr.io/ponylang/release-bot-action:0.6.3 40 | with: 41 | entrypoint: update-version-in-README 42 | env: 43 | GIT_USER_NAME: "Ponylang Main Bot" 44 | GIT_USER_EMAIL: "ponylang.main@gmail.com" 45 | - name: Update corral.json 46 | uses: docker://ghcr.io/ponylang/release-bot-action:0.6.3 47 | with: 48 | entrypoint: update-version-in-corral-json 49 | env: 50 | GIT_USER_NAME: "Ponylang Main Bot" 51 | GIT_USER_EMAIL: "ponylang.main@gmail.com" 52 | 53 | # tag for release 54 | # this will kick off the next stage of the release process 55 | # no additional steps should be added to this job 56 | tag-release: 57 | name: Tag the release 58 | needs: 59 | - pre-tagging 60 | runs-on: ubuntu-latest 61 | steps: 62 | - name: Checkout main 63 | uses: actions/checkout@v4.1.1 64 | with: 65 | ref: "main" 66 | token: ${{ secrets.RELEASE_TOKEN }} 67 | - name: Trigger artefact creation 68 | uses: docker://ghcr.io/ponylang/release-bot-action:0.6.3 69 | with: 70 | entrypoint: trigger-artefact-creation 71 | env: 72 | GIT_USER_NAME: "Ponylang Main Bot" 73 | GIT_USER_EMAIL: "ponylang.main@gmail.com" 74 | 75 | # all cleanup tags that should happen after tagging for release should happen 76 | # in the post-tagging job. examples of things you might do: 77 | # add a new unreleased section to a changelog 78 | # set a version back to "snapshot" 79 | # in general, post-tagging is for "going back to normal" from tasks that were 80 | # done during the pre-tagging job 81 | post-tagging: 82 | name: Tasks run after tagging the release 83 | needs: 84 | - tag-release 85 | runs-on: ubuntu-latest 86 | steps: 87 | - name: Checkout main 88 | uses: actions/checkout@v4.1.1 89 | with: 90 | ref: "main" 91 | token: ${{ secrets.RELEASE_TOKEN }} 92 | - name: Add "unreleased" section to CHANGELOG.md 93 | uses: docker://ghcr.io/ponylang/release-bot-action:0.6.3 94 | with: 95 | entrypoint: add-unreleased-section-to-changelog 96 | env: 97 | GIT_USER_NAME: "Ponylang Main Bot" 98 | GIT_USER_EMAIL: "ponylang.main@gmail.com" 99 | -------------------------------------------------------------------------------- /.github/workflows/release-notes-reminder.yml: -------------------------------------------------------------------------------- 1 | name: Release Notes Reminder 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - labeled 7 | 8 | permissions: 9 | packages: read 10 | pull-requests: write 11 | 12 | jobs: 13 | release-note-reminder: 14 | runs-on: ubuntu-latest 15 | name: Prompt to add release notes 16 | steps: 17 | - name: Prompt to add release notes 18 | uses: docker://ghcr.io/ponylang/release-notes-reminder-bot-action:0.1.1 19 | env: 20 | API_CREDENTIALS: ${{ secrets.PONYLANG_MAIN_API_TOKEN }} 21 | 22 | -------------------------------------------------------------------------------- /.github/workflows/release-notes.yml: -------------------------------------------------------------------------------- 1 | name: Release Notes 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | tags-ignore: 8 | - '**' 9 | paths-ignore: 10 | - .release-notes/next-release.md 11 | - .release-notes/\d+.\d+.\d+.md 12 | 13 | permissions: 14 | packages: read 15 | pull-requests: read 16 | contents: write 17 | 18 | jobs: 19 | release-notes: 20 | runs-on: ubuntu-latest 21 | name: Update release notes 22 | steps: 23 | - name: Update 24 | uses: docker://ghcr.io/ponylang/release-notes-bot-action:0.3.10 25 | with: 26 | GIT_USER_NAME: "Ponylang Main Bot" 27 | GIT_USER_EMAIL: "ponylang.main@gmail.com" 28 | env: 29 | API_CREDENTIALS: ${{ secrets.GITHUB_TOKEN }} 30 | - name: Send alert on failure 31 | if: ${{ failure() }} 32 | uses: zulip/github-actions-zulip/send-message@e4c8f27c732ba9bd98ac6be0583096dea82feea5 33 | with: 34 | api-key: ${{ secrets.ZULIP_SCHEDULED_JOB_FAILURE_API_KEY }} 35 | email: ${{ secrets.ZULIP_SCHEDULED_JOB_FAILURE_EMAIL }} 36 | organization-url: 'https://ponylang.zulipchat.com/' 37 | to: notifications 38 | type: stream 39 | topic: ${{ github.repository }} scheduled job failure 40 | content: ${{ github.server_url}}/${{ github.repository }}/actions/runs/${{ github.run_id }} failed. 41 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '[0-9]+.[0-9]+.[0-9]+' 7 | 8 | concurrency: release 9 | 10 | permissions: 11 | contents: read 12 | pages: write 13 | id-token: write 14 | packages: read 15 | 16 | jobs: 17 | # validation to assure that we should in fact continue with the release should 18 | # be done here. the primary reason for this step is to verify that the release 19 | # was started correctly by pushing a `release-X.Y.Z` tag rather than `X.Y.Z`. 20 | pre-artefact-creation: 21 | name: Tasks to run before artefact creation 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Checkout main 25 | uses: actions/checkout@v4.1.1 26 | with: 27 | ref: "main" 28 | token: ${{ secrets.RELEASE_TOKEN }} 29 | - name: Validate CHANGELOG 30 | uses: docker://ghcr.io/ponylang/release-bot-action:0.6.3 31 | with: 32 | entrypoint: pre-artefact-changelog-check 33 | 34 | generate-documentation: 35 | name: Generate documentation for release 36 | environment: 37 | name: github-pages 38 | url: ${{ steps.deployment.outputs.page_url }} 39 | runs-on: ubuntu-latest 40 | needs: 41 | - pre-artefact-creation 42 | container: 43 | image: ghcr.io/ponylang/library-documentation-action-v2-insiders:release 44 | steps: 45 | - name: Checkout 46 | uses: actions/checkout@v4.1.1 47 | with: 48 | ref: "main" 49 | token: ${{ secrets.RELEASE_TOKEN }} 50 | - name: Generate documentation 51 | run: /entrypoint.py 52 | env: 53 | INPUT_SITE_URL: "https://ponylang.github.io/reactive_streams/" 54 | INPUT_LIBRARY_NAME: "reactive_streams" 55 | INPUT_DOCS_BUILD_DIR: "build/reactive_streams-docs" 56 | - name: Setup Pages 57 | uses: actions/configure-pages@v5 58 | - name: Upload artifact 59 | uses: actions/upload-pages-artifact@v3 60 | with: 61 | path: 'build/reactive_streams-docs/site/' 62 | - name: Deploy to GitHub Pages 63 | id: deployment 64 | uses: actions/deploy-pages@v4 65 | 66 | trigger-release-announcement: 67 | name: Trigger release announcement 68 | runs-on: ubuntu-latest 69 | needs: 70 | - generate-documentation 71 | steps: 72 | - uses: actions/checkout@v4.1.1 73 | with: 74 | ref: "main" 75 | token: ${{ secrets.RELEASE_TOKEN }} 76 | - name: Trigger 77 | uses: docker://ghcr.io/ponylang/release-bot-action:0.6.3 78 | with: 79 | entrypoint: trigger-release-announcement 80 | env: 81 | GIT_USER_NAME: "Ponylang Main Bot" 82 | GIT_USER_EMAIL: "ponylang.main@gmail.com" 83 | -------------------------------------------------------------------------------- /.github/workflows/remove-discuss-during-sync.yml: -------------------------------------------------------------------------------- 1 | name: Remove discuss during sync label 2 | 3 | on: 4 | issues: 5 | types: 6 | - closed 7 | pull_request_target: 8 | types: 9 | - closed 10 | 11 | permissions: 12 | pull-requests: write 13 | 14 | jobs: 15 | remove-label: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Remove label 19 | uses: andymckay/labeler@467347716a3bdbca7f277cb6cd5fa9c5205c5412 20 | with: 21 | repo-token: ${{ secrets.PONYLANG_MAIN_API_TOKEN }} 22 | remove-labels: "discuss during sync" 23 | -------------------------------------------------------------------------------- /.markdownlintignore: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | CODE_OF_CONDUCT.md 3 | .release-notes/ 4 | -------------------------------------------------------------------------------- /.release-notes/0.1.1.md: -------------------------------------------------------------------------------- 1 | ## Internal only release 2 | 3 | This was release was purely for non-public facing changes. 4 | -------------------------------------------------------------------------------- /.release-notes/0.2.0.md: -------------------------------------------------------------------------------- 1 | ## Update to work with Pony 0.47.0 2 | 3 | Pony 0.47.0 introduce a change to disallow interfaces from having private functions. We've updated to match. 4 | 5 | -------------------------------------------------------------------------------- /.release-notes/0.2.1.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ponylang/reactive_streams/1e812b50bd6e6400c16643ae006a52e6c18b0c16/.release-notes/0.2.1.md -------------------------------------------------------------------------------- /.release-notes/next-release.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ponylang/reactive_streams/1e812b50bd6e6400c16643ae006a52e6c18b0c16/.release-notes/next-release.md -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/) and [Keep a CHANGELOG](http://keepachangelog.com/). 4 | 5 | ## [unreleased] - unreleased 6 | 7 | ### Fixed 8 | 9 | 10 | ### Added 11 | 12 | 13 | ### Changed 14 | 15 | 16 | ## [0.2.1] - 2022-02-26 17 | 18 | ## [0.2.0] - 2022-02-02 19 | 20 | ### Changed 21 | 22 | - Update to work with Pony 0.47.0 ([PR #24](https://github.com/ponylang/reactive_streams/pull/24)) 23 | 24 | ## [0.1.1] - 2021-02-08 25 | 26 | ## [0.1.0] - 2020-05-10 27 | 28 | ### Added 29 | 30 | - Initial release 31 | 32 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project maintainers at coc@ponylang.io. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | 48 | # Social Rules 49 | 50 | In addition to having a code of conduct as an anti-harassment policy, we have a small set of [social rules](https://www.recurse.com/manual#sub-sec-social-rules) we follow. We (the project maintainers) lifted these rules from the [Recurse Center](https://www.recurse.com). We've seen these rules in effect in other environments. We'd like the Pony community to share a similar positive environment. These rules are intended to be lightweight, and to make more explicit certain social norms that are normally implicit. Most of our social rules really boil down to “don't be a jerk” or “don't be annoying.” Of course, almost nobody sets out to be a jerk or annoying, so telling people not to be jerks isn't a very productive strategy. 51 | 52 | Unlike the anti-harassment policy, violation of the social rules will not result in expulsion from the Pony community or a strong warning from project maintainers. Rather, they are designed to provide some lightweight social structure for community members to use when interacting with each other. 53 | 54 | ## No feigning surprise. 55 | 56 | The first rule means you shouldn't act surprised when people say they don't know something. This applies to both technical things ("What?! I can't believe you don't know what the stack is!") and non-technical things ("You don't know who RMS is?!"). Feigning surprise has absolutely no social or educational benefit: When people feign surprise, it's usually to make them feel better about themselves and others feel worse. And even when that's not the intention, it's almost always the effect. 57 | 58 | ## No well-actually's 59 | 60 | A well-actually happens when someone says something that's almost - but not entirely - correct, and you say, "well, actually…" and then give a minor correction. This is especially annoying when the correction has no bearing on the actual conversation. This doesn't mean we aren't about truth-seeking or that we don't care about being precise. Almost all well-actually's in our experience are about grandstanding, not truth-seeking. 61 | 62 | ## No subtle -isms 63 | 64 | Our last social rule bans subtle racism, sexism, homophobia, transphobia, and other kinds of bias. This one is different from the rest, because it covers a class of behaviors instead of one very specific pattern. 65 | 66 | Subtle -isms are small things that make others feel uncomfortable, things that we all sometimes do by mistake. For example, saying "It's so easy my grandmother could do it" is a subtle -ism. Like the other three social rules, this one is often accidentally broken. Like the other three, it's not a big deal to mess up – you just apologize and move on. 67 | 68 | If you see a subtle -ism in the Pony community, you can point it out to the relevant person, either publicly or privately, or you can ask one of the project maintainers to say something. After this, we ask that all further discussion move off of public channels. If you are a third party, and you don't see what could be biased about the comment that was made, feel free to talk to the project maintainers. Please don't say, "Comment X wasn't homophobic!" Similarly, please don't pile on to someone who made a mistake. The "subtle" in "subtle -isms" means that it's probably not obvious to everyone right away what was wrong with the comment. 69 | 70 | If you have any questions about any part of the code of conduct or social rules, please feel free to reach out to any of the project maintainers. 71 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | You want to contribute to reactive_streams? Awesome. 4 | 5 | There are a number of ways to contribute. As this document is a little long, feel free to jump to the section that applies to you currently: 6 | 7 | * [Bug report](#bug-report) 8 | * [How to contribute](#how-to-contribute) 9 | * [Pull request](#pull-request) 10 | 11 | Additional notes regarding formatting: 12 | 13 | * [Documentation formatting](#documentation-formatting) 14 | * [Code formatting](#code-formatting) 15 | * [File naming](#file-naming) 16 | 17 | ## Bug report 18 | 19 | First of all please [search existing issues](https://github.com/ponylang/reactive_streams/issues) to make sure your issue hasn't already been reported. If you cannot find a suitable issue — [create a new one](https://github.com/ponylang/reactive_streams/issues/new). 20 | 21 | Provide the following details: 22 | 23 | * short summary of what you were trying to achieve, 24 | * a code snippet causing the bug, 25 | * expected result, 26 | * actual results and 27 | * environment details: at least operating system version 28 | 29 | If possible, try to isolate the problem and provide just enough code to demonstrate it. Add any related information which might help to fix the issue. 30 | 31 | ## How to contribute 32 | 33 | This project uses a fairly standard GitHub pull request workflow. If you have already contributed to a project via GitHub pull request, you can skip this section and proceed to the [specific details of what we ask for in a pull request](#pull-request). If this is your first time contributing to a project via GitHub, read on. 34 | 35 | Here is the basic GitHub workflow: 36 | 37 | 1. Fork this repo. you can do this via the GitHub website. This will result in you having your own copy of the repo under your GitHub account. 38 | 2. Clone your forked repo to your local machine 39 | 3. Make a branch for your change 40 | 4. Make your change on that branch 41 | 5. Push your change to your repo 42 | 6. Use the github ui to open a PR 43 | 44 | Some things to note that aren't immediately obvious to folks just starting out: 45 | 46 | 1. Your fork doesn't automatically stay up to date with changes in the main repo. 47 | 2. Any changes you make on your branch that you used for one PR will automatically appear in another PR so if you have more than 1 PR, be sure to always create different branches for them. 48 | 3. Weird things happen with commit history if you don't create your PR branches off of `main` so always make sure you have the `main` branch checked out before creating a branch for a PR 49 | 50 | You can get help using GitHub via [the official documentation](https://help.github.com/). Some highlights include: 51 | 52 | * [Fork A Repo](https://help.github.com/articles/fork-a-repo/) 53 | * [Creating a pull request](https://help.github.com/articles/creating-a-pull-request/) 54 | * [Syncing a fork](https://help.github.com/articles/syncing-a-fork/) 55 | 56 | ## Pull request 57 | 58 | While we don't require that your pull request be a single commit, note that we will end up squashing all your commits into a single commit when we merge. While your PR is in review, we may ask for additional changes, please do not squash those commits while the review is underway. We ask that you not squash while a review is underway as it can make it hard to follow what is going on. 59 | 60 | When opening your pull request, please make sure that the initial comment on the PR is the commit message we should use when we merge. Making sure your commit message conforms to these guidelines for [writ(ing) a good commit message](http://chris.beams.io/posts/git-commit/). 61 | 62 | Make sure to issue 1 pull request per feature. Don't lump unrelated changes together. If you find yourself using the word "and" in your commit comment, you 63 | are probably doing too much for a single PR. 64 | 65 | We keep a [CHANGELOG](CHANGELOG.md) of all software changes with behavioural effects in ponyc. If your PR includes such changes (rather than say a documentation update), a Pony team member will do the following before merging it, so that the PR will be automatically added to the CHANGELOG: 66 | 67 | * Ensure that the ticket is tagged with one or more appropriate "changelog - *" labels - each label corresponds to a section of the changelog where this change will be automatically mentioned. 68 | * Ensure that the ticket title is appropriate - the title will be used as the summary of the change, so it should be appropriately formatted, including a ticket reference if the PR is a fix to an existing bug ticket. 69 | * For example, an appropriate title for a PR that fixes a bug reported in issue ticket #98 might look like: 70 | * *Fixed compiler crash related to tuple recovery (issue #98)* 71 | 72 | Once those conditions are met, the PR can be merged, and an automated system will immediately add the entry to the changelog. Keeping the changelog entries out of the file changes in the PR helps to avoid conflicts and other administrative headaches when many PRs are in progress. 73 | 74 | Any change that involves a changelog entry will trigger a bot to request that you add release notes to your PR. 75 | 76 | Pull requests from accounts that aren't members of the Ponylang organization require approval from a member before running. Approval is required after each update that you make. This could involve a lot of waiting on your part for approvals. If you are opening PRs to verify that changes all pass CI before "opening it for real", we strongly suggest that you open the PR against the `main` branch of your fork. CI will then run in your fork and you don't need to wait for approval from a Ponylang member. 77 | 78 | ## Documentation formatting 79 | 80 | When contributing to documentation, try to keep the following style guidelines in mind: 81 | 82 | As much as possible all documentation should be textual and in Markdown format. Diagrams are often needed to clarify a point. For any images, an original high-resolution source should be provided as well so updates can be made. 83 | 84 | Documentation is not "source code." As such, it should not be wrapped at 80 columns. Documentation should be allowed to flow naturally until the end of a paragraph. It is expected that the reader will turn on soft wrapping as needed. 85 | 86 | All code examples in documentation should be formatted in a fashion appropriate to the language in question. 87 | 88 | All command line examples in documentation should be presented in a copy and paste friendly fashion. Assume the user is using the `bash` shell. GitHub formatting on long command lines can be unfriendly to copy-and-paste. Long command lines should be broken up using `\` so that each line is no more than 80 columns. Wrapping at 80 columns should result in a good display experience in GitHub. Additionally, continuation lines should be indented two spaces. 89 | 90 | OK: 91 | 92 | ```bash 93 | my_command --some-option foo --path-to-file ../../project/long/line/foo \ 94 | --some-other-option bar 95 | ``` 96 | 97 | Not OK: 98 | 99 | ```bash 100 | my_command --some-option foo --path-to-file ../../project/long/line/foo --some-other-option bar 101 | ``` 102 | 103 | Wherever possible when writing documentation, favor full command options rather than short versions. Full flags are usually much easier to modify because the meaning is clearer. 104 | 105 | OK: 106 | 107 | ```bash 108 | my_command --messages 100 109 | ``` 110 | 111 | Not OK: 112 | 113 | ```bash 114 | my_command -m 100 115 | ``` 116 | 117 | ## Code formatting 118 | 119 | The basics: 120 | 121 | * Indentation 122 | 123 | Indent using spaces, not tabs. Indentation is language specific. 124 | 125 | * Watch your whitespace! 126 | 127 | Use an editor plugin to remove unused trailing whitespace including both at the end of a line and at the end of a file. By the same token, remember to leave a single newline only line at the end of each file. It makes output files to the console much more pleasant. 128 | 129 | * Line Length 130 | 131 | We all have different sized monitors. What might look good on yours might look like awful on another. Be kind and wrap all lines at 80 columns unless you have a good reason not to. 132 | 133 | * Reformatting code to meet standards 134 | 135 | Try to avoid doing it. A commit that changes the formatting for large chunks of a file makes for an ugly commit history when looking for changes. Don't commit code that doesn't conform to coding standards in the first place. If you do reformat code, make sure it is either standalone reformatting with no logic changes or confined solely to code whose logic you touched. For example, updating the indentation in a file? Do not make logic changes along with it. Editing a line that has extra whitespace at the end? Feel free to remove it. 136 | 137 | The details: 138 | 139 | All Pony sources should follow the [Pony standard library style guide](https://github.com/ponylang/ponyc/blob/main/STYLE_GUIDE.md). 140 | 141 | ## File naming 142 | 143 | Pony code follows the [Pony standard library file naming guidelines](https://github.com/ponylang/ponyc/blob/main/STYLE_GUIDE.md#naming). 144 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2015, The Pony Developers 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | config ?= release 2 | 3 | PACKAGE := reactive_streams 4 | GET_DEPENDENCIES_WITH := corral fetch 5 | CLEAN_DEPENDENCIES_WITH := corral clean 6 | COMPILE_WITH := corral run -- ponyc 7 | 8 | BUILD_DIR ?= build/$(config) 9 | SRC_DIR := $(PACKAGE) 10 | EXAMPLES_DIR := examples 11 | tests_binary := $(BUILD_DIR)/$(PACKAGE) 12 | docs_dir := build/$(PACKAGE)-docs 13 | 14 | ifdef config 15 | ifeq (,$(filter $(config),debug release)) 16 | $(error Unknown configuration "$(config)") 17 | endif 18 | endif 19 | 20 | ifeq ($(config),release) 21 | PONYC = $(COMPILE_WITH) 22 | else 23 | PONYC = $(COMPILE_WITH) --debug 24 | endif 25 | 26 | SOURCE_FILES := $(shell find $(SRC_DIR) -name *.pony) 27 | EXAMPLES := $(notdir $(shell find $(EXAMPLES_DIR)/* -type d)) 28 | EXAMPLES_SOURCE_FILES := $(shell find $(EXAMPLES_DIR) -name *.pony) 29 | EXAMPLES_BINARIES := $(addprefix $(BUILD_DIR)/,$(EXAMPLES)) 30 | 31 | test: unit-tests build-examples 32 | 33 | unit-tests: $(tests_binary) 34 | $^ --exclude=integration --sequential 35 | 36 | $(tests_binary): $(SOURCE_FILES) | $(BUILD_DIR) 37 | $(GET_DEPENDENCIES_WITH) 38 | $(PONYC) -o ${BUILD_DIR} $(SRC_DIR) 39 | 40 | build-examples: $(EXAMPLES_BINARIES) 41 | 42 | $(EXAMPLES_BINARIES): $(BUILD_DIR)/%: $(SOURCE_FILES) $(EXAMPLES_SOURCE_FILES) | $(BUILD_DIR) 43 | $(GET_DEPENDENCIES_WITH) 44 | $(PONYC) -o $(BUILD_DIR) $(EXAMPLES_DIR)/$* 45 | 46 | clean: 47 | $(CLEAN_DEPENDENCIES_WITH) 48 | rm -rf $(BUILD_DIR) 49 | 50 | $(docs_dir): $(SOURCE_FILES) 51 | rm -rf $(docs_dir) 52 | $(GET_DEPENDENCIES_WITH) 53 | $(PONYC) --docs-public --pass=docs --output build $(SRC_DIR) 54 | 55 | docs: $(docs_dir) 56 | 57 | TAGS: 58 | ctags --recurse=yes $(SRC_DIR) 59 | 60 | all: test 61 | 62 | $(BUILD_DIR): 63 | mkdir -p $(BUILD_DIR) 64 | 65 | .PHONY: all build-examples clean TAGS test 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # reactive_streams 2 | 3 | Pony implementation of [http://www.reactive-streams.org/](http://www.reactive-streams.org/). 4 | 5 | ## Status 6 | 7 | Beta 8 | 9 | ## Installation 10 | 11 | * Install [corral](https://github.com/ponylang/corral) 12 | * `corral add github.com/ponylang/reactive_streams.git --version 0.2.1` 13 | * `corral fetch` to fetch your dependencies 14 | * `use "reactive_streams"` to include this package 15 | * `corral run -- ponyc` to compile your application 16 | 17 | ## API Documentation 18 | 19 | [https://ponylang.github.io/reactive_streams/](https://ponylang.github.io/reactive_streams/) 20 | -------------------------------------------------------------------------------- /RELEASE_PROCESS.md: -------------------------------------------------------------------------------- 1 | # How to cut a reactive_streams release 2 | 3 | This document is aimed at members of the Pony team who might be cutting a release of reactive_streams. It serves as a checklist that can take you through doing a release step-by-step. 4 | 5 | ## Prerequisites 6 | 7 | You must have commit access to the reactive_streams repository 8 | 9 | ## Releasing 10 | 11 | Please note that this document was written with the assumption that you are using a clone of the `reactive_streams` repo. You have to be using a clone rather than a fork. It is advised that you do this by making a fresh clone of the `reactive_streams` repo from which you will release. 12 | 13 | ```bash 14 | git clone git@github.com:ponylang/reactive_streams.git reactive_streams-release-clean 15 | cd reactive_streams-release-clean 16 | ``` 17 | 18 | Before getting started, you will need a number for the version that you will be releasing as well as an agreed upon "golden commit" that will form the basis of the release. 19 | 20 | The "golden commit" must be `HEAD` on the `main` branch of this repository. At this time, releasing from any other location is not supported. 21 | 22 | In this document, we pretend that the new release version is `0.3.1`. Wherever you see those values, please substitute with your own version. 23 | 24 | ```bash 25 | git tag release-0.3.1 26 | git push origin release-0.3.1 27 | ``` 28 | -------------------------------------------------------------------------------- /STYLE_GUIDE.md: -------------------------------------------------------------------------------- 1 | # Style Guide 2 | 3 | reactive_streams follows the [Pony standard library Style Guide](https://github.com/ponylang/ponyc/blob/main/STYLE_GUIDE.md). 4 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.2.1 2 | -------------------------------------------------------------------------------- /corral.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "reactive_streams" 4 | ], 5 | "deps": [], 6 | "info": { 7 | "description": "Pony implementation of reactive streams", 8 | "homepage": "https://github.com/ponylang/reactive_streams", 9 | "license": "BSD-2-Clause", 10 | "documentation_url": "https://ponylang.github.io/reactive_streams/", 11 | "version": "0.2.1", 12 | "name": "reactive_streams" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/spl4/main.pony: -------------------------------------------------------------------------------- 1 | use "../../reactive_streams" 2 | use "time" 3 | use "collections" 4 | 5 | use @printf[I32](fmt: Pointer[U8] tag, ...) 6 | 7 | primitive Defaults 8 | fun tag items(): U64 => 1000//1 << 20 9 | fun tag producers(): U64 => 32 10 | fun tag processors(): U64 => 32 11 | fun tag consumers(): U64 => 32 12 | fun tag cap(): U64 => 256 13 | fun tag sinks(): U64 => producers() * processors() * consumers() 14 | fun tag nexts(): F64 => items().f64() * sinks().f64() 15 | 16 | actor Main 17 | let _env: Env 18 | var start: U64 = 0 19 | var repetitions: U64 = 16 20 | var arrived: U64 = 0 21 | 22 | new create(env: Env) => 23 | _env = env 24 | restart() 25 | 26 | be arrive() => 27 | arrived = arrived + 1 28 | 29 | if arrived == Defaults.sinks() then 30 | restart() 31 | 32 | let elapsed = Time.nanos() - start 33 | let secs = elapsed.f64() / F64(1000 * 1000 * 1000) 34 | let ips = (Defaults.nexts() / secs).u64() 35 | 36 | _env.out.print("Time: " + secs.string()) 37 | _env.out.print(" items per sec: " + ips.string()) 38 | 39 | arrived = 0 40 | end 41 | 42 | be restart() => 43 | start = Time.nanos() 44 | 45 | if (repetitions = repetitions - 1) > 1 then 46 | for prod in Range[U64](0, Defaults.producers()) do 47 | Pub.compute(this) 48 | end 49 | end 50 | 51 | actor Sub is Subscriber[Bool] 52 | let _main: Main 53 | var _count: U64 = 0 54 | var _sub: Subscription = NoSubscription 55 | 56 | new create(main: Main) => 57 | _main = main 58 | 59 | be on_subscribe(s: Subscription iso) => 60 | _sub = consume s 61 | _sub.request(Defaults.cap()) 62 | 63 | be on_next(a: Bool) => 64 | _count = _count + 1 65 | 66 | if ((_count and ((Defaults.cap() >> 1) - 1)) == 0) then 67 | _sub.request(Defaults.cap() >> 1) 68 | end 69 | 70 | be on_complete() => 71 | _main.arrive() 72 | 73 | be on_error(e: ReactiveError) => 74 | None 75 | 76 | actor Proc is ManagedPublisher[Bool] 77 | let _broadcast: Broadcast[Bool] 78 | var _sub: Subscription = NoSubscription 79 | var _count: U64 = 0 80 | 81 | new create() => 82 | _broadcast = Broadcast[Bool](this, Defaults.cap()) 83 | 84 | fun ref _subscriber_manager(): SubscriberManager[Bool] => 85 | _broadcast 86 | 87 | be on_subscribe(s: Subscription iso) => 88 | _sub = consume s 89 | _sub.request(Defaults.cap()) 90 | 91 | be on_next(a: Bool) => 92 | _count = _count + 1 93 | 94 | if (_count and ((Defaults.cap() >> 1) - 1)) == 0 then 95 | _sub.request(Defaults.cap() >> 1) 96 | end 97 | 98 | _broadcast.publish(a) 99 | 100 | if _broadcast.queue_size() > 0 then 101 | @printf("PUB %p queue %lu\n".cstring(), 102 | this, _broadcast.queue_size()) 103 | end 104 | 105 | be on_error(e: ReactiveError) => None 106 | 107 | be on_complete() => _broadcast.on_complete() 108 | 109 | be attach_to(pub: Pub) => pub.subscribe(this) 110 | 111 | actor Pub is ManagedPublisher[Bool] 112 | let _broadcast: Broadcast[Bool] 113 | var _remaining: U64 114 | 115 | new compute(main: Main) => 116 | _broadcast = Broadcast[Bool](this, Defaults.cap()) 117 | _remaining = Defaults.items() 118 | 119 | for j in Range[U64](0, Defaults.processors()) do 120 | let t = Proc 121 | for i in Range[U64](0, Defaults.consumers()) do 122 | t.subscribe(Sub(main)) 123 | end 124 | t.attach_to(this) 125 | end 126 | 127 | be on_request(s: Subscriber[Bool], n: U64) => 128 | _broadcast.on_request(s, n) 129 | 130 | if _broadcast.subscriber_count() == Defaults.processors() then 131 | let send = _remaining.min(_broadcast.min_request()) 132 | 133 | if send > 0 then 134 | _remaining = _remaining - send 135 | 136 | for i in Range[U64](0, send) do 137 | _broadcast.publish(true) 138 | end 139 | 140 | if _remaining == 0 then 141 | _broadcast.on_complete() 142 | end 143 | end 144 | end 145 | 146 | fun ref _subscriber_manager(): SubscriberManager[Bool] => 147 | _broadcast 148 | -------------------------------------------------------------------------------- /lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "locks": [] 3 | } 4 | -------------------------------------------------------------------------------- /reactive_streams/broadcast.pony: -------------------------------------------------------------------------------- 1 | use "collections" 2 | 3 | class Broadcast[A: Any #share] is SubscriberManager[A] 4 | """ 5 | Broadcasts data to all subscribers. 6 | """ 7 | let _pub: ManagedPublisher[A] 8 | let _map: MapIs[Subscriber[A], _SubscriberState] = _map.create() 9 | let _queue: List[A] = _queue.create() 10 | let _qbound: U64 11 | var _min_request: U64 = 0 12 | var _max_request: U64 = 0 13 | 14 | new create(pub: ManagedPublisher[A], qbound: U64 = U64.max_value()) => 15 | """ 16 | Create a Broadcast for a given ManagedPublisher. 17 | """ 18 | _pub = pub 19 | _qbound = if qbound == 0 then 1 else qbound end 20 | 21 | fun min_request(): U64 => 22 | """ 23 | Returns the lowest request count of all subscribers. 24 | """ 25 | _min_request 26 | 27 | fun max_request(): U64 => 28 | """ 29 | Returns the highest request count of all subscribers. 30 | """ 31 | _max_request 32 | 33 | fun queue_bound(): U64 => 34 | """ 35 | Returns the queue bound. 36 | """ 37 | _qbound 38 | 39 | fun queue_size(): U64 => 40 | """ 41 | Returns the current queue size. 42 | """ 43 | _queue.size().u64() 44 | 45 | fun subscriber_count(): U64 => 46 | """ 47 | Returns the current subscriber count. 48 | """ 49 | _map.size().u64() 50 | 51 | fun ref publish(a: A) => 52 | """ 53 | Send data to all subscribers. 54 | """ 55 | if _map.size() == 0 then 56 | // Drop if we have no subscribers. 57 | return 58 | end 59 | 60 | if _max_request == 0 then 61 | // No subscribers have pending demand. 62 | if _queue.size().u64() == _qbound then 63 | try _queue.shift()? end 64 | end 65 | 66 | _queue.push(a) 67 | return 68 | end 69 | 70 | if _min_request > 0 then 71 | // All subscribers have pending demand. Send the new data to them all, 72 | // reducing each one's request count. Also reduce _min_request. 73 | _min_request = _min_request - 1 74 | _max_request = _max_request - 1 75 | 76 | for (sub, state) in _map.pairs() do 77 | state.request = state.request - 1 78 | sub.on_next(a) 79 | end 80 | 81 | return 82 | end 83 | 84 | if _queue.size().u64() == _qbound then 85 | // We have hit our bound and must drop from the queue. 86 | try 87 | _queue.shift()? 88 | _queue.push(a) 89 | 90 | for (sub, state) in _map.pairs() do 91 | if state.request > 0 then 92 | // The subscriber has pending demand. Leave the queue_position 93 | // where it is, but reduce the request count. 94 | state.request = state.request - 1 95 | sub.on_next(a) 96 | else 97 | // The subscriber has no pending demand. Since we dropped from the 98 | // queue, move the queue_position back one. 99 | if state.queue_position > 0 then 100 | state.queue_position = state.queue_position - 1 101 | end 102 | end 103 | end 104 | 105 | return 106 | end 107 | end 108 | 109 | // We aren't at the queue bound. 110 | _queue.push(a) 111 | let pos = _queue.size().u64() 112 | 113 | for (sub, state) in _map.pairs() do 114 | if state.request > 0 then 115 | // The subscriber has pending demand. Move the queue_position to the 116 | // current tail, and reduce the request count. 117 | state.request = state.request - 1 118 | state.queue_position = pos 119 | sub.on_next(a) 120 | end 121 | end 122 | 123 | fun ref on_complete() => 124 | """ 125 | A ManagedPublisher should call this when it has no more data to produce. 126 | """ 127 | for sub in _map.keys() do 128 | sub.on_complete() 129 | end 130 | _reset() 131 | 132 | fun ref on_error(e: ReactiveError) => 133 | """ 134 | A ManagedPublisher should call this when its internal state has resulted in 135 | an error that should be propagated to all subscribers. 136 | """ 137 | for sub in _map.keys() do 138 | sub.on_error(e) 139 | end 140 | _reset() 141 | 142 | fun ref _reset() => 143 | """ 144 | Reset after completion or error. 145 | """ 146 | _map.clear() 147 | _queue.clear() 148 | _min_request = 0 149 | _max_request = 0 150 | 151 | fun ref on_subscribe(sub: Subscriber[A]) => 152 | """ 153 | A ManagedPublisher should call this when it receives Publisher.subscribe. 154 | """ 155 | let prev = _map(sub) = _SubscriberState 156 | 157 | if prev isnt None then 158 | sub.on_error(SubscribedAlready) 159 | end 160 | 161 | sub.on_subscribe(_Subscription[A](sub, _pub)) 162 | _min_request = 0 163 | 164 | fun ref on_request(sub: Subscriber[A], n: U64) => 165 | """ 166 | A ManagedPublisher should call this when it receives 167 | ManagedPublisher._on_request. 168 | """ 169 | try 170 | let state = _map(sub)? 171 | var inc = n 172 | 173 | if (state.request == 0) and (state.queue_position < _queue.size().u64()) 174 | then 175 | // Send pending backlog. 176 | var count = inc.min(_queue.size().u64() - state.queue_position) 177 | 178 | try 179 | var node = _queue.index(state.queue_position.usize())? 180 | 181 | for i in Range[U64](0, count) do 182 | sub.on_next(node()?) 183 | 184 | if node.has_next() then 185 | node = node.next() as ListNode[A] 186 | end 187 | end 188 | end 189 | 190 | let adjust = state.queue_position == 0 191 | state.queue_position = state.queue_position + count 192 | inc = inc - count 193 | 194 | // Possibly djust the queue if we were blocking the head. 195 | if adjust then 196 | _adjust_queue() 197 | end 198 | end 199 | 200 | if inc > 0 then 201 | let recalc = state.request == _min_request 202 | 203 | if (state.request + inc) < state.request then 204 | state.request = U64.max_value() 205 | else 206 | state.request = state.request + inc 207 | end 208 | 209 | _max_request = _max_request.max(state.request) 210 | if recalc then _find_min_request() end 211 | end 212 | end 213 | 214 | fun ref on_cancel(sub: Subscriber[A]) => 215 | """ 216 | A ManagedPublisher should call this when it receives 217 | ManagedPublisher._on_cancel. 218 | """ 219 | try 220 | (_, let state) = _map.remove(sub)? 221 | 222 | if 223 | (state.request == 0) and 224 | (state.queue_position == 0) and 225 | (_queue.size() > 0) 226 | then 227 | // If this subscriber was blocking at the head of the queue, we may be 228 | // able to adjust the queue. 229 | _adjust_queue() 230 | end 231 | end 232 | 233 | fun ref _find_min_request() => 234 | """ 235 | Recalculate the _min_request value. 236 | """ 237 | _min_request = U64.max_value() 238 | 239 | for state in _map.values() do 240 | _min_request = _min_request.min(state.request) 241 | end 242 | 243 | fun ref _adjust_queue() => 244 | """ 245 | When a subscriber is removed or gets sent backlog data, call this to drop 246 | elements from the queue that are no longer needed. 247 | """ 248 | var min_queue_position = U64.max_value() 249 | 250 | for state in _map.values() do 251 | min_queue_position = min_queue_position.min(state.queue_position) 252 | end 253 | 254 | if min_queue_position > 0 then 255 | try 256 | for i in Range[U64](0, min_queue_position) do 257 | _queue.shift()? 258 | end 259 | end 260 | 261 | for state in _map.values() do 262 | state.queue_position = state.queue_position - min_queue_position 263 | end 264 | end 265 | 266 | class _SubscriberState 267 | """ 268 | Keeps track of the pending demand and queue position for a subscriber. 269 | """ 270 | var request: U64 = 0 271 | var queue_position: U64 = 0 272 | 273 | fun ref clear() => 274 | request = 0 275 | queue_position = 0 276 | -------------------------------------------------------------------------------- /reactive_streams/errors.pony: -------------------------------------------------------------------------------- 1 | trait val ReactiveError 2 | 3 | primitive SubscribedAlready is ReactiveError 4 | primitive PublisherFull is ReactiveError 5 | -------------------------------------------------------------------------------- /reactive_streams/managedpublisher.pony: -------------------------------------------------------------------------------- 1 | trait tag ManagedPublisher[A: Any #share] is Publisher[A] 2 | """ 3 | A ManagedPublisher must have a SubscriberManager and give access to it. 4 | """ 5 | fun ref _subscriber_manager(): SubscriberManager[A] 6 | """ 7 | Return the SubscriberManager associated with this ManagedPublisher. 8 | """ 9 | 10 | be subscribe(s: Subscriber[A]) => 11 | """ 12 | A ManagedPublisher must respond by calling SubscriberManager._on_subscribe. 13 | """ 14 | _subscriber_manager().on_subscribe(s) 15 | 16 | be on_request(s: Subscriber[A], n: U64) => 17 | """ 18 | A ManagedPublisher must respond by calling SubscriberManager._on_request. 19 | """ 20 | _subscriber_manager().on_request(s, n) 21 | 22 | be on_cancel(s: Subscriber[A]) => 23 | """ 24 | A ManagedPublisher must respond by calling SubscriberManager._on_cancel. 25 | """ 26 | _subscriber_manager().on_cancel(s) 27 | -------------------------------------------------------------------------------- /reactive_streams/processor.pony: -------------------------------------------------------------------------------- 1 | interface tag Processor[A: Any #share, B: Any #share] is (Subscriber[A] & Publisher[B]) 2 | """ 3 | A Processor represents a processing stage—which is both a Subscriber and a 4 | Publisher and obeys the contracts of both. 5 | """ 6 | -------------------------------------------------------------------------------- /reactive_streams/publisher.pony: -------------------------------------------------------------------------------- 1 | interface tag Publisher[A: Any #share] 2 | """ 3 | A Publisher is a provider of a potentially unbounded number of sequenced 4 | elements, publishing them according to the demand received from its 5 | Subscriber(s). 6 | 7 | A Publisher can serve multiple Subscribers subscribed dynamically at various 8 | points in time. 9 | """ 10 | be subscribe(s: Subscriber[A]) 11 | """ 12 | Request Publisher to start streaming data. 13 | 14 | This is a "factory method" and can be called multiple times, each time 15 | starting a new Subscription. 16 | 17 | Each Subscription will work for only a single Subscriber. 18 | 19 | A Subscriber should only subscribe once to a single Publisher. 20 | 21 | If the Publisher rejects the subscription attempt or otherwise fails it 22 | will signal the error via Subscriber.on_error. 23 | """ 24 | -------------------------------------------------------------------------------- /reactive_streams/subscriber.pony: -------------------------------------------------------------------------------- 1 | interface tag Subscriber[A: Any #share] 2 | """ 3 | Will receive call to on_subscribe once after passing an instance of 4 | Subscriber to Publisher.subscribe. 5 | 6 | No further notifications will be received until Subscription.request is 7 | called. 8 | 9 | After signaling demand: 10 | 11 | * One or more invocations of on_next up to the maximum number defined by 12 | Subscription.request 13 | * Single invocation of on_error or on_complete which signals a terminal 14 | state after which no further events will be sent. 15 | 16 | Demand can be signaled via Subscription.request whenever the Subscriber 17 | instance is capable of handling more. 18 | """ 19 | be on_subscribe(s: Subscription iso) => 20 | """ 21 | Invoked after calling Publisher.subscribe. 22 | 23 | No data will start flowing until Subscription.request is invoked. 24 | 25 | It is the responsibility of this Subscriber instance to call 26 | Subscription.request whenever more data is wanted. 27 | 28 | The Publisher will send notifications only in response to 29 | Subscription.request. 30 | """ 31 | None 32 | 33 | be on_next(a: A) => 34 | """ 35 | Data notification sent by the Publisher in response to requests to 36 | Subscription.request. 37 | """ 38 | None 39 | 40 | be on_error(e: ReactiveError) => 41 | """ 42 | Failed terminal state. 43 | 44 | No further events will be sent even if Subscription.request is invoked 45 | again. 46 | """ 47 | None 48 | 49 | be on_complete() => 50 | """ 51 | Successful terminal state. 52 | 53 | No further events will be sent even if Subscription.request is invoked 54 | again. 55 | """ 56 | None 57 | -------------------------------------------------------------------------------- /reactive_streams/subscribermanager.pony: -------------------------------------------------------------------------------- 1 | interface SubscriberManager[A: Any #share] 2 | """ 3 | Manages a subscriber list. 4 | """ 5 | fun min_request(): U64 6 | """ 7 | Returns the lowest request count of all subscribers. 8 | """ 9 | 10 | fun max_request(): U64 11 | """ 12 | Returns the highest request count of all subscribers. 13 | """ 14 | 15 | fun queue_bound(): U64 16 | """ 17 | Returns the queue bound. 18 | """ 19 | 20 | fun queue_size(): U64 21 | """ 22 | Returns the current queue size. 23 | """ 24 | 25 | fun subscriber_count(): U64 26 | """ 27 | Returns the current subscriber count. 28 | """ 29 | 30 | fun ref publish(a: A) 31 | """ 32 | A ManagedPublisher should call this when it has data to publish. 33 | Subscribers with pending demand will be sent the data immediately. If any 34 | subscribers with no pending demand exist, the data will be kept on a 35 | queue to be sent when subscribers request additional data. 36 | 37 | The queue size can be bounded. If so, undelivered old data will be dropped 38 | if new data must be queued and the queue has hit its size limit. 39 | """ 40 | 41 | fun ref on_complete() 42 | """ 43 | A ManagedPublisher should call this when it has no more data to produce. 44 | """ 45 | 46 | fun ref on_error(e: ReactiveError) 47 | """ 48 | A ManagedPublisher should call this when its internal state has resulted in 49 | an error that should be propagated to all subscribers. 50 | """ 51 | 52 | fun ref on_subscribe(sub: Subscriber[A]) 53 | """ 54 | A ManagedPublisher should call this when it receives Publisher.subscribe. 55 | """ 56 | 57 | fun ref on_request(sub: Subscriber[A], n: U64) 58 | """ 59 | A ManagedPublisher should call this when it receives 60 | ManagedPublisher._on_request. 61 | """ 62 | 63 | fun ref on_cancel(sub: Subscriber[A]) 64 | """ 65 | A ManagedPublisher should call this when it receives 66 | ManagedPublisher._on_cancel. 67 | """ 68 | 69 | class iso _Subscription[A: Any #share] is Subscription 70 | """ 71 | Implements Subscription[A], allowing a subscriber to a ManagedPublisher to 72 | request more data or cancel its subscription. 73 | """ 74 | let _sub: Subscriber[A] 75 | let _pub: ManagedPublisher[A] 76 | var _cancelled: Bool = false 77 | 78 | new iso create(sub: Subscriber[A], pub: ManagedPublisher[A]) => 79 | """ 80 | Create a _Subscription for a given subscriber and publisher. 81 | """ 82 | _sub = sub 83 | _pub = pub 84 | 85 | fun ref request(n: U64) => 86 | """ 87 | Request more data. NOP if the subscription has been cancelled. 88 | """ 89 | if not _cancelled and (n > 0) then 90 | _pub.on_request(_sub, n) 91 | end 92 | 93 | fun ref cancel() => 94 | """ 95 | Cancel the subscription. NOP if it has already been cancelled. 96 | """ 97 | if not _cancelled then 98 | _cancelled = true 99 | _pub.on_cancel(_sub) 100 | end 101 | -------------------------------------------------------------------------------- /reactive_streams/subscription.pony: -------------------------------------------------------------------------------- 1 | interface Subscription 2 | """ 3 | A Subscription represents a one-to-one lifecycle of a Subscriber subscribing 4 | to a Publisher. 5 | 6 | It can only be used by a single Subscriber. 7 | 8 | It is used to both signal desire for data and cancel demand (and allow 9 | resource cleanup). 10 | """ 11 | fun ref request(n: U64) => 12 | """ 13 | No events will be sent by a Publisher until demand is signaled via this 14 | method. 15 | 16 | It can be called however often and whenever needed—but the outstanding 17 | cumulative demand must never exceed U64.max_value. 18 | 19 | An outstanding cumulative demand of U64.max_value may be treated by the 20 | Publisher as "effectively unbounded". 21 | 22 | Whatever has been requested can be sent by the Publisher so only signal 23 | demand for what can be safely handled. 24 | 25 | A Publisher can send less than is requested if the stream ends but then 26 | must emit either Subscriber.on_error or Subscriber.on_complete. 27 | """ 28 | None 29 | 30 | fun ref cancel() => 31 | """ 32 | Request the Publisher to stop sending data and clean up resources. 33 | 34 | Data may still be sent to meet previously signalled demand after calling 35 | cancel as this request is asynchronous. 36 | """ 37 | None 38 | 39 | class NoSubscription is Subscription 40 | -------------------------------------------------------------------------------- /reactive_streams/test.pony: -------------------------------------------------------------------------------- 1 | use "pony_test" 2 | 3 | actor \nodoc\ Main is TestList 4 | new create(env: Env) => PonyTest(env, this) 5 | 6 | fun tag tests(test: PonyTest) => 7 | test(_TestOne) 8 | 9 | class \nodoc\ _TestOne is UnitTest 10 | fun name(): String => "reactive_streams/One" 11 | 12 | fun tag apply(h: TestHelper) => 13 | let pub = _TestPublisher(h, 1) 14 | let sub = _TestSubscriber(h, pub) 15 | pub.test_one(sub) 16 | h.long_test(1_000_000_000) 17 | 18 | actor \nodoc\ _TestPublisher is ManagedPublisher[U64] 19 | let _h: TestHelper 20 | let _mgr: SubscriberManager[U64] 21 | let _subs: U64 22 | 23 | new create(h: TestHelper, subs: U64, qbounds: U64 = U64.max_value()) => 24 | _h = h 25 | _mgr = Unicast[U64](this, qbounds) 26 | _subs = subs 27 | 28 | fun ref _subscriber_manager(): SubscriberManager[U64] => 29 | _mgr 30 | 31 | be test_one(sub: _TestSubscriber) => 32 | if _mgr.subscriber_count() < _subs then 33 | test_one(sub) 34 | return 35 | end 36 | 37 | _h.assert_eq[U64](_mgr.subscriber_count(), _subs) 38 | _h.assert_eq[U64](_mgr.queue_size(), 0) 39 | _mgr.publish(1) 40 | _mgr.publish(2) 41 | _mgr.publish(3) 42 | _mgr.publish(4) 43 | _mgr.publish(5) 44 | 45 | test_one_2(sub) 46 | 47 | be test_one_2(sub: _TestSubscriber) => 48 | if _mgr.queue_size() > 0 then 49 | test_one_2(sub) 50 | else 51 | sub.test_one_2() 52 | end 53 | 54 | actor \nodoc\ _TestSubscriber is Subscriber[U64] 55 | let _h: TestHelper 56 | let _pub: _TestPublisher 57 | var _sub: Subscription = NoSubscription 58 | var _sum: U64 = 0 59 | 60 | new create(h: TestHelper, pub: _TestPublisher) => 61 | _h = h 62 | _pub = pub 63 | _pub.subscribe(this) 64 | 65 | be on_subscribe(sub: Subscription iso) => 66 | _sub = consume sub 67 | _sub.request(1) 68 | 69 | be on_next(i: U64) => 70 | _sum = _sum + i 71 | _sub.request(1) 72 | 73 | be test_one_2() => 74 | _h.assert_eq[U64](_sum, 15) 75 | _h.complete(true) 76 | -------------------------------------------------------------------------------- /reactive_streams/unicast.pony: -------------------------------------------------------------------------------- 1 | use "collections" 2 | 3 | class Unicast[A: Any #share] 4 | """ 5 | Send data to a single subscriber. 6 | """ 7 | let _pub: ManagedPublisher[A] 8 | var _sub: (Subscriber[A] | None) = None 9 | var _request: U64 = 0 10 | let _queue: List[A] = _queue.create() 11 | let _qbound: U64 12 | 13 | new create(pub: ManagedPublisher[A], qbound: U64 = U64.max_value()) => 14 | """ 15 | Create a Unicast for a given ManagedPublisher. 16 | """ 17 | _pub = pub 18 | _qbound = if qbound == 0 then 1 else qbound end 19 | 20 | fun min_request(): U64 => 21 | """ 22 | Returns the lowest request count of all subscribers. 23 | """ 24 | _request 25 | 26 | fun max_request(): U64 => 27 | """ 28 | Returns the highest request count of all subscribers. 29 | """ 30 | _request 31 | 32 | fun queue_bound(): U64 => 33 | """ 34 | Returns the queue bound. 35 | """ 36 | _qbound 37 | 38 | fun queue_size(): U64 => 39 | """ 40 | Returns the current queue size. 41 | """ 42 | _queue.size().u64() 43 | 44 | fun subscriber_count(): U64 => 45 | """ 46 | Returns the current subscriber count. 47 | """ 48 | if _sub is None then 0 else 1 end 49 | 50 | fun ref publish(a: A) => 51 | """ 52 | Send data to the subscriber. 53 | """ 54 | try 55 | let sub = _sub as Subscriber[A] 56 | 57 | if _request > 0 then 58 | _request = _request - 1 59 | sub.on_next(a) 60 | else 61 | if _queue.size().u64() == _qbound then 62 | _queue.shift()? 63 | end 64 | 65 | _queue.push(a) 66 | end 67 | end 68 | 69 | fun ref on_complete() => 70 | """ 71 | A ManagedPublisher should call this when it has no more data to produce. 72 | """ 73 | try 74 | let sub = _sub as Subscriber[A] 75 | sub.on_complete() 76 | _sub = None 77 | _request = 0 78 | end 79 | 80 | _queue.clear() 81 | 82 | fun ref on_error(e: ReactiveError) => 83 | """ 84 | A ManagedPublisher should call this when its internal state has resulted in 85 | an error that should be propagated to all subscribers. 86 | """ 87 | try 88 | let sub = _sub as Subscriber[A] 89 | sub.on_error(e) 90 | _sub = None 91 | _request = 0 92 | end 93 | 94 | _queue.clear() 95 | 96 | fun ref on_subscribe(sub: Subscriber[A]) => 97 | """ 98 | A ManagedPublisher should call this when it receives Publisher.subscribe. 99 | """ 100 | if _sub is None then 101 | _sub = sub 102 | sub.on_subscribe(_Subscription[A](sub, _pub)) 103 | elseif _sub is sub then 104 | _request = 0 105 | _queue.clear() 106 | 107 | sub.on_error(SubscribedAlready) 108 | sub.on_subscribe(_Subscription[A](sub, _pub)) 109 | else 110 | let subscription = _Subscription[A](sub, _pub) 111 | subscription.cancel() 112 | sub.on_subscribe(consume subscription) 113 | sub.on_error(PublisherFull) 114 | end 115 | 116 | fun ref on_request(sub: Subscriber[A], n: U64) => 117 | """ 118 | A ManagedPublisher should call this when it receives 119 | ManagedPublisher._on_request. 120 | """ 121 | if sub is _sub then 122 | _request = _request + n 123 | 124 | while (_queue.size() > 0) and (_request > 0) do 125 | try 126 | sub.on_next(_queue.shift()?) 127 | _request = _request - 1 128 | end 129 | end 130 | end 131 | 132 | fun ref on_cancel(sub: Subscriber[A]) => 133 | """ 134 | A ManagedPublisher should call this when it receives 135 | ManagedPublisher._on_cancel. 136 | """ 137 | if sub is _sub then 138 | _sub = None 139 | _request = 0 140 | _queue.clear() 141 | end 142 | --------------------------------------------------------------------------------