├── .editorconfig ├── .flake8 ├── .github ├── ISSUE_TEMPLATE.md ├── release_log.py └── workflows │ ├── release.yml │ └── tests.yml ├── .gitignore ├── .gitmodules ├── LICENSE-APACHE ├── LICENSE-MIT ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs ├── .gitignore ├── api │ └── index.rst ├── conf.py ├── dev │ └── index.rst ├── index.rst └── user │ └── index.rst ├── examples └── bench │ ├── echoclient.py │ ├── echoserver.py │ └── rlserver.py ├── mypy.ini ├── performance.png ├── pyproject.toml ├── setup.py ├── tests ├── __init__.py ├── __main__.py ├── certs │ ├── ssl_cert.pem │ └── ssl_key.pem ├── test_aiohttp.py ├── test_base.py ├── test_context.py ├── test_cython.py ├── test_dealloc.py ├── test_dns.py ├── test_executors.py ├── test_fs_event.py ├── test_libuv_api.py ├── test_pipes.py ├── test_process.py ├── test_process_spawning.py ├── test_regr1.py ├── test_runner.py ├── test_signals.py ├── test_sockets.py ├── test_sourcecode.py ├── test_tcp.py ├── test_testbase.py ├── test_udp.py └── test_unix.py └── uvloop ├── .gitignore ├── __init__.py ├── _noop.py ├── _testbase.py ├── _version.py ├── cbhandles.pxd ├── cbhandles.pyx ├── dns.pyx ├── errors.pyx ├── handles ├── async_.pxd ├── async_.pyx ├── basetransport.pxd ├── basetransport.pyx ├── check.pxd ├── check.pyx ├── fsevent.pxd ├── fsevent.pyx ├── handle.pxd ├── handle.pyx ├── idle.pxd ├── idle.pyx ├── pipe.pxd ├── pipe.pyx ├── poll.pxd ├── poll.pyx ├── process.pxd ├── process.pyx ├── stream.pxd ├── stream.pyx ├── streamserver.pxd ├── streamserver.pyx ├── tcp.pxd ├── tcp.pyx ├── timer.pxd ├── timer.pyx ├── udp.pxd └── udp.pyx ├── includes ├── __init__.py ├── compat.h ├── consts.pxi ├── debug.h ├── debug.pxd ├── flowcontrol.pxd ├── fork_handler.h ├── python.pxd ├── stdlib.pxi ├── system.pxd └── uv.pxd ├── loop.pxd ├── loop.pyi ├── loop.pyx ├── lru.pyx ├── pseudosock.pyx ├── py.typed ├── request.pxd ├── request.pyx ├── server.pxd ├── server.pyx ├── sslproto.pxd └── sslproto.pyx /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | 7 | [Makefile] 8 | indent_style = tab 9 | 10 | [*.{py,pyx,pxd,pxi,yml,h}] 11 | indent_size = 4 12 | indent_style = space 13 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | filename = *.py,*.pyi 3 | ignore = E402,E731,D100,D101,D102,D103,D104,D105,W503,W504,E252 4 | exclude = .git,__pycache__,build,dist,.eggs,postgres,vendor 5 | 6 | per-file-ignores = 7 | *.pyx,*.pxd,*.pxi: E211, E222, E225, E226, E227, E999 8 | *.pyi: F401, F403, F405, F811, E127, E128, E203, E266, E301, E302, E305, E501, E701, E704, E741, B303, W503, W504 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | * **uvloop version**: 12 | * **Python version**: 13 | * **Platform**: 14 | * **Can you reproduce the bug with `PYTHONASYNCIODEBUG` in env?**: 15 | * **Does uvloop behave differently from vanilla asyncio? How?**: 16 | 17 | 18 | -------------------------------------------------------------------------------- /.github/release_log.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | 4 | import argparse 5 | import json 6 | import requests 7 | import re 8 | 9 | 10 | BASE_URL = 'https://api.github.com/repos/magicstack/uvloop/compare' 11 | 12 | 13 | def main(): 14 | parser = argparse.ArgumentParser( 15 | description='Generate release log.') 16 | parser.add_argument('--to', dest='to_hash', default='master', type=str) 17 | parser.add_argument('--from', dest='from_hash', type=str) 18 | args = parser.parse_args() 19 | 20 | r = requests.get(f'{BASE_URL}/{args.from_hash}...{args.to_hash}') 21 | data = json.loads(r.text) 22 | 23 | for commit in data['commits']: 24 | message = commit['commit']['message'] 25 | first_line = message.partition('\n\n')[0] 26 | if commit.get('author'): 27 | username = '@{}'.format(commit['author']['login']) 28 | else: 29 | username = commit['commit']['author']['name'] 30 | sha = commit["sha"][:8] 31 | 32 | m = re.search(r'\#(?P\d+)\b', message) 33 | if m: 34 | issue_num = m.group('num') 35 | else: 36 | issue_num = None 37 | 38 | print(f'* {first_line}') 39 | print(f' (by {username} in {sha}', end='') 40 | if issue_num: 41 | print(f' for #{issue_num})') 42 | else: 43 | print(')') 44 | print() 45 | 46 | 47 | if __name__ == '__main__': 48 | main() 49 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "master" 7 | - "ci" 8 | - "[0-9]+.[0-9x]+*" 9 | paths: 10 | - "uvloop/_version.py" 11 | 12 | jobs: 13 | validate-release-request: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Validate release PR 17 | uses: edgedb/action-release/validate-pr@bae6b9134e872166b43d218dd79397c851c41c9a 18 | id: checkver 19 | with: 20 | require_team: Release Managers 21 | require_approval: no 22 | github_token: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }} 23 | version_file: uvloop/_version.py 24 | version_line_pattern: | 25 | __version__\s*=\s*(?:['"])([[:PEP440:]])(?:['"]) 26 | 27 | - name: Stop if not approved 28 | if: steps.checkver.outputs.approved != 'true' 29 | run: | 30 | echo ::error::PR is not approved yet. 31 | exit 1 32 | 33 | - name: Store release version for later use 34 | env: 35 | VERSION: ${{ steps.checkver.outputs.version }} 36 | run: | 37 | mkdir -p dist/ 38 | echo "${VERSION}" > dist/VERSION 39 | 40 | - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 41 | with: 42 | name: dist 43 | path: dist/ 44 | 45 | build-sdist: 46 | needs: validate-release-request 47 | runs-on: ubuntu-22.04 48 | 49 | env: 50 | PIP_DISABLE_PIP_VERSION_CHECK: 1 51 | 52 | steps: 53 | - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 54 | with: 55 | fetch-depth: 50 56 | submodules: true 57 | 58 | - uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1 59 | with: 60 | python-version: 3.x 61 | 62 | - name: Build source distribution 63 | run: | 64 | python -m pip install --upgrade setuptools wheel pip 65 | python setup.py sdist 66 | 67 | - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 68 | with: 69 | name: dist 70 | path: dist/*.tar.* 71 | 72 | build-wheels: 73 | needs: validate-release-request 74 | runs-on: ${{ matrix.os }} 75 | strategy: 76 | fail-fast: false 77 | matrix: 78 | os: [ubuntu-latest, macos-latest] 79 | cibw_python: 80 | - "cp38-*" 81 | - "cp39-*" 82 | - "cp310-*" 83 | - "cp311-*" 84 | - "cp312-*" 85 | - "cp313-*" 86 | cibw_arch: ["x86_64", "aarch64", "universal2"] 87 | exclude: 88 | - os: ubuntu-latest 89 | cibw_arch: universal2 90 | - os: macos-latest 91 | cibw_arch: aarch64 92 | 93 | defaults: 94 | run: 95 | shell: bash 96 | 97 | env: 98 | PIP_DISABLE_PIP_VERSION_CHECK: 1 99 | 100 | steps: 101 | - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 102 | with: 103 | fetch-depth: 50 104 | submodules: true 105 | 106 | - name: Set up QEMU 107 | if: matrix.os == 'ubuntu-latest' && matrix.cibw_arch == 'aarch64' 108 | uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 109 | with: 110 | platforms: arm64 111 | 112 | - name: Install macOS deps 113 | if: startsWith(matrix.os, 'macos') 114 | run: | 115 | brew install gnu-sed libtool autoconf automake 116 | 117 | - uses: pypa/cibuildwheel@7940a4c0e76eb2030e473a5f864f291f63ee879b # v2.21.3 118 | env: 119 | CIBW_BUILD_VERBOSITY: 1 120 | CIBW_BUILD: ${{ matrix.cibw_python }} 121 | CIBW_ARCHS: ${{ matrix.cibw_arch }} 122 | CIBW_TEST_SKIP: "*universal2:arm64" 123 | 124 | - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 125 | with: 126 | name: dist 127 | path: wheelhouse/*.whl 128 | 129 | publish: 130 | needs: [build-sdist, build-wheels] 131 | runs-on: ubuntu-latest 132 | 133 | steps: 134 | - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 135 | with: 136 | fetch-depth: 5 137 | submodules: false 138 | 139 | - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 140 | with: 141 | name: dist 142 | path: dist/ 143 | 144 | - name: Extract Release Version 145 | id: relver 146 | run: | 147 | set -e 148 | echo version=$(cat dist/VERSION) >> $GITHUB_OUTPUT 149 | rm dist/VERSION 150 | 151 | - name: Merge and tag the PR 152 | uses: edgedb/action-release/merge@bae6b9134e872166b43d218dd79397c851c41c9a 153 | with: 154 | github_token: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }} 155 | ssh_key: ${{ secrets.RELEASE_BOT_SSH_KEY }} 156 | gpg_key: ${{ secrets.RELEASE_BOT_GPG_KEY }} 157 | gpg_key_id: "5C468778062D87BF!" 158 | tag_name: v${{ steps.relver.outputs.version }} 159 | 160 | - name: Publish Github Release 161 | uses: elprans/gh-action-create-release@master 162 | env: 163 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 164 | with: 165 | tag_name: v${{ steps.relver.outputs.version }} 166 | release_name: v${{ steps.relver.outputs.version }} 167 | target: ${{ github.event.pull_request.base.ref }} 168 | body: ${{ github.event.pull_request.body }} 169 | draft: false 170 | 171 | - run: | 172 | ls -al dist/ 173 | 174 | - name: Upload to PyPI 175 | uses: pypa/gh-action-pypi-publish@b7f401de30cb6434a1e19f805ff006643653240e # v1.8.10 176 | with: 177 | user: __token__ 178 | password: ${{ secrets.PYPI_TOKEN }} 179 | # password: ${{ secrets.TEST_PYPI_TOKEN }} 180 | # repository_url: https://test.pypi.org/legacy/ 181 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - ci 8 | pull_request: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | test: 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | python-version: 19 | - "3.8" 20 | - "3.9" 21 | - "3.10" 22 | - "3.11" 23 | - "3.12" 24 | - "3.13" 25 | os: [ubuntu-latest, macos-latest] 26 | 27 | env: 28 | PIP_DISABLE_PIP_VERSION_CHECK: 1 29 | 30 | steps: 31 | - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 32 | with: 33 | fetch-depth: 50 34 | submodules: true 35 | 36 | - name: Check if release PR. 37 | uses: edgedb/action-release/validate-pr@bae6b9134e872166b43d218dd79397c851c41c9a 38 | id: release 39 | with: 40 | github_token: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }} 41 | missing_version_ok: yes 42 | version_file: uvloop/_version.py 43 | version_line_pattern: | 44 | __version__\s*=\s*(?:['"])([[:PEP440:]])(?:['"]) 45 | 46 | - name: Set up Python ${{ matrix.python-version }} 47 | uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1 48 | if: steps.release.outputs.version == 0 49 | with: 50 | python-version: ${{ matrix.python-version }} 51 | allow-prereleases: true 52 | 53 | - name: Install macOS deps 54 | if: matrix.os == 'macos-latest' && steps.release.outputs.version == 0 55 | run: | 56 | brew install gnu-sed libtool autoconf automake 57 | 58 | - name: Install Python Deps 59 | if: steps.release.outputs.version == 0 60 | run: | 61 | pip install -e .[test,dev] 62 | 63 | - name: Test 64 | if: steps.release.outputs.version == 0 65 | run: | 66 | make test 67 | 68 | - name: Test (debug build) 69 | if: steps.release.outputs.version == 0 70 | run: | 71 | make distclean && make debug && make test 72 | 73 | # This job exists solely to act as the test job aggregate to be 74 | # targeted by branch policies. 75 | regression-tests: 76 | name: "Regression Tests" 77 | needs: [test] 78 | runs-on: ubuntu-latest 79 | 80 | steps: 81 | - run: echo OK 82 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *._* 2 | *.pyc 3 | *.pyo 4 | *.ymlc 5 | *.ymlc~ 6 | *.scssc 7 | *.so 8 | *~ 9 | .#* 10 | .DS_Store 11 | .project 12 | .pydevproject 13 | .settings 14 | .idea 15 | /.ropeproject 16 | \#*# 17 | /pub 18 | /test*.py 19 | /.local 20 | /perf.data* 21 | /config_local.yml 22 | /build 23 | __pycache__/ 24 | .d8_history 25 | /*.egg 26 | /*.egg-info 27 | /dist 28 | /.cache 29 | docs/_build 30 | uvloop/loop.*.pyd 31 | /.pytest_cache/ 32 | /.mypy_cache/ 33 | /.vscode 34 | /.eggs 35 | /.venv* 36 | /wheelhouse 37 | /uvloop-dev 38 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/libuv"] 2 | path = vendor/libuv 3 | url = https://github.com/libuv/libuv.git 4 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (C) 2016-present the uvloop authors and contributors. 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include docs *.py *.rst 2 | recursive-include examples *.py 3 | recursive-include tests *.py *.pem 4 | recursive-include uvloop *.pyx *.pxd *.pxi *.py *.c *.h *.pyi py.typed 5 | recursive-include vendor/libuv * 6 | recursive-exclude vendor/libuv/.git * 7 | recursive-exclude vendor/libuv/docs * 8 | recursive-exclude vendor/libuv/img * 9 | include LICENSE-MIT LICENSE-APACHE README.rst Makefile performance.png .flake8 mypy.ini 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: _default clean clean-libuv distclean compile debug docs test testinstalled release setup-build ci-clean 2 | 3 | 4 | PYTHON ?= python 5 | ROOT = $(dir $(realpath $(firstword $(MAKEFILE_LIST)))) 6 | 7 | 8 | _default: compile 9 | 10 | 11 | clean: 12 | rm -fr dist/ doc/_build/ *.egg-info uvloop/loop.*.pyd uvloop/loop_d.*.pyd 13 | rm -fr uvloop/*.c uvloop/*.html uvloop/*.so 14 | rm -fr uvloop/handles/*.html uvloop/includes/*.html 15 | find . -name '__pycache__' | xargs rm -rf 16 | 17 | 18 | ci-clean: clean 19 | rm -fr build/lib.* build/temp.* build/libuv 20 | 21 | 22 | clean-libuv: 23 | (cd vendor/libuv; git clean -dfX) 24 | 25 | 26 | distclean: clean clean-libuv 27 | rm -fr build/ 28 | 29 | 30 | setup-build: 31 | $(PYTHON) setup.py build_ext --inplace --cython-always 32 | 33 | 34 | compile: clean setup-build 35 | 36 | 37 | debug: clean 38 | $(PYTHON) setup.py build_ext --inplace --debug \ 39 | --cython-always \ 40 | --cython-annotate \ 41 | --cython-directives="linetrace=True" \ 42 | --define UVLOOP_DEBUG,CYTHON_TRACE,CYTHON_TRACE_NOGIL 43 | 44 | 45 | docs: 46 | $(PYTHON) setup.py build_ext --inplace build_sphinx 47 | 48 | 49 | test: 50 | PYTHONASYNCIODEBUG=1 $(PYTHON) -m unittest discover -v tests 51 | $(PYTHON) -m unittest discover -v tests 52 | 53 | 54 | testinstalled: 55 | cd "$${HOME}" && $(PYTHON) -m unittest discover -v $(ROOT)/tests 56 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://img.shields.io/github/actions/workflow/status/MagicStack/uvloop/tests.yml?branch=master 2 | :target: https://github.com/MagicStack/uvloop/actions/workflows/tests.yml?query=branch%3Amaster 3 | 4 | .. image:: https://img.shields.io/pypi/v/uvloop.svg 5 | :target: https://pypi.python.org/pypi/uvloop 6 | 7 | .. image:: https://pepy.tech/badge/uvloop 8 | :target: https://pepy.tech/project/uvloop 9 | :alt: PyPI - Downloads 10 | 11 | 12 | uvloop is a fast, drop-in replacement of the built-in asyncio 13 | event loop. uvloop is implemented in Cython and uses libuv 14 | under the hood. 15 | 16 | The project documentation can be found 17 | `here `_. Please also check out the 18 | `wiki `_. 19 | 20 | 21 | Performance 22 | ----------- 23 | 24 | uvloop makes asyncio 2-4x faster. 25 | 26 | .. image:: https://raw.githubusercontent.com/MagicStack/uvloop/master/performance.png 27 | :target: http://magic.io/blog/uvloop-blazing-fast-python-networking/ 28 | 29 | The above chart shows the performance of an echo server with different 30 | message sizes. The *sockets* benchmark uses ``loop.sock_recv()`` and 31 | ``loop.sock_sendall()`` methods; the *streams* benchmark uses asyncio 32 | high-level streams, created by the ``asyncio.start_server()`` function; 33 | and the *protocol* benchmark uses ``loop.create_server()`` with a simple 34 | echo protocol. Read more about uvloop in a 35 | `blog post `_ 36 | about it. 37 | 38 | 39 | Installation 40 | ------------ 41 | 42 | uvloop requires Python 3.8 or greater and is available on PyPI. 43 | Use pip to install it:: 44 | 45 | $ pip install uvloop 46 | 47 | Note that it is highly recommended to **upgrade pip before** installing 48 | uvloop with:: 49 | 50 | $ pip install -U pip 51 | 52 | 53 | Using uvloop 54 | ------------ 55 | 56 | As of uvloop 0.18, the preferred way of using it is via the 57 | ``uvloop.run()`` helper function: 58 | 59 | 60 | .. code:: python 61 | 62 | import uvloop 63 | 64 | async def main(): 65 | # Main entry-point. 66 | ... 67 | 68 | uvloop.run(main()) 69 | 70 | ``uvloop.run()`` works by simply configuring ``asyncio.run()`` 71 | to use uvloop, passing all of the arguments to it, such as ``debug``, 72 | e.g. ``uvloop.run(main(), debug=True)``. 73 | 74 | With Python 3.11 and earlier the following alternative 75 | snippet can be used: 76 | 77 | .. code:: python 78 | 79 | import asyncio 80 | import sys 81 | 82 | import uvloop 83 | 84 | async def main(): 85 | # Main entry-point. 86 | ... 87 | 88 | if sys.version_info >= (3, 11): 89 | with asyncio.Runner(loop_factory=uvloop.new_event_loop) as runner: 90 | runner.run(main()) 91 | else: 92 | uvloop.install() 93 | asyncio.run(main()) 94 | 95 | 96 | Building From Source 97 | -------------------- 98 | 99 | To build uvloop, you'll need Python 3.8 or greater: 100 | 101 | 1. Clone the repository: 102 | 103 | .. code:: 104 | 105 | $ git clone --recursive git@github.com:MagicStack/uvloop.git 106 | $ cd uvloop 107 | 108 | 2. Create a virtual environment and activate it: 109 | 110 | .. code:: 111 | 112 | $ python3 -m venv uvloop-dev 113 | $ source uvloop-dev/bin/activate 114 | 115 | 3. Install development dependencies: 116 | 117 | .. code:: 118 | 119 | $ pip install -e .[dev] 120 | 121 | 4. Build and run tests: 122 | 123 | .. code:: 124 | 125 | $ make 126 | $ make test 127 | 128 | 129 | License 130 | ------- 131 | 132 | uvloop is dual-licensed under MIT and Apache 2.0 licenses. 133 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | _static 3 | _templates 4 | -------------------------------------------------------------------------------- /docs/api/index.rst: -------------------------------------------------------------------------------- 1 | API 2 | === 3 | 4 | If you are looking for information on a specific function, class or method, 5 | this part of the documentation is for you. 6 | 7 | 8 | uvloop 9 | ------ 10 | 11 | .. autoclass:: uvloop.EventLoopPolicy 12 | :members: 13 | 14 | .. autofunction:: uvloop.new_event_loop 15 | 16 | .. autoclass:: uvloop.Loop 17 | :members: 18 | :undoc-members: 19 | :inherited-members: 20 | 21 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import alabaster 4 | import os 5 | import sys 6 | 7 | sys.path.insert(0, os.path.abspath('..')) 8 | 9 | version_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), 10 | 'uvloop', '_version.py') 11 | 12 | with open(version_file, 'r') as f: 13 | for line in f: 14 | if line.startswith('__version__ ='): 15 | _, _, version = line.partition('=') 16 | version = version.strip(" \n'\"") 17 | break 18 | else: 19 | raise RuntimeError( 20 | 'unable to read the version from uvloop/_version.py') 21 | 22 | 23 | # -- General configuration ------------------------------------------------ 24 | 25 | extensions = [ 26 | 'sphinx.ext.autodoc', 27 | 'alabaster', 28 | ] 29 | templates_path = ['_templates'] 30 | source_suffix = '.rst' 31 | master_doc = 'index' 32 | project = 'uvloop' 33 | copyright = '2016-present, MagicStack, Inc' 34 | author = 'Yury Selivanov' 35 | release = version 36 | language = None 37 | exclude_patterns = ['_build'] 38 | pygments_style = 'sphinx' 39 | todo_include_todos = False 40 | 41 | 42 | # -- Options for HTML output ---------------------------------------------- 43 | 44 | html_theme = 'alabaster' 45 | html_theme_options = { 46 | 'description': 'uvloop is an ultra fast implementation of the ' 47 | 'asyncio event loop on top of libuv.', 48 | 'show_powered_by': False, 49 | } 50 | html_theme_path = [alabaster.get_path()] 51 | html_title = 'uvloop Documentation' 52 | html_short_title = 'uvloop' 53 | html_static_path = [] 54 | html_sidebars = { 55 | '**': [ 56 | 'about.html', 57 | 'navigation.html', 58 | ] 59 | } 60 | html_show_sourcelink = False 61 | html_show_sphinx = False 62 | html_show_copyright = True 63 | htmlhelp_basename = 'uvloopdoc' 64 | 65 | 66 | # -- Options for LaTeX output --------------------------------------------- 67 | 68 | latex_elements = {} 69 | 70 | latex_documents = [ 71 | (master_doc, 'uvloop.tex', 'uvloop Documentation', 72 | 'Yury Selivanov', 'manual'), 73 | ] 74 | 75 | 76 | # -- Options for manual page output --------------------------------------- 77 | 78 | man_pages = [ 79 | (master_doc, 'uvloop', 'uvloop Documentation', 80 | [author], 1) 81 | ] 82 | 83 | 84 | # -- Options for Texinfo output ------------------------------------------- 85 | 86 | texinfo_documents = [ 87 | (master_doc, 'uvloop', 'uvloop Documentation', 88 | author, 'uvloop', 'One line description of project.', 89 | 'Miscellaneous'), 90 | ] 91 | -------------------------------------------------------------------------------- /docs/dev/index.rst: -------------------------------------------------------------------------------- 1 | Developers Guide 2 | ================ 3 | 4 | The project is hosted on `GitHub `_. 5 | and uses `GitHub Actions `_ for 6 | Continuous Integration. 7 | 8 | A goal for the `uvloop` project is to provide a drop in replacement for the 9 | `asyncio` event loop. Any deviation from the behavior of the reference 10 | `asyncio` event loop is considered a bug. 11 | 12 | If you have found a bug or have an idea for an enhancement that would 13 | improve the library, use the 14 | `bug tracker `_. 15 | 16 | 17 | Get the source 18 | -------------- 19 | 20 | .. code-block:: console 21 | 22 | $ git clone --recursive git@github.com:MagicStack/uvloop.git 23 | 24 | The ``--recursive`` argument is important. It will fetch the ``libuv`` source 25 | from the `libuv` Github repository. 26 | 27 | 28 | Build 29 | ----- 30 | 31 | To build `uvloop`, you'll need ``Cython`` and Python 3.8. 32 | 33 | .. note:: 34 | 35 | The best way to work on `uvloop` is to create a virtual env, so that 36 | you'll have Cython and Python commands pointing to the correct 37 | tools. 38 | 39 | .. code-block:: console 40 | 41 | $ python3 -m venv myvenv 42 | $ source myvenv/bin/activate 43 | 44 | Install Cython if not already present. 45 | 46 | .. code-block:: console 47 | 48 | $ pip install Cython 49 | 50 | 51 | Build `uvloop` by running the ``make`` rule from the top level directory. 52 | 53 | .. code-block:: console 54 | 55 | $ cd uvloop 56 | $ make 57 | 58 | 59 | Test 60 | ---- 61 | 62 | The easiest method to run all of the unit tests is to run the ``make test`` 63 | rule from the top level directory. This runs the standard library 64 | ``unittest`` tool which discovers all the unit tests and runs them. 65 | It actually runs them twice, once with the `PYTHONASYNCIODEBUG` enabled and 66 | once without. 67 | 68 | .. code-block:: console 69 | 70 | $ cd uvloop 71 | $ make test 72 | 73 | 74 | Individual Tests 75 | ++++++++++++++++ 76 | 77 | Individual unit tests can be run using the standard library ``unittest`` 78 | or ``pytest`` package. 79 | 80 | The easiest approach to ensure that ``uvloop`` can be found by Python is to 81 | install the package using ``pip``: 82 | 83 | .. code-block:: console 84 | 85 | $ cd uvloop 86 | $ pip install -e . 87 | 88 | You can then run the unit tests individually from the tests directory using 89 | ``unittest``: 90 | 91 | .. code-block:: console 92 | 93 | $ cd uvloop/tests 94 | $ python -m unittest test_tcp 95 | 96 | or using ``pytest``: 97 | 98 | .. code-block:: console 99 | 100 | $ cd uvloop/tests 101 | $ py.test -k test_signals_sigint_uvcode 102 | 103 | 104 | Documentation 105 | ------------- 106 | 107 | To rebuild the project documentation, developers should run the ``make docs`` 108 | rule from the top level directory. It performs a number of steps to create 109 | a new set of `sphinx `_ html content. 110 | 111 | This step requires Sphinx to be installed. Sphinx can be installed using 112 | pip: 113 | 114 | .. code-block:: console 115 | 116 | $ pip install sphinx 117 | 118 | Once Sphinx is available you can make the documentation using: 119 | 120 | .. code-block:: console 121 | 122 | $ make docs 123 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://img.shields.io/github/actions/workflow/status/MagicStack/uvloop/tests.yml?branch=master 2 | :target: https://github.com/MagicStack/uvloop/actions/workflows/tests.yml?query=branch%3Amaster 3 | 4 | .. image:: https://img.shields.io/pypi/status/uvloop.svg?maxAge=2592000?style=plastic 5 | :target: https://pypi.python.org/pypi/uvloop 6 | 7 | .. image:: https://img.shields.io/github/stars/magicstack/uvloop.svg?style=social&label=GitHub 8 | :target: https://github.com/MagicStack/uvloop 9 | 10 | 11 | uvloop 12 | ====== 13 | 14 | `uvloop` is a fast, drop-in replacement of the built-in asyncio event loop. 15 | `uvloop` is released under the MIT license. 16 | 17 | `uvloop` and asyncio, combined with the power of async/await in Python 3.7, 18 | makes it easier than ever to write high-performance networking code in Python. 19 | 20 | `uvloop` makes asyncio fast. In fact, it is at least 2x faster than nodejs, 21 | gevent, as well as any other Python asynchronous framework. The performance of 22 | uvloop-based asyncio is close to that of Go programs. 23 | 24 | You can read more about uvloop in this 25 | `blog post `_. 26 | 27 | Architecture 28 | ------------ 29 | 30 | The asyncio module, introduced by PEP 3156, is a collection of network 31 | transports, protocols, and streams abstractions, with a pluggable event loop. 32 | The event loop is the heart of asyncio. It provides APIs for: 33 | 34 | - scheduling calls, 35 | - transmitting data over the network, 36 | - performing DNS queries, 37 | - handling OS signals, 38 | - convenient abstractions to create servers and connections, 39 | - working with subprocesses asynchronously. 40 | 41 | `uvloop` implements the :class:`asyncio.AbstractEventLoop` interface which 42 | means that it provides a drop-in replacement of the asyncio event loop. 43 | 44 | `uvloop` is written in Cython and is built on top of libuv. 45 | 46 | libuv is a high performance, multiplatform asynchronous I/O library used by 47 | nodejs. Because of how wide-spread and popular nodejs is, libuv is fast and 48 | stable. 49 | 50 | `uvloop` implements all asyncio event loop APIs. High-level Python objects 51 | wrap low-level libuv structs and functions. Inheritance is used to keep the 52 | code DRY and ensure that any manual memory management is in sync with libuv 53 | primitives' lifespans. 54 | 55 | 56 | Contents 57 | -------- 58 | 59 | .. toctree:: 60 | :maxdepth: 1 61 | 62 | user/index 63 | dev/index 64 | -------------------------------------------------------------------------------- /docs/user/index.rst: -------------------------------------------------------------------------------- 1 | User Guide 2 | ========== 3 | 4 | This section of the documentation provides information about how to use 5 | uvloop. 6 | 7 | 8 | Installation 9 | ------------ 10 | 11 | `uvloop` is available from PyPI. It requires Python 3.8. 12 | 13 | Use pip to install it. 14 | 15 | .. code-block:: console 16 | 17 | $ pip install uvloop 18 | 19 | 20 | Using uvloop 21 | ------------ 22 | 23 | To make asyncio use the event loop provided by `uvloop`, you install the 24 | `uvloop` event loop policy: 25 | 26 | .. code-block:: python 27 | 28 | import asyncio 29 | import uvloop 30 | asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) 31 | 32 | 33 | Alternatively, you can create an instance of the loop manually, using: 34 | 35 | .. code-block:: python 36 | 37 | import asyncio 38 | import uvloop 39 | loop = uvloop.new_event_loop() 40 | asyncio.set_event_loop(loop) 41 | -------------------------------------------------------------------------------- /examples/bench/echoclient.py: -------------------------------------------------------------------------------- 1 | # Copied with minimal modifications from curio 2 | # https://github.com/dabeaz/curio 3 | 4 | 5 | import argparse 6 | import concurrent.futures 7 | import socket 8 | import ssl 9 | import time 10 | 11 | 12 | if __name__ == '__main__': 13 | parser = argparse.ArgumentParser() 14 | parser.add_argument('--msize', default=1000, type=int, 15 | help='message size in bytes') 16 | parser.add_argument('--mpr', default=1, type=int, 17 | help='messages per request') 18 | parser.add_argument('--num', default=200000, type=int, 19 | help='number of messages') 20 | parser.add_argument('--times', default=1, type=int, 21 | help='number of times to run the test') 22 | parser.add_argument('--workers', default=3, type=int, 23 | help='number of workers') 24 | parser.add_argument('--addr', default='127.0.0.1:25000', type=str, 25 | help='address:port of echoserver') 26 | parser.add_argument('--ssl', default=False, action='store_true') 27 | args = parser.parse_args() 28 | 29 | client_context = None 30 | if args.ssl: 31 | print('with SSL') 32 | if hasattr(ssl, 'PROTOCOL_TLS'): 33 | client_context = ssl.SSLContext(ssl.PROTOCOL_TLS) 34 | else: 35 | client_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) 36 | if hasattr(client_context, 'check_hostname'): 37 | client_context.check_hostname = False 38 | client_context.verify_mode = ssl.CERT_NONE 39 | 40 | unix = False 41 | if args.addr.startswith('file:'): 42 | unix = True 43 | addr = args.addr[5:] 44 | else: 45 | addr = args.addr.split(':') 46 | addr[1] = int(addr[1]) 47 | addr = tuple(addr) 48 | print('will connect to: {}'.format(addr)) 49 | 50 | MSGSIZE = args.msize 51 | REQSIZE = MSGSIZE * args.mpr 52 | 53 | msg = b'x' * (MSGSIZE - 1) + b'\n' 54 | if args.mpr: 55 | msg *= args.mpr 56 | 57 | def run_test(n): 58 | print('Sending', NMESSAGES, 'messages') 59 | if args.mpr: 60 | n //= args.mpr 61 | 62 | if unix: 63 | sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 64 | else: 65 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 66 | 67 | try: 68 | sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 69 | except (OSError, NameError): 70 | pass 71 | 72 | if client_context: 73 | sock = client_context.wrap_socket(sock) 74 | 75 | sock.connect(addr) 76 | 77 | while n > 0: 78 | sock.sendall(msg) 79 | nrecv = 0 80 | while nrecv < REQSIZE: 81 | resp = sock.recv(REQSIZE) 82 | if not resp: 83 | raise SystemExit() 84 | nrecv += len(resp) 85 | n -= 1 86 | 87 | TIMES = args.times 88 | N = args.workers 89 | NMESSAGES = args.num 90 | start = time.time() 91 | for _ in range(TIMES): 92 | with concurrent.futures.ProcessPoolExecutor(max_workers=N) as e: 93 | for _ in range(N): 94 | e.submit(run_test, NMESSAGES) 95 | end = time.time() 96 | duration = end - start 97 | print(NMESSAGES * N * TIMES, 'in', duration) 98 | print(NMESSAGES * N * TIMES / duration, 'requests/sec') 99 | -------------------------------------------------------------------------------- /examples/bench/echoserver.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import gc 4 | import os.path 5 | import pathlib 6 | import socket 7 | import ssl 8 | 9 | 10 | PRINT = 0 11 | 12 | 13 | async def echo_server(loop, address, unix): 14 | if unix: 15 | sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 16 | else: 17 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 18 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 19 | sock.bind(address) 20 | sock.listen(5) 21 | sock.setblocking(False) 22 | if PRINT: 23 | print('Server listening at', address) 24 | with sock: 25 | while True: 26 | client, addr = await loop.sock_accept(sock) 27 | if PRINT: 28 | print('Connection from', addr) 29 | loop.create_task(echo_client(loop, client)) 30 | 31 | 32 | async def echo_client(loop, client): 33 | try: 34 | client.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 35 | except (OSError, NameError): 36 | pass 37 | 38 | with client: 39 | while True: 40 | data = await loop.sock_recv(client, 1000000) 41 | if not data: 42 | break 43 | await loop.sock_sendall(client, data) 44 | if PRINT: 45 | print('Connection closed') 46 | 47 | 48 | async def echo_client_streams(reader, writer): 49 | sock = writer.get_extra_info('socket') 50 | try: 51 | sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 52 | except (OSError, NameError): 53 | pass 54 | if PRINT: 55 | print('Connection from', sock.getpeername()) 56 | while True: 57 | data = await reader.read(1000000) 58 | if not data: 59 | break 60 | writer.write(data) 61 | if PRINT: 62 | print('Connection closed') 63 | writer.close() 64 | 65 | 66 | class EchoProtocol(asyncio.Protocol): 67 | def connection_made(self, transport): 68 | self.transport = transport 69 | 70 | def connection_lost(self, exc): 71 | self.transport = None 72 | 73 | def data_received(self, data): 74 | self.transport.write(data) 75 | 76 | 77 | class EchoBufferedProtocol(asyncio.BufferedProtocol): 78 | def connection_made(self, transport): 79 | self.transport = transport 80 | # Here the buffer is intended to be copied, so that the outgoing buffer 81 | # won't be wrongly updated by next read 82 | self.buffer = bytearray(256 * 1024) 83 | 84 | def connection_lost(self, exc): 85 | self.transport = None 86 | 87 | def get_buffer(self, sizehint): 88 | return self.buffer 89 | 90 | def buffer_updated(self, nbytes): 91 | self.transport.write(self.buffer[:nbytes]) 92 | 93 | 94 | async def print_debug(loop): 95 | while True: 96 | print(chr(27) + "[2J") # clear screen 97 | loop.print_debug_info() 98 | await asyncio.sleep(0.5) 99 | 100 | 101 | if __name__ == '__main__': 102 | parser = argparse.ArgumentParser() 103 | parser.add_argument('--uvloop', default=False, action='store_true') 104 | parser.add_argument('--streams', default=False, action='store_true') 105 | parser.add_argument('--proto', default=False, action='store_true') 106 | parser.add_argument('--addr', default='127.0.0.1:25000', type=str) 107 | parser.add_argument('--print', default=False, action='store_true') 108 | parser.add_argument('--ssl', default=False, action='store_true') 109 | parser.add_argument('--buffered', default=False, action='store_true') 110 | args = parser.parse_args() 111 | 112 | if args.uvloop: 113 | import uvloop 114 | loop = uvloop.new_event_loop() 115 | print('using UVLoop') 116 | else: 117 | loop = asyncio.new_event_loop() 118 | print('using asyncio loop') 119 | 120 | asyncio.set_event_loop(loop) 121 | loop.set_debug(False) 122 | 123 | if args.print: 124 | PRINT = 1 125 | 126 | if hasattr(loop, 'print_debug_info'): 127 | loop.create_task(print_debug(loop)) 128 | PRINT = 0 129 | 130 | unix = False 131 | if args.addr.startswith('file:'): 132 | unix = True 133 | addr = args.addr[5:] 134 | if os.path.exists(addr): 135 | os.remove(addr) 136 | else: 137 | addr = args.addr.split(':') 138 | addr[1] = int(addr[1]) 139 | addr = tuple(addr) 140 | 141 | print('serving on: {}'.format(addr)) 142 | 143 | server_context = None 144 | if args.ssl: 145 | print('with SSL') 146 | if hasattr(ssl, 'PROTOCOL_TLS'): 147 | server_context = ssl.SSLContext(ssl.PROTOCOL_TLS) 148 | else: 149 | server_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) 150 | server_context.load_cert_chain( 151 | (pathlib.Path(__file__).parent.parent.parent / 152 | 'tests' / 'certs' / 'ssl_cert.pem'), 153 | (pathlib.Path(__file__).parent.parent.parent / 154 | 'tests' / 'certs' / 'ssl_key.pem')) 155 | if hasattr(server_context, 'check_hostname'): 156 | server_context.check_hostname = False 157 | server_context.verify_mode = ssl.CERT_NONE 158 | 159 | if args.streams: 160 | if args.proto: 161 | print('cannot use --stream and --proto simultaneously') 162 | exit(1) 163 | 164 | if args.buffered: 165 | print('cannot use --stream and --buffered simultaneously') 166 | exit(1) 167 | 168 | print('using asyncio/streams') 169 | if unix: 170 | coro = asyncio.start_unix_server(echo_client_streams, 171 | addr, 172 | ssl=server_context) 173 | else: 174 | coro = asyncio.start_server(echo_client_streams, 175 | *addr, 176 | ssl=server_context) 177 | srv = loop.run_until_complete(coro) 178 | elif args.proto: 179 | if args.streams: 180 | print('cannot use --stream and --proto simultaneously') 181 | exit(1) 182 | 183 | if args.buffered: 184 | print('using buffered protocol') 185 | protocol = EchoBufferedProtocol 186 | else: 187 | print('using simple protocol') 188 | protocol = EchoProtocol 189 | 190 | if unix: 191 | coro = loop.create_unix_server(protocol, addr, 192 | ssl=server_context) 193 | else: 194 | coro = loop.create_server(protocol, *addr, 195 | ssl=server_context) 196 | srv = loop.run_until_complete(coro) 197 | else: 198 | if args.ssl: 199 | print('cannot use SSL for loop.sock_* methods') 200 | exit(1) 201 | 202 | print('using sock_recv/sock_sendall') 203 | loop.create_task(echo_server(loop, addr, unix)) 204 | try: 205 | loop.run_forever() 206 | finally: 207 | if hasattr(loop, 'print_debug_info'): 208 | gc.collect() 209 | print(chr(27) + "[2J") 210 | loop.print_debug_info() 211 | 212 | loop.close() 213 | -------------------------------------------------------------------------------- /examples/bench/rlserver.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import gc 4 | import os.path 5 | import socket as stdsock 6 | 7 | 8 | PRINT = 0 9 | 10 | 11 | async def echo_client_streams(reader, writer): 12 | sock = writer.get_extra_info('socket') 13 | try: 14 | sock.setsockopt( 15 | stdsock.IPPROTO_TCP, stdsock.TCP_NODELAY, 1) 16 | except (OSError, NameError): 17 | pass 18 | if PRINT: 19 | print('Connection from', sock.getpeername()) 20 | while True: 21 | data = await reader.readline() 22 | if not data: 23 | break 24 | writer.write(data) 25 | if PRINT: 26 | print('Connection closed') 27 | writer.close() 28 | 29 | 30 | async def print_debug(loop): 31 | while True: 32 | print(chr(27) + "[2J") # clear screen 33 | loop.print_debug_info() 34 | await asyncio.sleep(0.5) 35 | 36 | 37 | if __name__ == '__main__': 38 | parser = argparse.ArgumentParser() 39 | parser.add_argument('--uvloop', default=False, action='store_true') 40 | parser.add_argument('--addr', default='127.0.0.1:25000', type=str) 41 | parser.add_argument('--print', default=False, action='store_true') 42 | args = parser.parse_args() 43 | 44 | if args.uvloop: 45 | import uvloop 46 | loop = uvloop.new_event_loop() 47 | print('using UVLoop') 48 | else: 49 | loop = asyncio.new_event_loop() 50 | print('using asyncio loop') 51 | 52 | asyncio.set_event_loop(loop) 53 | loop.set_debug(False) 54 | 55 | if args.print: 56 | PRINT = 1 57 | 58 | if hasattr(loop, 'print_debug_info'): 59 | loop.create_task(print_debug(loop)) 60 | PRINT = 0 61 | 62 | unix = False 63 | if args.addr.startswith('file:'): 64 | unix = True 65 | addr = args.addr[5:] 66 | if os.path.exists(addr): 67 | os.remove(addr) 68 | else: 69 | addr = args.addr.split(':') 70 | addr[1] = int(addr[1]) 71 | addr = tuple(addr) 72 | 73 | print('readline performance test') 74 | print('serving on: {}'.format(addr)) 75 | 76 | print('using asyncio/streams') 77 | if unix: 78 | coro = asyncio.start_unix_server(echo_client_streams, 79 | addr, limit=256000) 80 | else: 81 | coro = asyncio.start_server(echo_client_streams, 82 | *addr, limit=256000) 83 | srv = loop.run_until_complete(coro) 84 | 85 | try: 86 | loop.run_forever() 87 | finally: 88 | if hasattr(loop, 'print_debug_info'): 89 | gc.collect() 90 | print(chr(27) + "[2J") 91 | loop.print_debug_info() 92 | 93 | loop.close() 94 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | incremental = True 3 | strict = True 4 | 5 | [mypy-uvloop._testbase] 6 | ignore_errors = True 7 | -------------------------------------------------------------------------------- /performance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MagicStack/uvloop/96b7ed31afaf02800d779a395591da6a2c8c50e1/performance.png -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "uvloop" 3 | description = "Fast implementation of asyncio event loop on top of libuv" 4 | authors = [{name = "Yury Selivanov", email = "yury@magic.io"}] 5 | requires-python = '>=3.8.0' 6 | readme = "README.rst" 7 | license = {text = "MIT License"} 8 | dynamic = ["version"] 9 | keywords = [ 10 | "asyncio", 11 | "networking", 12 | ] 13 | classifiers = [ 14 | "Development Status :: 5 - Production/Stable", 15 | "Framework :: AsyncIO", 16 | "Intended Audience :: Developers", 17 | "License :: OSI Approved :: Apache Software License", 18 | "License :: OSI Approved :: MIT License", 19 | "Operating System :: POSIX", 20 | "Operating System :: MacOS :: MacOS X", 21 | "Programming Language :: Python :: 3 :: Only", 22 | "Programming Language :: Python :: 3.8", 23 | "Programming Language :: Python :: 3.9", 24 | "Programming Language :: Python :: 3.10", 25 | "Programming Language :: Python :: 3.11", 26 | "Programming Language :: Python :: 3.12", 27 | "Programming Language :: Python :: 3.13", 28 | "Programming Language :: Python :: Implementation :: CPython", 29 | "Topic :: System :: Networking", 30 | ] 31 | 32 | [project.urls] 33 | github = "https://github.com/MagicStack/uvloop" 34 | 35 | [project.optional-dependencies] 36 | test = [ 37 | # pycodestyle is a dependency of flake8, but it must be frozen because 38 | # their combination breaks too often 39 | # (example breakage: https://gitlab.com/pycqa/flake8/issues/427) 40 | 'aiohttp>=3.10.5', 41 | 'flake8~=5.0', 42 | 'psutil', 43 | 'pycodestyle~=2.9.0', 44 | 'pyOpenSSL~=23.0.0', 45 | 'mypy>=0.800', 46 | ] 47 | dev = [ 48 | 'setuptools>=60', 49 | 'Cython~=3.0', 50 | ] 51 | docs = [ 52 | 'Sphinx~=4.1.2', 53 | 'sphinxcontrib-asyncio~=0.3.0', 54 | 'sphinx_rtd_theme~=0.5.2', 55 | ] 56 | 57 | [build-system] 58 | requires = [ 59 | "setuptools>=60", 60 | "wheel", 61 | "Cython~=3.0", 62 | ] 63 | build-backend = "setuptools.build_meta" 64 | 65 | [tool.setuptools] 66 | zip-safe = false 67 | packages = ["uvloop"] 68 | 69 | [tool.setuptools.exclude-package-data] 70 | "*" = ["*.c", "*.h"] 71 | 72 | [tool.cibuildwheel] 73 | build-frontend = "build" 74 | test-extras = "test" 75 | test-command = "python -m unittest discover -v {project}/tests" 76 | 77 | [tool.pytest.ini_options] 78 | addopts = "--capture=no --assert=plain --strict-markers --tb=native --import-mode=importlib" 79 | testpaths = "tests" 80 | filterwarnings = "default" 81 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | vi = sys.version_info 4 | if vi < (3, 8): 5 | raise RuntimeError('uvloop requires Python 3.8 or greater') 6 | 7 | if sys.platform in ('win32', 'cygwin', 'cli'): 8 | raise RuntimeError('uvloop does not support Windows at the moment') 9 | 10 | import os 11 | import os.path 12 | import pathlib 13 | import platform 14 | import re 15 | import shutil 16 | import subprocess 17 | import sys 18 | 19 | from setuptools import setup, Extension 20 | from setuptools.command.build_ext import build_ext 21 | from setuptools.command.sdist import sdist 22 | 23 | 24 | CYTHON_DEPENDENCY = 'Cython~=3.0' 25 | MACHINE = platform.machine() 26 | MODULES_CFLAGS = [os.getenv('UVLOOP_OPT_CFLAGS', '-O2')] 27 | _ROOT = pathlib.Path(__file__).parent 28 | LIBUV_DIR = str(_ROOT / 'vendor' / 'libuv') 29 | LIBUV_BUILD_DIR = str(_ROOT / 'build' / 'libuv-{}'.format(MACHINE)) 30 | 31 | 32 | def _libuv_build_env(): 33 | env = os.environ.copy() 34 | 35 | cur_cflags = env.get('CFLAGS', '') 36 | if not re.search(r'-O\d', cur_cflags): 37 | cur_cflags += ' -O2' 38 | 39 | env['CFLAGS'] = (cur_cflags + ' -fPIC ' + env.get('ARCHFLAGS', '')) 40 | 41 | return env 42 | 43 | 44 | def _libuv_autogen(env): 45 | if os.path.exists(os.path.join(LIBUV_DIR, 'configure')): 46 | # No need to use autogen, the configure script is there. 47 | return 48 | 49 | if not os.path.exists(os.path.join(LIBUV_DIR, 'autogen.sh')): 50 | raise RuntimeError( 51 | 'the libuv submodule has not been checked out; ' 52 | 'try running "git submodule init; git submodule update"') 53 | 54 | subprocess.run( 55 | ['/bin/sh', 'autogen.sh'], cwd=LIBUV_DIR, env=env, check=True) 56 | 57 | 58 | class uvloop_sdist(sdist): 59 | def run(self): 60 | # Make sure sdist archive contains configure 61 | # to avoid the dependency on autotools. 62 | _libuv_autogen(_libuv_build_env()) 63 | super().run() 64 | 65 | 66 | class uvloop_build_ext(build_ext): 67 | user_options = build_ext.user_options + [ 68 | ('cython-always', None, 69 | 'run cythonize() even if .c files are present'), 70 | ('cython-annotate', None, 71 | 'Produce a colorized HTML version of the Cython source.'), 72 | ('cython-directives=', None, 73 | 'Cythion compiler directives'), 74 | ('use-system-libuv', None, 75 | 'Use the system provided libuv, instead of the bundled one'), 76 | ] 77 | 78 | boolean_options = build_ext.boolean_options + [ 79 | 'cython-always', 80 | 'cython-annotate', 81 | 'use-system-libuv', 82 | ] 83 | 84 | def initialize_options(self): 85 | super().initialize_options() 86 | self.use_system_libuv = False 87 | self.cython_always = False 88 | self.cython_annotate = None 89 | self.cython_directives = None 90 | 91 | def finalize_options(self): 92 | need_cythonize = self.cython_always 93 | cfiles = {} 94 | 95 | for extension in self.distribution.ext_modules: 96 | for i, sfile in enumerate(extension.sources): 97 | if sfile.endswith('.pyx'): 98 | prefix, ext = os.path.splitext(sfile) 99 | cfile = prefix + '.c' 100 | 101 | if os.path.exists(cfile) and not self.cython_always: 102 | extension.sources[i] = cfile 103 | else: 104 | if os.path.exists(cfile): 105 | cfiles[cfile] = os.path.getmtime(cfile) 106 | else: 107 | cfiles[cfile] = 0 108 | need_cythonize = True 109 | 110 | if need_cythonize: 111 | import pkg_resources 112 | 113 | # Double check Cython presence in case setup_requires 114 | # didn't go into effect (most likely because someone 115 | # imported Cython before setup_requires injected the 116 | # correct egg into sys.path. 117 | try: 118 | import Cython 119 | except ImportError: 120 | raise RuntimeError( 121 | 'please install {} to compile uvloop from source'.format( 122 | CYTHON_DEPENDENCY)) 123 | 124 | cython_dep = pkg_resources.Requirement.parse(CYTHON_DEPENDENCY) 125 | if Cython.__version__ not in cython_dep: 126 | raise RuntimeError( 127 | 'uvloop requires {}, got Cython=={}'.format( 128 | CYTHON_DEPENDENCY, Cython.__version__ 129 | )) 130 | 131 | from Cython.Build import cythonize 132 | 133 | directives = {} 134 | if self.cython_directives: 135 | for directive in self.cython_directives.split(','): 136 | k, _, v = directive.partition('=') 137 | if v.lower() == 'false': 138 | v = False 139 | if v.lower() == 'true': 140 | v = True 141 | 142 | directives[k] = v 143 | self.cython_directives = directives 144 | 145 | self.distribution.ext_modules[:] = cythonize( 146 | self.distribution.ext_modules, 147 | compiler_directives=directives, 148 | annotate=self.cython_annotate, 149 | compile_time_env=dict(DEFAULT_FREELIST_SIZE=250), 150 | emit_linenums=self.debug) 151 | 152 | super().finalize_options() 153 | 154 | def build_libuv(self): 155 | env = _libuv_build_env() 156 | 157 | # Make sure configure and friends are present in case 158 | # we are building from a git checkout. 159 | _libuv_autogen(env) 160 | 161 | # Copy the libuv tree to build/ so that its build 162 | # products don't pollute sdist accidentally. 163 | if os.path.exists(LIBUV_BUILD_DIR): 164 | shutil.rmtree(LIBUV_BUILD_DIR) 165 | shutil.copytree(LIBUV_DIR, LIBUV_BUILD_DIR) 166 | 167 | # Sometimes pip fails to preserve the timestamps correctly, 168 | # in which case, make will try to run autotools again. 169 | subprocess.run( 170 | ['touch', 'configure.ac', 'aclocal.m4', 'configure', 171 | 'Makefile.am', 'Makefile.in'], 172 | cwd=LIBUV_BUILD_DIR, env=env, check=True) 173 | 174 | if 'LIBUV_CONFIGURE_HOST' in env: 175 | cmd = ['./configure', '--host=' + env['LIBUV_CONFIGURE_HOST']] 176 | else: 177 | cmd = ['./configure'] 178 | subprocess.run( 179 | cmd, 180 | cwd=LIBUV_BUILD_DIR, env=env, check=True) 181 | 182 | try: 183 | njobs = len(os.sched_getaffinity(0)) 184 | except AttributeError: 185 | njobs = os.cpu_count() 186 | j_flag = '-j{}'.format(njobs or 1) 187 | c_flag = "CFLAGS={}".format(env['CFLAGS']) 188 | subprocess.run( 189 | ['make', j_flag, c_flag], 190 | cwd=LIBUV_BUILD_DIR, env=env, check=True) 191 | 192 | def build_extensions(self): 193 | if self.use_system_libuv: 194 | self.compiler.add_library('uv') 195 | 196 | if sys.platform == 'darwin' and \ 197 | os.path.exists('/opt/local/include'): 198 | # Support macports on Mac OS X. 199 | self.compiler.add_include_dir('/opt/local/include') 200 | else: 201 | libuv_lib = os.path.join(LIBUV_BUILD_DIR, '.libs', 'libuv.a') 202 | if not os.path.exists(libuv_lib): 203 | self.build_libuv() 204 | if not os.path.exists(libuv_lib): 205 | raise RuntimeError('failed to build libuv') 206 | 207 | self.extensions[-1].extra_objects.extend([libuv_lib]) 208 | self.compiler.add_include_dir(os.path.join(LIBUV_DIR, 'include')) 209 | 210 | if sys.platform.startswith('linux'): 211 | self.compiler.add_library('rt') 212 | elif sys.platform.startswith(('freebsd', 'dragonfly')): 213 | self.compiler.add_library('kvm') 214 | elif sys.platform.startswith('sunos'): 215 | self.compiler.add_library('kstat') 216 | 217 | self.compiler.add_library('pthread') 218 | 219 | super().build_extensions() 220 | 221 | 222 | with open(str(_ROOT / 'uvloop' / '_version.py')) as f: 223 | for line in f: 224 | if line.startswith('__version__ ='): 225 | _, _, version = line.partition('=') 226 | VERSION = version.strip(" \n'\"") 227 | break 228 | else: 229 | raise RuntimeError( 230 | 'unable to read the version from uvloop/_version.py') 231 | 232 | 233 | setup_requires = [] 234 | 235 | if not (_ROOT / 'uvloop' / 'loop.c').exists() or '--cython-always' in sys.argv: 236 | # No Cython output, require Cython to build. 237 | setup_requires.append(CYTHON_DEPENDENCY) 238 | 239 | 240 | setup( 241 | version=VERSION, 242 | cmdclass={ 243 | 'sdist': uvloop_sdist, 244 | 'build_ext': uvloop_build_ext 245 | }, 246 | ext_modules=[ 247 | Extension( 248 | "uvloop.loop", 249 | sources=[ 250 | "uvloop/loop.pyx", 251 | ], 252 | extra_compile_args=MODULES_CFLAGS 253 | ), 254 | ], 255 | setup_requires=setup_requires, 256 | ) 257 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MagicStack/uvloop/96b7ed31afaf02800d779a395591da6a2c8c50e1/tests/__init__.py -------------------------------------------------------------------------------- /tests/__main__.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import sys 3 | import unittest 4 | import unittest.runner 5 | 6 | 7 | def suite(): 8 | test_loader = unittest.TestLoader() 9 | test_suite = test_loader.discover(os.path.dirname(__file__)) 10 | return test_suite 11 | 12 | 13 | if __name__ == '__main__': 14 | runner = unittest.runner.TextTestRunner() 15 | result = runner.run(suite()) 16 | sys.exit(not result.wasSuccessful()) 17 | -------------------------------------------------------------------------------- /tests/certs/ssl_cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIF8TCCBFmgAwIBAgIJAMstgJlaaVJcMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNV 3 | BAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEW 4 | MBQGA1UEAwwNb3VyLWNhLXNlcnZlcjAeFw0xODA4MjkxNDIzMTZaFw0yODA3MDcx 5 | NDIzMTZaMF8xCzAJBgNVBAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEj 6 | MCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24xEjAQBgNVBAMMCWxv 7 | Y2FsaG9zdDCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAJ8oLzdB739k 8 | YxZiFukBFGIpyjqYkj0I015p/sDz1MT7DljcZLBLy7OqnkLpB5tnM8256DwdihPA 9 | 3zlnfEzTfr9DD0qFBW2H5cMCoz7X17koeRhzGDd3dkjUeBjXvR5qRosG8wM3lQug 10 | U7AizY+3Azaj1yN3mZ9K5a20jr58Kqinz+Xxx6sb2JfYYff2neJbBahNm5id0AD2 11 | pi/TthZqO5DURJYo+MdgZOcy+7jEjOJsLWZd3Yzq78iM07qDjbpIoVpENZCTHTWA 12 | hX8LIqz0OBmh4weQpm4+plU7E4r4D82uauocWw8iyuznCTtABWO7n9fWySmf9QZC 13 | WYxHAFpBQs6zUVqAD7nhFdTqpQ9bRiaEnjE4HiAccPW+MAoSxFnv/rNzEzI6b4zU 14 | NspFMfg1aNVamdjxdpUZ1GG1Okf0yPJykqEX4PZl3La1Be2q7YZ1wydR523Xd+f3 15 | EO4/g+imETSKn8gyCf6Rvib175L4r2WV1CXQH7gFwZYCod6WHYq5TQIDAQABo4IB 16 | wDCCAbwwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA4GA1UdDwEB/wQEAwIFoDAdBgNV 17 | HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4E 18 | FgQUj+od4zNcABazi29rb9NMy7XLfFUwfQYDVR0jBHYwdIAU3b/K2ubRNLo3dSHK 19 | b5oIKPI1tkihUaRPME0xCzAJBgNVBAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29m 20 | dHdhcmUgRm91bmRhdGlvbiBDQTEWMBQGA1UEAwwNb3VyLWNhLXNlcnZlcoIJAMst 21 | gJlaaVJbMIGDBggrBgEFBQcBAQR3MHUwPAYIKwYBBQUHMAKGMGh0dHA6Ly90ZXN0 22 | Y2EucHl0aG9udGVzdC5uZXQvdGVzdGNhL3B5Y2FjZXJ0LmNlcjA1BggrBgEFBQcw 23 | AYYpaHR0cDovL3Rlc3RjYS5weXRob250ZXN0Lm5ldC90ZXN0Y2Evb2NzcC8wQwYD 24 | VR0fBDwwOjA4oDagNIYyaHR0cDovL3Rlc3RjYS5weXRob250ZXN0Lm5ldC90ZXN0 25 | Y2EvcmV2b2NhdGlvbi5jcmwwDQYJKoZIhvcNAQELBQADggGBACf1jFkQ9MbnKAC/ 26 | uo17EwPxHKZfswZVpCK527LVRr33DN1DbrR5ZWchDCpV7kCOhZ+fR7sKKk22ZHSY 27 | oH+u3PEu20J3GOB1iyY1aMNB7WvId3JvappdVWkC/VpUyFfLsGUDFuIPADmZZqCb 28 | iJMX4loteTVfl1d4xK/1mV6Gq9MRrRqiDfpSELn+v53OM9mGspwW+NZ1CIrbCuW0 29 | KxZ/tPkqn8PSd9fNZR70bB7rWbnwrl+kH8xKxLl6qdlrMmg74WWwhLeQxK7+9DdP 30 | IaDenzqx5cwWBGY/C0HcQj0gPuy3lSs1V/q+f7Y6uspPWP51PgiJLIywXS75iRAr 31 | +UFGTzwAtyfTZSQoFyMmMULqfk6T5HtoVMqfRvPvK+mFDLWEstU1NIB1K/CRI7gI 32 | AY65ClTU+zRS/tlF8IA7tsFvgtEf8jsI9kamlidhS1gyeg4dWcVErV4aeTPB1AUv 33 | StPYQkKNM+NjytWHl5tNuBoDNLsc0gI/WSPiI4CIY8LwomOoiw== 34 | -----END CERTIFICATE----- 35 | -------------------------------------------------------------------------------- /tests/certs/ssl_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQCfKC83Qe9/ZGMW 3 | YhbpARRiKco6mJI9CNNeaf7A89TE+w5Y3GSwS8uzqp5C6QebZzPNueg8HYoTwN85 4 | Z3xM036/Qw9KhQVth+XDAqM+19e5KHkYcxg3d3ZI1HgY170eakaLBvMDN5ULoFOw 5 | Is2PtwM2o9cjd5mfSuWttI6+fCqop8/l8cerG9iX2GH39p3iWwWoTZuYndAA9qYv 6 | 07YWajuQ1ESWKPjHYGTnMvu4xIzibC1mXd2M6u/IjNO6g426SKFaRDWQkx01gIV/ 7 | CyKs9DgZoeMHkKZuPqZVOxOK+A/NrmrqHFsPIsrs5wk7QAVju5/X1skpn/UGQlmM 8 | RwBaQULOs1FagA+54RXU6qUPW0YmhJ4xOB4gHHD1vjAKEsRZ7/6zcxMyOm+M1DbK 9 | RTH4NWjVWpnY8XaVGdRhtTpH9MjycpKhF+D2Zdy2tQXtqu2GdcMnUedt13fn9xDu 10 | P4PophE0ip/IMgn+kb4m9e+S+K9lldQl0B+4BcGWAqHelh2KuU0CAwEAAQKCAYEA 11 | lKiWIYjmyRjdLKUGPTES9vWNvNmRjozV0RQ0LcoSbMMLDZkeO0UwyWqOVHUQ8+ib 12 | jIcfEjeNJxI57oZopeHOO5vJhpNlFH+g7ltiW2qERqA1K88lSXm99Bzw6FNqhCRE 13 | K8ub5N9fyfJA+P4o/xm0WK8EXk5yIUV17p/9zJJxzgKgv2jsVTi3QG2OZGvn4Oug 14 | ByomMZEGHkBDzdxz8c/cP1Tlk1RFuwSgews178k2xq7AYSM/s0YmHi7b/RSvptX6 15 | 1v8P8kXNUe4AwTaNyrlvF2lwIadZ8h1hA7tCE2n44b7a7KfhAkwcbr1T59ioYh6P 16 | zxsyPT678uD51dbtD/DXJCcoeeFOb8uzkR2KNcrnQzZpCJnRq4Gp5ybxwsxxuzpr 17 | gz0gbNlhuWtE7EoSzmIK9t+WTS7IM2CvZymd6/OAh1Fuw6AQhSp64XRp3OfMMAAC 18 | Ie2EPtKj4islWGT8VoUjuRYGmdRh4duAH1dkiAXOWA3R7y5a1/y/iE8KE8BtxocB 19 | AoHBAM8aiURgpu1Fs0Oqz6izec7KSLL3l8hmW+MKUOfk/Ybng6FrTFsL5YtzR+Ap 20 | wW4wwWnnIKEc1JLiZ7g8agRETK8hr5PwFXUn/GSWC0SMsazLJToySQS5LOV0tLzK 21 | kJ3jtNU7tnlDGNkCHTHSoVL2T/8t+IkZI/h5Z6wjlYPvU2Iu0nVIXtiG+alv4A6M 22 | Hrh9l5or4mjB6rGnVXeYohLkCm6s/W97ahVxLMcEdbsBo1prm2JqGnSoiR/tEFC/ 23 | QHQnbQKBwQDEu7kW0Yg9sZ89QtYtVQ1YpixFZORaUeRIRLnpEs1w7L1mCbOZ2Lj9 24 | JHxsH05cYAc7HJfPwwxv3+3aGAIC/dfu4VSwEFtatAzUpzlhzKS5+HQCWB4JUNNU 25 | MQ3+FwK2xQX4Ph8t+OzrFiYcK2g0An5UxWMa2HWIAWUOhnTOydAVsoH6yP31cVm4 26 | 0hxoABCwflaNLNGjRUyfBpLTAcNu/YtcE+KREy7YAAgXXrhRSO4XpLsSXwLnLT7/ 27 | YOkoBWDcTWECgcBPWnSUDZCIQ3efithMZJBciqd2Y2X19Dpq8O31HImD4jtOY0V7 28 | cUB/wSkeHAGwjd/eCyA2e0x8B2IEdqmMfvr+86JJxekC3dJYXCFvH5WIhsH53YCa 29 | 3bT1KlWCLP9ib/g+58VQC0R/Cc9T4sfLePNH7D5ZkZd1wlbV30CPr+i8KwKay6MD 30 | xhvtLx+jk07GE+E9wmjbCMo7TclyrLoVEOlqZMAqshgApT+p9eyCPetwXuDHwa3n 31 | WxhHclcZCV7R4rUCgcAkdGSnxcvpIrDPOUNWwxvmAWTStw9ZbTNP8OxCNCm9cyDl 32 | d4bAS1h8D/a+Uk7C70hnu7Sl2w7C7Eu2zhwRUdhhe3+l4GINPK/j99i6NqGPlGpq 33 | xMlMEJ4YS768BqeKFpg0l85PRoEgTsphDeoROSUPsEPdBZ9BxIBlYKTkbKESZDGR 34 | twzYHljx1n1NCDYPflmrb1KpXn4EOcObNghw2KqqNUUWfOeBPwBA1FxzM4BrAStp 35 | DBINpGS4Dc0mjViVegECgcA3hTtm82XdxQXj9LQmb/E3lKx/7H87XIOeNMmvjYuZ 36 | iS9wKrkF+u42vyoDxcKMCnxP5056wpdST4p56r+SBwVTHcc3lGBSGcMTIfwRXrj3 37 | thOA2our2n4ouNIsYyTlcsQSzifwmpRmVMRPxl9fYVdEWUgB83FgHT0D9avvZnF9 38 | t9OccnGJXShAIZIBADhVj/JwG4FbaX42NijD5PNpVLk1Y17OV0I576T9SfaQoBjJ 39 | aH1M/zC4aVaS0DYB/Gxq7v8= 40 | -----END PRIVATE KEY----- 41 | -------------------------------------------------------------------------------- /tests/test_aiohttp.py: -------------------------------------------------------------------------------- 1 | try: 2 | import aiohttp 3 | import aiohttp.web 4 | except ImportError: 5 | skip_tests = True 6 | else: 7 | skip_tests = False 8 | 9 | import asyncio 10 | import sys 11 | import unittest 12 | import weakref 13 | 14 | from uvloop import _testbase as tb 15 | 16 | 17 | class _TestAioHTTP: 18 | 19 | def test_aiohttp_basic_1(self): 20 | 21 | PAYLOAD = '

It Works!

' * 10000 22 | 23 | async def on_request(request): 24 | return aiohttp.web.Response(text=PAYLOAD) 25 | 26 | asyncio.set_event_loop(self.loop) 27 | app = aiohttp.web.Application() 28 | app.router.add_get('/', on_request) 29 | 30 | runner = aiohttp.web.AppRunner(app) 31 | self.loop.run_until_complete(runner.setup()) 32 | site = aiohttp.web.TCPSite(runner, '0.0.0.0', '0') 33 | self.loop.run_until_complete(site.start()) 34 | port = site._server.sockets[0].getsockname()[1] 35 | 36 | async def test(): 37 | # Make sure we're using the correct event loop. 38 | self.assertIs(asyncio.get_event_loop(), self.loop) 39 | 40 | for addr in (('localhost', port), 41 | ('127.0.0.1', port)): 42 | async with aiohttp.ClientSession() as client: 43 | async with client.get('http://{}:{}'.format(*addr)) as r: 44 | self.assertEqual(r.status, 200) 45 | result = await r.text() 46 | self.assertEqual(result, PAYLOAD) 47 | 48 | self.loop.run_until_complete(test()) 49 | self.loop.run_until_complete(runner.cleanup()) 50 | 51 | def test_aiohttp_graceful_shutdown(self): 52 | if self.implementation == 'asyncio' and sys.version_info >= (3, 12, 0): 53 | # In Python 3.12.0, asyncio.Server.wait_closed() waits for all 54 | # existing connections to complete, before aiohttp sends 55 | # on_shutdown signals. 56 | # https://github.com/aio-libs/aiohttp/issues/7675#issuecomment-1752143748 57 | # https://github.com/python/cpython/pull/98582 58 | raise unittest.SkipTest('bug in aiohttp: #7675') 59 | 60 | async def websocket_handler(request): 61 | ws = aiohttp.web.WebSocketResponse() 62 | await ws.prepare(request) 63 | request.app['websockets'].add(ws) 64 | try: 65 | async for msg in ws: 66 | await ws.send_str(msg.data) 67 | finally: 68 | request.app['websockets'].discard(ws) 69 | return ws 70 | 71 | async def on_shutdown(app): 72 | for ws in set(app['websockets']): 73 | await ws.close( 74 | code=aiohttp.WSCloseCode.GOING_AWAY, 75 | message='Server shutdown') 76 | 77 | asyncio.set_event_loop(self.loop) 78 | app = aiohttp.web.Application() 79 | app.router.add_get('/', websocket_handler) 80 | app.on_shutdown.append(on_shutdown) 81 | app['websockets'] = weakref.WeakSet() 82 | 83 | runner = aiohttp.web.AppRunner(app) 84 | self.loop.run_until_complete(runner.setup()) 85 | site = aiohttp.web.TCPSite( 86 | runner, 87 | '0.0.0.0', 88 | 0, 89 | # https://github.com/aio-libs/aiohttp/pull/7188 90 | shutdown_timeout=0.1, 91 | ) 92 | self.loop.run_until_complete(site.start()) 93 | port = site._server.sockets[0].getsockname()[1] 94 | 95 | async def client(): 96 | async with aiohttp.ClientSession() as client: 97 | async with client.ws_connect( 98 | 'http://127.0.0.1:{}'.format(port)) as ws: 99 | await ws.send_str("hello") 100 | async for msg in ws: 101 | assert msg.data == "hello" 102 | 103 | client_task = asyncio.ensure_future(client()) 104 | 105 | async def stop(): 106 | await asyncio.sleep(0.1) 107 | try: 108 | await asyncio.wait_for(runner.cleanup(), timeout=0.5) 109 | finally: 110 | try: 111 | client_task.cancel() 112 | await client_task 113 | except asyncio.CancelledError: 114 | pass 115 | 116 | self.loop.run_until_complete(stop()) 117 | 118 | 119 | @unittest.skipIf(skip_tests, "no aiohttp module") 120 | class Test_UV_AioHTTP(_TestAioHTTP, tb.UVTestCase): 121 | pass 122 | 123 | 124 | @unittest.skipIf(skip_tests, "no aiohttp module") 125 | class Test_AIO_AioHTTP(_TestAioHTTP, tb.AIOTestCase): 126 | pass 127 | -------------------------------------------------------------------------------- /tests/test_cython.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from uvloop._testbase import UVTestCase 4 | 5 | 6 | class TestCythonIntegration(UVTestCase): 7 | 8 | def test_cython_coro_is_coroutine(self): 9 | from uvloop.loop import _test_coroutine_1 10 | from asyncio.coroutines import _format_coroutine 11 | 12 | coro = _test_coroutine_1() 13 | 14 | coro_fmt = _format_coroutine(coro) 15 | self.assertTrue( 16 | coro_fmt.startswith('_test_coroutine_1() done') 17 | or coro_fmt.startswith('_test_coroutine_1() running') 18 | ) 19 | self.assertEqual(_test_coroutine_1.__qualname__, '_test_coroutine_1') 20 | self.assertEqual(_test_coroutine_1.__name__, '_test_coroutine_1') 21 | self.assertTrue(asyncio.iscoroutine(coro)) 22 | fut = asyncio.ensure_future(coro) 23 | self.assertTrue(isinstance(fut, asyncio.Future)) 24 | self.assertTrue(isinstance(fut, asyncio.Task)) 25 | fut.cancel() 26 | 27 | with self.assertRaises(asyncio.CancelledError): 28 | self.loop.run_until_complete(fut) 29 | 30 | try: 31 | _format_coroutine(coro) # This line checks against Cython segfault 32 | except TypeError: 33 | # TODO: Fix Cython to not reset __name__/__qualname__ to None 34 | pass 35 | coro.close() 36 | -------------------------------------------------------------------------------- /tests/test_dealloc.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import subprocess 3 | import sys 4 | 5 | from uvloop import _testbase as tb 6 | 7 | 8 | class TestDealloc(tb.UVTestCase): 9 | 10 | def test_dealloc_1(self): 11 | # Somewhere between Cython 0.25.2 and 0.26.0 uvloop programs 12 | # started to trigger the following output: 13 | # 14 | # $ python prog.py 15 | # Error in sys.excepthook: 16 | # 17 | # Original exception was: 18 | # 19 | # Upon some debugging, it appeared that Handle.__dealloc__ was 20 | # called at a time where some CPython objects become non-functional, 21 | # and any exception in __dealloc__ caused CPython to output the 22 | # above. 23 | # 24 | # This regression test starts an event loop in debug mode, 25 | # lets it run for a brief period of time, and exits the program. 26 | # This will trigger Handle.__dealloc__, CallbackHandle.__dealloc__, 27 | # and Loop.__dealloc__ methods. The test will fail if they produce 28 | # any unwanted output. 29 | 30 | async def test(): 31 | prog = '''\ 32 | import uvloop 33 | 34 | async def foo(): 35 | return 42 36 | 37 | def main(): 38 | loop = uvloop.new_event_loop() 39 | loop.set_debug(True) 40 | loop.run_until_complete(foo()) 41 | # Do not close the loop on purpose: let __dealloc__ methods run. 42 | 43 | if __name__ == '__main__': 44 | main() 45 | ''' 46 | 47 | cmd = sys.executable 48 | proc = await asyncio.create_subprocess_exec( 49 | cmd, b'-W', b'ignore', b'-c', prog, 50 | stdout=subprocess.PIPE, 51 | stderr=subprocess.PIPE) 52 | 53 | await proc.wait() 54 | out = await proc.stdout.read() 55 | err = await proc.stderr.read() 56 | 57 | return out, err 58 | 59 | out, err = self.loop.run_until_complete(test()) 60 | self.assertEqual(out, b'', 'stdout is not empty') 61 | self.assertEqual(err, b'', 'stderr is not empty') 62 | -------------------------------------------------------------------------------- /tests/test_dns.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import socket 3 | import unittest 4 | 5 | from uvloop import _testbase as tb 6 | 7 | 8 | def patched_getaddrinfo(*args, **kwargs): 9 | # corrected socket.getaddrinfo() behavior: ai_canonname always follows the 10 | # flag AI_CANONNAME, even if `host` is an IP 11 | rv = [] 12 | result = socket.getaddrinfo(*args, **kwargs) 13 | first = True 14 | for af, sk, proto, canon_name, addr in result: 15 | if kwargs.get('flags', 0) & socket.AI_CANONNAME: 16 | if not canon_name and first: 17 | first = False 18 | canon_name = args[0] 19 | if not isinstance(canon_name, str): 20 | canon_name = canon_name.decode('ascii') 21 | elif canon_name: 22 | canon_name = '' 23 | rv.append((af, sk, proto, canon_name, addr)) 24 | return rv 25 | 26 | 27 | class BaseTestDNS: 28 | 29 | def _test_getaddrinfo(self, *args, _patch=False, _sorted=False, **kwargs): 30 | err = None 31 | try: 32 | if _patch: 33 | a1 = patched_getaddrinfo(*args, **kwargs) 34 | else: 35 | a1 = socket.getaddrinfo(*args, **kwargs) 36 | except (socket.gaierror, UnicodeError) as ex: 37 | err = ex 38 | 39 | try: 40 | a2 = self.loop.run_until_complete( 41 | self.loop.getaddrinfo(*args, **kwargs)) 42 | except (socket.gaierror, UnicodeError) as ex: 43 | if err is not None: 44 | self.assertEqual(ex.args, err.args) 45 | else: 46 | ex.__context__ = err 47 | raise ex 48 | except OSError as ex: 49 | ex.__context__ = err 50 | raise ex 51 | else: 52 | if err is not None: 53 | raise err 54 | 55 | if _sorted: 56 | if kwargs.get('flags', 0) & socket.AI_CANONNAME and a1 and a2: 57 | af, sk, proto, canon_name1, addr = a1[0] 58 | a1[0] = (af, sk, proto, '', addr) 59 | af, sk, proto, canon_name2, addr = a2[0] 60 | a2[0] = (af, sk, proto, '', addr) 61 | self.assertEqual(canon_name1, canon_name2) 62 | 63 | self.assertEqual(sorted(a1), sorted(a2)) 64 | else: 65 | self.assertEqual(a1, a2) 66 | 67 | def _test_getnameinfo(self, *args, **kwargs): 68 | err = None 69 | try: 70 | a1 = socket.getnameinfo(*args, **kwargs) 71 | except Exception as ex: 72 | err = ex 73 | 74 | try: 75 | a2 = self.loop.run_until_complete( 76 | self.loop.getnameinfo(*args, **kwargs)) 77 | except Exception as ex: 78 | if err is not None: 79 | if ex.__class__ is not err.__class__: 80 | print(ex, err) 81 | self.assertIs(ex.__class__, err.__class__) 82 | self.assertEqual(ex.args, err.args) 83 | else: 84 | raise 85 | else: 86 | if err is not None: 87 | raise err 88 | 89 | self.assertEqual(a1, a2) 90 | 91 | def test_getaddrinfo_1(self): 92 | self._test_getaddrinfo('example.com', 80, _sorted=True) 93 | self._test_getaddrinfo('example.com', 80, type=socket.SOCK_STREAM, 94 | _sorted=True) 95 | 96 | def test_getaddrinfo_2(self): 97 | self._test_getaddrinfo('example.com', 80, flags=socket.AI_CANONNAME, 98 | _sorted=True) 99 | 100 | def test_getaddrinfo_3(self): 101 | self._test_getaddrinfo('a' + '1' * 50 + '.wat', 800) 102 | 103 | def test_getaddrinfo_4(self): 104 | self._test_getaddrinfo('example.com', 80, family=-1) 105 | self._test_getaddrinfo('example.com', 80, type=socket.SOCK_STREAM, 106 | family=-1) 107 | 108 | def test_getaddrinfo_5(self): 109 | self._test_getaddrinfo('example.com', '80', _sorted=True) 110 | self._test_getaddrinfo('example.com', '80', type=socket.SOCK_STREAM, 111 | _sorted=True) 112 | 113 | def test_getaddrinfo_6(self): 114 | self._test_getaddrinfo(b'example.com', b'80', _sorted=True) 115 | self._test_getaddrinfo(b'example.com', b'80', type=socket.SOCK_STREAM, 116 | _sorted=True) 117 | 118 | def test_getaddrinfo_7(self): 119 | self._test_getaddrinfo(None, 0) 120 | self._test_getaddrinfo(None, 0, type=socket.SOCK_STREAM) 121 | 122 | def test_getaddrinfo_8(self): 123 | self._test_getaddrinfo('', 0) 124 | self._test_getaddrinfo('', 0, type=socket.SOCK_STREAM) 125 | 126 | def test_getaddrinfo_9(self): 127 | self._test_getaddrinfo(b'', 0) 128 | self._test_getaddrinfo(b'', 0, type=socket.SOCK_STREAM) 129 | 130 | def test_getaddrinfo_10(self): 131 | self._test_getaddrinfo(None, None) 132 | self._test_getaddrinfo(None, None, type=socket.SOCK_STREAM) 133 | 134 | def test_getaddrinfo_11(self): 135 | self._test_getaddrinfo(b'example.com', '80', _sorted=True) 136 | self._test_getaddrinfo(b'example.com', '80', type=socket.SOCK_STREAM, 137 | _sorted=True) 138 | 139 | def test_getaddrinfo_12(self): 140 | # musl always returns ai_canonname but we don't 141 | patch = self.implementation != 'asyncio' 142 | 143 | self._test_getaddrinfo('127.0.0.1', '80') 144 | self._test_getaddrinfo('127.0.0.1', '80', type=socket.SOCK_STREAM, 145 | _patch=patch) 146 | 147 | def test_getaddrinfo_13(self): 148 | # musl always returns ai_canonname but we don't 149 | patch = self.implementation != 'asyncio' 150 | 151 | self._test_getaddrinfo(b'127.0.0.1', b'80') 152 | self._test_getaddrinfo(b'127.0.0.1', b'80', type=socket.SOCK_STREAM, 153 | _patch=patch) 154 | 155 | def test_getaddrinfo_14(self): 156 | # musl always returns ai_canonname but we don't 157 | patch = self.implementation != 'asyncio' 158 | 159 | self._test_getaddrinfo(b'127.0.0.1', b'http') 160 | self._test_getaddrinfo(b'127.0.0.1', b'http', type=socket.SOCK_STREAM, 161 | _patch=patch) 162 | 163 | def test_getaddrinfo_15(self): 164 | # musl always returns ai_canonname but we don't 165 | patch = self.implementation != 'asyncio' 166 | 167 | self._test_getaddrinfo('127.0.0.1', 'http') 168 | self._test_getaddrinfo('127.0.0.1', 'http', type=socket.SOCK_STREAM, 169 | _patch=patch) 170 | 171 | def test_getaddrinfo_16(self): 172 | self._test_getaddrinfo('localhost', 'http') 173 | self._test_getaddrinfo('localhost', 'http', type=socket.SOCK_STREAM) 174 | 175 | def test_getaddrinfo_17(self): 176 | self._test_getaddrinfo(b'localhost', 'http') 177 | self._test_getaddrinfo(b'localhost', 'http', type=socket.SOCK_STREAM) 178 | 179 | def test_getaddrinfo_18(self): 180 | self._test_getaddrinfo('localhost', b'http') 181 | self._test_getaddrinfo('localhost', b'http', type=socket.SOCK_STREAM) 182 | 183 | def test_getaddrinfo_19(self): 184 | # musl always returns ai_canonname while macOS never return for IPs, 185 | # but we strictly follow the docs to use the AI_CANONNAME flag in a 186 | # shortcut __static_getaddrinfo_pyaddr() 187 | patch = self.implementation != 'asyncio' 188 | 189 | self._test_getaddrinfo('::1', 80) 190 | self._test_getaddrinfo('::1', 80, type=socket.SOCK_STREAM, 191 | _patch=patch) 192 | self._test_getaddrinfo('::1', 80, type=socket.SOCK_STREAM, 193 | flags=socket.AI_CANONNAME, _patch=patch) 194 | 195 | def test_getaddrinfo_20(self): 196 | # musl always returns ai_canonname while macOS never return for IPs, 197 | # but we strictly follow the docs to use the AI_CANONNAME flag in a 198 | # shortcut __static_getaddrinfo_pyaddr() 199 | patch = self.implementation != 'asyncio' 200 | 201 | self._test_getaddrinfo('127.0.0.1', 80) 202 | self._test_getaddrinfo('127.0.0.1', 80, type=socket.SOCK_STREAM, 203 | _patch=patch) 204 | self._test_getaddrinfo('127.0.0.1', 80, type=socket.SOCK_STREAM, 205 | flags=socket.AI_CANONNAME, _patch=patch) 206 | 207 | # https://github.com/libuv/libuv/security/advisories/GHSA-f74f-cvh7-c6q6 208 | # See also: https://github.com/MagicStack/uvloop/pull/600 209 | def test_getaddrinfo_21(self): 210 | payload = f'0x{"0" * 246}7f000001.example.com'.encode('ascii') 211 | self._test_getaddrinfo(payload, 80) 212 | self._test_getaddrinfo(payload, 80, type=socket.SOCK_STREAM) 213 | 214 | def test_getaddrinfo_22(self): 215 | payload = f'0x{"0" * 246}7f000001.example.com' 216 | self._test_getaddrinfo(payload, 80) 217 | self._test_getaddrinfo(payload, 80, type=socket.SOCK_STREAM) 218 | 219 | ###### 220 | 221 | def test_getnameinfo_1(self): 222 | self._test_getnameinfo(('127.0.0.1', 80), 0) 223 | 224 | def test_getnameinfo_2(self): 225 | self._test_getnameinfo(('127.0.0.1', 80, 1231231231213), 0) 226 | 227 | def test_getnameinfo_3(self): 228 | self._test_getnameinfo(('127.0.0.1', 80, 0, 0), 0) 229 | 230 | def test_getnameinfo_4(self): 231 | self._test_getnameinfo(('::1', 80), 0) 232 | 233 | def test_getnameinfo_5(self): 234 | self._test_getnameinfo(('localhost', 8080), 0) 235 | 236 | 237 | class Test_UV_DNS(BaseTestDNS, tb.UVTestCase): 238 | 239 | def test_getaddrinfo_close_loop(self): 240 | # Test that we can close the loop with a running 241 | # DNS query. 242 | 243 | try: 244 | # Check that we have internet connection 245 | socket.getaddrinfo('example.com', 80) 246 | except socket.error: 247 | raise unittest.SkipTest 248 | 249 | async def run(): 250 | fut = self.loop.create_task( 251 | self.loop.getaddrinfo('example.com', 80)) 252 | await asyncio.sleep(0) 253 | fut.cancel() 254 | self.loop.stop() 255 | 256 | try: 257 | self.loop.run_until_complete(run()) 258 | finally: 259 | self.loop.close() 260 | 261 | 262 | class Test_AIO_DNS(BaseTestDNS, tb.AIOTestCase): 263 | pass 264 | -------------------------------------------------------------------------------- /tests/test_executors.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import concurrent.futures 3 | import multiprocessing 4 | import unittest 5 | 6 | from uvloop import _testbase as tb 7 | 8 | 9 | def fib(n): 10 | if n < 2: 11 | return 1 12 | return fib(n - 2) + fib(n - 1) 13 | 14 | 15 | class _TestExecutors: 16 | 17 | def run_pool_test(self, pool_factory): 18 | async def run(): 19 | pool = pool_factory() 20 | with pool: 21 | coros = [] 22 | for i in range(0, 10): 23 | coros.append(self.loop.run_in_executor(pool, fib, i)) 24 | res = await asyncio.gather(*coros) 25 | self.assertEqual(res, fib10) 26 | await asyncio.sleep(0.01) 27 | 28 | fib10 = [fib(i) for i in range(10)] 29 | self.loop.run_until_complete(run()) 30 | 31 | @unittest.skipIf( 32 | multiprocessing.get_start_method(False) == 'spawn', 33 | 'no need to test on macOS where spawn is used instead of fork') 34 | def test_executors_process_pool_01(self): 35 | self.run_pool_test(concurrent.futures.ProcessPoolExecutor) 36 | 37 | def test_executors_process_pool_02(self): 38 | self.run_pool_test(concurrent.futures.ThreadPoolExecutor) 39 | 40 | 41 | class TestUVExecutors(_TestExecutors, tb.UVTestCase): 42 | pass 43 | 44 | 45 | class TestAIOExecutors(_TestExecutors, tb.AIOTestCase): 46 | pass 47 | -------------------------------------------------------------------------------- /tests/test_fs_event.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os.path 3 | import tempfile 4 | 5 | from uvloop import _testbase as tb 6 | from uvloop.loop import FileSystemEvent 7 | 8 | 9 | class Test_UV_FS_EVENT_CHANGE(tb.UVTestCase): 10 | async def _file_writer(self): 11 | f = await self.q.get() 12 | while True: 13 | f.write('hello uvloop\n') 14 | f.flush() 15 | x = await self.q.get() 16 | if x is None: 17 | return 18 | 19 | def fs_event_setup(self): 20 | self.change_event_count = 0 21 | self.fname = '' 22 | self.q = asyncio.Queue() 23 | 24 | def event_cb(self, ev_fname: bytes, evt: FileSystemEvent): 25 | _d, fn = os.path.split(self.fname) 26 | self.assertEqual(ev_fname, fn) 27 | self.assertEqual(evt, FileSystemEvent.CHANGE) 28 | self.change_event_count += 1 29 | if self.change_event_count < 4: 30 | self.q.put_nowait(0) 31 | else: 32 | self.q.put_nowait(None) 33 | 34 | def test_fs_event_change(self): 35 | self.fs_event_setup() 36 | 37 | async def run(write_task): 38 | self.q.put_nowait(tf) 39 | try: 40 | await asyncio.wait_for(write_task, 4) 41 | except asyncio.TimeoutError: 42 | write_task.cancel() 43 | 44 | with tempfile.NamedTemporaryFile('wt') as tf: 45 | self.fname = tf.name.encode() 46 | h = self.loop._monitor_fs(tf.name, self.event_cb) 47 | self.assertFalse(h.cancelled()) 48 | 49 | self.loop.run_until_complete(run( 50 | self.loop.create_task(self._file_writer()))) 51 | h.cancel() 52 | self.assertTrue(h.cancelled()) 53 | 54 | self.assertEqual(self.change_event_count, 4) 55 | 56 | 57 | class Test_UV_FS_EVENT_RENAME(tb.UVTestCase): 58 | async def _file_renamer(self): 59 | await self.q.get() 60 | os.rename(os.path.join(self.dname, self.changed_name), 61 | os.path.join(self.dname, self.changed_name + "-new")) 62 | await self.q.get() 63 | 64 | def fs_event_setup(self): 65 | self.dname = '' 66 | self.changed_name = "hello_fs_event.txt" 67 | self.changed_set = {self.changed_name, self.changed_name + '-new'} 68 | self.q = asyncio.Queue() 69 | 70 | def event_cb(self, ev_fname: bytes, evt: FileSystemEvent): 71 | ev_fname = ev_fname.decode() 72 | self.assertEqual(evt, FileSystemEvent.RENAME) 73 | self.changed_set.remove(ev_fname) 74 | if len(self.changed_set) == 0: 75 | self.q.put_nowait(None) 76 | 77 | def test_fs_event_rename(self): 78 | self.fs_event_setup() 79 | 80 | async def run(write_task): 81 | self.q.put_nowait(0) 82 | try: 83 | await asyncio.wait_for(write_task, 4) 84 | except asyncio.TimeoutError: 85 | write_task.cancel() 86 | 87 | with tempfile.TemporaryDirectory() as td_name: 88 | self.dname = td_name 89 | f = open(os.path.join(td_name, self.changed_name), 'wt') 90 | f.write('hello!') 91 | f.close() 92 | h = self.loop._monitor_fs(td_name, self.event_cb) 93 | self.assertFalse(h.cancelled()) 94 | 95 | self.loop.run_until_complete(run( 96 | self.loop.create_task(self._file_renamer()))) 97 | h.cancel() 98 | self.assertTrue(h.cancelled()) 99 | 100 | self.assertEqual(len(self.changed_set), 0) 101 | -------------------------------------------------------------------------------- /tests/test_libuv_api.py: -------------------------------------------------------------------------------- 1 | from uvloop import _testbase as tb 2 | from uvloop.loop import libuv_get_loop_t_ptr, libuv_get_version 3 | from uvloop.loop import _testhelper_unwrap_capsuled_pointer as unwrap 4 | 5 | 6 | class Test_UV_libuv(tb.UVTestCase): 7 | def test_libuv_get_loop_t_ptr(self): 8 | loop1 = self.new_loop() 9 | cap1 = libuv_get_loop_t_ptr(loop1) 10 | cap2 = libuv_get_loop_t_ptr(loop1) 11 | 12 | loop2 = self.new_loop() 13 | cap3 = libuv_get_loop_t_ptr(loop2) 14 | 15 | try: 16 | self.assertEqual(unwrap(cap1), unwrap(cap2)) 17 | self.assertNotEqual(unwrap(cap1), unwrap(cap3)) 18 | finally: 19 | loop1.close() 20 | loop2.close() 21 | 22 | def test_libuv_get_version(self): 23 | self.assertGreater(libuv_get_version(), 0) 24 | -------------------------------------------------------------------------------- /tests/test_pipes.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import io 3 | import os 4 | import socket 5 | 6 | from uvloop import _testbase as tb 7 | 8 | 9 | # All tests are copied from asyncio (mostly as-is) 10 | 11 | 12 | class MyReadPipeProto(asyncio.Protocol): 13 | done = None 14 | 15 | def __init__(self, loop=None): 16 | self.state = ['INITIAL'] 17 | self.nbytes = 0 18 | self.transport = None 19 | if loop is not None: 20 | self.done = asyncio.Future(loop=loop) 21 | 22 | def connection_made(self, transport): 23 | self.transport = transport 24 | assert self.state == ['INITIAL'], self.state 25 | self.state.append('CONNECTED') 26 | 27 | def data_received(self, data): 28 | assert self.state == ['INITIAL', 'CONNECTED'], self.state 29 | self.nbytes += len(data) 30 | 31 | def eof_received(self): 32 | assert self.state == ['INITIAL', 'CONNECTED'], self.state 33 | self.state.append('EOF') 34 | 35 | def connection_lost(self, exc): 36 | if 'EOF' not in self.state: 37 | self.state.append('EOF') # It is okay if EOF is missed. 38 | assert self.state == ['INITIAL', 'CONNECTED', 'EOF'], self.state 39 | self.state.append('CLOSED') 40 | if self.done: 41 | self.done.set_result(None) 42 | 43 | 44 | class MyWritePipeProto(asyncio.BaseProtocol): 45 | done = None 46 | paused = False 47 | 48 | def __init__(self, loop=None): 49 | self.state = 'INITIAL' 50 | self.transport = None 51 | if loop is not None: 52 | self.done = asyncio.Future(loop=loop) 53 | 54 | def connection_made(self, transport): 55 | self.transport = transport 56 | assert self.state == 'INITIAL', self.state 57 | self.state = 'CONNECTED' 58 | 59 | def connection_lost(self, exc): 60 | assert self.state == 'CONNECTED', self.state 61 | self.state = 'CLOSED' 62 | if self.done: 63 | self.done.set_result(None) 64 | 65 | def pause_writing(self): 66 | self.paused = True 67 | 68 | def resume_writing(self): 69 | self.paused = False 70 | 71 | 72 | class _BasePipeTest: 73 | def test_read_pipe(self): 74 | proto = MyReadPipeProto(loop=self.loop) 75 | 76 | rpipe, wpipe = os.pipe() 77 | pipeobj = io.open(rpipe, 'rb', 1024) 78 | 79 | async def connect(): 80 | t, p = await self.loop.connect_read_pipe( 81 | lambda: proto, pipeobj) 82 | self.assertIs(p, proto) 83 | self.assertIs(t, proto.transport) 84 | self.assertEqual(['INITIAL', 'CONNECTED'], proto.state) 85 | self.assertEqual(0, proto.nbytes) 86 | 87 | self.loop.run_until_complete(connect()) 88 | 89 | os.write(wpipe, b'1') 90 | tb.run_until(self.loop, lambda: proto.nbytes >= 1) 91 | self.assertEqual(1, proto.nbytes) 92 | 93 | os.write(wpipe, b'2345') 94 | tb.run_until(self.loop, lambda: proto.nbytes >= 5) 95 | self.assertEqual(['INITIAL', 'CONNECTED'], proto.state) 96 | self.assertEqual(5, proto.nbytes) 97 | 98 | os.close(wpipe) 99 | self.loop.run_until_complete(proto.done) 100 | self.assertEqual( 101 | ['INITIAL', 'CONNECTED', 'EOF', 'CLOSED'], proto.state) 102 | # extra info is available 103 | self.assertIsNotNone(proto.transport.get_extra_info('pipe')) 104 | 105 | def test_read_pty_output(self): 106 | proto = MyReadPipeProto(loop=self.loop) 107 | 108 | master, slave = os.openpty() 109 | master_read_obj = io.open(master, 'rb', 0) 110 | 111 | async def connect(): 112 | t, p = await self.loop.connect_read_pipe( 113 | lambda: proto, master_read_obj) 114 | self.assertIs(p, proto) 115 | self.assertIs(t, proto.transport) 116 | self.assertEqual(['INITIAL', 'CONNECTED'], proto.state) 117 | self.assertEqual(0, proto.nbytes) 118 | 119 | self.loop.run_until_complete(connect()) 120 | 121 | os.write(slave, b'1') 122 | tb.run_until(self.loop, lambda: proto.nbytes) 123 | self.assertEqual(1, proto.nbytes) 124 | 125 | os.write(slave, b'2345') 126 | tb.run_until(self.loop, lambda: proto.nbytes >= 5) 127 | self.assertEqual(['INITIAL', 'CONNECTED'], proto.state) 128 | self.assertEqual(5, proto.nbytes) 129 | 130 | # On Linux, transport raises EIO when slave is closed -- 131 | # ignore it. 132 | self.loop.set_exception_handler(lambda loop, ctx: None) 133 | os.close(slave) 134 | proto.transport.close() 135 | self.loop.run_until_complete(proto.done) 136 | 137 | self.assertEqual( 138 | ['INITIAL', 'CONNECTED', 'EOF', 'CLOSED'], proto.state) 139 | # extra info is available 140 | self.assertIsNotNone(proto.transport.get_extra_info('pipe')) 141 | 142 | def test_write_pipe(self): 143 | rpipe, wpipe = os.pipe() 144 | os.set_blocking(rpipe, False) 145 | pipeobj = io.open(wpipe, 'wb', 1024) 146 | 147 | proto = MyWritePipeProto(loop=self.loop) 148 | connect = self.loop.connect_write_pipe(lambda: proto, pipeobj) 149 | transport, p = self.loop.run_until_complete(connect) 150 | self.assertIs(p, proto) 151 | self.assertIs(transport, proto.transport) 152 | self.assertEqual('CONNECTED', proto.state) 153 | 154 | transport.write(b'1') 155 | 156 | data = bytearray() 157 | 158 | def reader(data): 159 | try: 160 | chunk = os.read(rpipe, 1024) 161 | except BlockingIOError: 162 | return len(data) 163 | data += chunk 164 | return len(data) 165 | 166 | tb.run_until(self.loop, lambda: reader(data) >= 1) 167 | self.assertEqual(b'1', data) 168 | 169 | transport.write(b'2345') 170 | tb.run_until(self.loop, lambda: reader(data) >= 5) 171 | self.assertEqual(b'12345', data) 172 | self.assertEqual('CONNECTED', proto.state) 173 | 174 | os.close(rpipe) 175 | 176 | # extra info is available 177 | self.assertIsNotNone(proto.transport.get_extra_info('pipe')) 178 | 179 | # close connection 180 | proto.transport.close() 181 | self.loop.run_until_complete(proto.done) 182 | self.assertEqual('CLOSED', proto.state) 183 | 184 | def test_write_pipe_disconnect_on_close(self): 185 | rsock, wsock = socket.socketpair() 186 | rsock.setblocking(False) 187 | 188 | pipeobj = io.open(wsock.detach(), 'wb', 1024) 189 | 190 | proto = MyWritePipeProto(loop=self.loop) 191 | connect = self.loop.connect_write_pipe(lambda: proto, pipeobj) 192 | transport, p = self.loop.run_until_complete(connect) 193 | self.assertIs(p, proto) 194 | self.assertIs(transport, proto.transport) 195 | self.assertEqual('CONNECTED', proto.state) 196 | 197 | transport.write(b'1') 198 | data = self.loop.run_until_complete(self.loop.sock_recv(rsock, 1024)) 199 | self.assertEqual(b'1', data) 200 | 201 | rsock.close() 202 | 203 | self.loop.run_until_complete(proto.done) 204 | self.assertEqual('CLOSED', proto.state) 205 | 206 | def test_write_pty(self): 207 | master, slave = os.openpty() 208 | os.set_blocking(master, False) 209 | 210 | slave_write_obj = io.open(slave, 'wb', 0) 211 | 212 | proto = MyWritePipeProto(loop=self.loop) 213 | connect = self.loop.connect_write_pipe(lambda: proto, slave_write_obj) 214 | transport, p = self.loop.run_until_complete(connect) 215 | self.assertIs(p, proto) 216 | self.assertIs(transport, proto.transport) 217 | self.assertEqual('CONNECTED', proto.state) 218 | 219 | transport.write(b'1') 220 | 221 | data = bytearray() 222 | 223 | def reader(data): 224 | try: 225 | chunk = os.read(master, 1024) 226 | except BlockingIOError: 227 | return len(data) 228 | data += chunk 229 | return len(data) 230 | 231 | tb.run_until(self.loop, lambda: reader(data) >= 1, 232 | timeout=10) 233 | self.assertEqual(b'1', data) 234 | 235 | transport.write(b'2345') 236 | tb.run_until(self.loop, lambda: reader(data) >= 5, 237 | timeout=10) 238 | self.assertEqual(b'12345', data) 239 | self.assertEqual('CONNECTED', proto.state) 240 | 241 | os.close(master) 242 | 243 | # extra info is available 244 | self.assertIsNotNone(proto.transport.get_extra_info('pipe')) 245 | 246 | # close connection 247 | proto.transport.close() 248 | self.loop.run_until_complete(proto.done) 249 | self.assertEqual('CLOSED', proto.state) 250 | 251 | def test_write_buffer_full(self): 252 | rpipe, wpipe = os.pipe() 253 | pipeobj = io.open(wpipe, 'wb', 1024) 254 | 255 | proto = MyWritePipeProto(loop=self.loop) 256 | connect = self.loop.connect_write_pipe(lambda: proto, pipeobj) 257 | transport, p = self.loop.run_until_complete(connect) 258 | self.assertIs(p, proto) 259 | self.assertIs(transport, proto.transport) 260 | self.assertEqual('CONNECTED', proto.state) 261 | 262 | for i in range(32): 263 | transport.write(b'x' * 32768) 264 | if proto.paused: 265 | transport.write(b'x' * 32768) 266 | break 267 | else: 268 | self.fail("Didn't reach a full buffer") 269 | 270 | os.close(rpipe) 271 | self.loop.run_until_complete(asyncio.wait_for(proto.done, 1)) 272 | self.assertEqual('CLOSED', proto.state) 273 | 274 | 275 | class Test_UV_Pipes(_BasePipeTest, tb.UVTestCase): 276 | pass 277 | 278 | 279 | class Test_AIO_Pipes(_BasePipeTest, tb.AIOTestCase): 280 | pass 281 | -------------------------------------------------------------------------------- /tests/test_process_spawning.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import ctypes.util 3 | import logging 4 | from concurrent.futures import ThreadPoolExecutor 5 | from threading import Thread 6 | from unittest import TestCase 7 | 8 | import uvloop 9 | 10 | 11 | class ProcessSpawningTestCollection(TestCase): 12 | 13 | def test_spawning_external_process(self): 14 | """Test spawning external process (using `popen` system call) that 15 | cause loop freeze.""" 16 | 17 | async def run(loop): 18 | event = asyncio.Event() 19 | 20 | dummy_workers = [simulate_loop_activity(loop, event) 21 | for _ in range(5)] 22 | spawn_worker = spawn_external_process(loop, event) 23 | done, pending = await asyncio.wait([ 24 | asyncio.ensure_future(fut) 25 | for fut in ([spawn_worker] + dummy_workers) 26 | ]) 27 | exceptions = [result.exception() 28 | for result in done if result.exception()] 29 | if exceptions: 30 | raise exceptions[0] 31 | 32 | return True 33 | 34 | async def simulate_loop_activity(loop, done_event): 35 | """Simulate loop activity by busy waiting for event.""" 36 | while True: 37 | try: 38 | await asyncio.wait_for(done_event.wait(), timeout=0.1) 39 | except asyncio.TimeoutError: 40 | pass 41 | 42 | if done_event.is_set(): 43 | return None 44 | 45 | async def spawn_external_process(loop, event): 46 | executor = ThreadPoolExecutor() 47 | try: 48 | call = loop.run_in_executor(executor, spawn_process) 49 | await asyncio.wait_for(call, timeout=3600) 50 | finally: 51 | event.set() 52 | executor.shutdown(wait=False) 53 | return True 54 | 55 | BUFFER_LENGTH = 1025 56 | BufferType = ctypes.c_char * (BUFFER_LENGTH - 1) 57 | 58 | def run_echo(popen, fread, pclose): 59 | fd = popen('echo test'.encode('ASCII'), 'r'.encode('ASCII')) 60 | try: 61 | while True: 62 | buffer = BufferType() 63 | data = ctypes.c_void_p(ctypes.addressof(buffer)) 64 | 65 | # -> this call will freeze whole loop in case of bug 66 | read = fread(data, 1, BUFFER_LENGTH, fd) 67 | if not read: 68 | break 69 | except Exception: 70 | logging.getLogger().exception('read error') 71 | raise 72 | finally: 73 | pclose(fd) 74 | 75 | def spawn_process(): 76 | """Spawn external process via `popen` system call.""" 77 | 78 | stdio = ctypes.CDLL(ctypes.util.find_library('c')) 79 | 80 | # popen system call 81 | popen = stdio.popen 82 | popen.argtypes = (ctypes.c_char_p, ctypes.c_char_p) 83 | popen.restype = ctypes.c_void_p 84 | 85 | # pclose system call 86 | pclose = stdio.pclose 87 | pclose.argtypes = (ctypes.c_void_p,) 88 | pclose.restype = ctypes.c_int 89 | 90 | # fread system call 91 | fread = stdio.fread 92 | fread.argtypes = (ctypes.c_void_p, ctypes.c_size_t, 93 | ctypes.c_size_t, ctypes.c_void_p) 94 | fread.restype = ctypes.c_size_t 95 | 96 | for iteration in range(1000): 97 | t = Thread(target=run_echo, 98 | args=(popen, fread, pclose), 99 | daemon=True) 100 | t.start() 101 | t.join(timeout=10.0) 102 | if t.is_alive(): 103 | raise Exception('process freeze detected at {}' 104 | .format(iteration)) 105 | 106 | return True 107 | 108 | loop = uvloop.new_event_loop() 109 | proc = loop.run_until_complete(run(loop)) 110 | self.assertTrue(proc) 111 | -------------------------------------------------------------------------------- /tests/test_regr1.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import queue 3 | import multiprocessing 4 | import signal 5 | import threading 6 | import unittest 7 | 8 | import uvloop 9 | 10 | from uvloop import _testbase as tb 11 | 12 | 13 | class EchoServerProtocol(asyncio.Protocol): 14 | 15 | def connection_made(self, transport): 16 | transport.write(b'z') 17 | 18 | 19 | class EchoClientProtocol(asyncio.Protocol): 20 | 21 | def __init__(self, loop): 22 | self.loop = loop 23 | 24 | def connection_made(self, transport): 25 | self.transport = transport 26 | 27 | def data_received(self, data): 28 | self.transport.close() 29 | 30 | def connection_lost(self, exc): 31 | self.loop.stop() 32 | 33 | 34 | class FailedTestError(BaseException): 35 | pass 36 | 37 | 38 | def run_server(quin, qout): 39 | server_loop = None 40 | 41 | def server_thread(): 42 | nonlocal server_loop 43 | loop = server_loop = uvloop.new_event_loop() 44 | asyncio.set_event_loop(loop) 45 | coro = loop.create_server(EchoServerProtocol, '127.0.0.1', 0) 46 | server = loop.run_until_complete(coro) 47 | addr = server.sockets[0].getsockname() 48 | qout.put(addr) 49 | loop.run_forever() 50 | server.close() 51 | loop.run_until_complete(server.wait_closed()) 52 | try: 53 | loop.close() 54 | except Exception as exc: 55 | print(exc) 56 | qout.put('stopped') 57 | 58 | thread = threading.Thread(target=server_thread, daemon=True) 59 | thread.start() 60 | 61 | quin.get() 62 | server_loop.call_soon_threadsafe(server_loop.stop) 63 | thread.join(1) 64 | 65 | 66 | class TestIssue39Regr(tb.UVTestCase): 67 | """See https://github.com/MagicStack/uvloop/issues/39 for details. 68 | 69 | Original code to reproduce the bug is by Jim Fulton. 70 | """ 71 | 72 | def on_alarm(self, sig, fr): 73 | if self.running: 74 | raise FailedTestError 75 | 76 | def run_test(self): 77 | for i in range(10): 78 | for threaded in [True, False]: 79 | if threaded: 80 | qin, qout = queue.Queue(), queue.Queue() 81 | threading.Thread( 82 | target=run_server, 83 | args=(qin, qout), 84 | daemon=True).start() 85 | else: 86 | qin = multiprocessing.Queue() 87 | qout = multiprocessing.Queue() 88 | multiprocessing.Process( 89 | target=run_server, 90 | args=(qin, qout), 91 | daemon=True).start() 92 | 93 | addr = qout.get() 94 | loop = self.new_loop() 95 | asyncio.set_event_loop(loop) 96 | loop.create_task( 97 | loop.create_connection( 98 | lambda: EchoClientProtocol(loop), 99 | host=addr[0], port=addr[1])) 100 | loop.run_forever() 101 | loop.close() 102 | qin.put('stop') 103 | qout.get() 104 | 105 | @unittest.skipIf( 106 | multiprocessing.get_start_method(False) == 'spawn', 107 | 'no need to test on macOS where spawn is used instead of fork') 108 | def test_issue39_regression(self): 109 | signal.signal(signal.SIGALRM, self.on_alarm) 110 | signal.alarm(5) 111 | 112 | try: 113 | self.running = True 114 | self.run_test() 115 | except FailedTestError: 116 | self.fail('deadlocked in libuv') 117 | finally: 118 | self.running = False 119 | signal.signal(signal.SIGALRM, signal.SIG_IGN) 120 | -------------------------------------------------------------------------------- /tests/test_runner.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import unittest 3 | import uvloop 4 | 5 | 6 | class TestSourceCode(unittest.TestCase): 7 | 8 | def test_uvloop_run_1(self): 9 | CNT = 0 10 | 11 | async def main(): 12 | nonlocal CNT 13 | CNT += 1 14 | 15 | loop = asyncio.get_running_loop() 16 | 17 | self.assertTrue(isinstance(loop, uvloop.Loop)) 18 | self.assertTrue(loop.get_debug()) 19 | 20 | return 'done' 21 | 22 | result = uvloop.run(main(), debug=True) 23 | 24 | self.assertEqual(result, 'done') 25 | self.assertEqual(CNT, 1) 26 | 27 | def test_uvloop_run_2(self): 28 | 29 | async def main(): 30 | pass 31 | 32 | coro = main() 33 | with self.assertRaisesRegex(TypeError, ' a non-uvloop event loop'): 34 | uvloop.run( 35 | coro, 36 | loop_factory=asyncio.DefaultEventLoopPolicy().new_event_loop, 37 | ) 38 | 39 | coro.close() 40 | -------------------------------------------------------------------------------- /tests/test_sourcecode.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import sys 4 | import unittest 5 | 6 | 7 | def find_uvloop_root(): 8 | return os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 9 | 10 | 11 | class TestSourceCode(unittest.TestCase): 12 | 13 | def test_flake8(self): 14 | edgepath = find_uvloop_root() 15 | config_path = os.path.join(edgepath, '.flake8') 16 | if not os.path.exists(config_path): 17 | raise RuntimeError('could not locate .flake8 file') 18 | 19 | try: 20 | import flake8 # NoQA 21 | except ImportError: 22 | raise unittest.SkipTest('flake8 module is missing') 23 | 24 | for subdir in ['examples', 'uvloop', 'tests']: 25 | try: 26 | subprocess.run( 27 | [sys.executable, '-m', 'flake8', '--config', config_path], 28 | check=True, 29 | stdout=subprocess.PIPE, 30 | stderr=subprocess.PIPE, 31 | cwd=os.path.join(edgepath, subdir)) 32 | except subprocess.CalledProcessError as ex: 33 | output = ex.stdout.decode() 34 | output += '\n' 35 | output += ex.stderr.decode() 36 | raise AssertionError( 37 | 'flake8 validation failed: {}\n{}'.format(ex, output) 38 | ) from None 39 | 40 | def test_mypy(self): 41 | edgepath = find_uvloop_root() 42 | config_path = os.path.join(edgepath, 'mypy.ini') 43 | if not os.path.exists(config_path): 44 | raise RuntimeError('could not locate mypy.ini file') 45 | 46 | try: 47 | import mypy # NoQA 48 | except ImportError: 49 | raise unittest.SkipTest('mypy module is missing') 50 | 51 | try: 52 | subprocess.run( 53 | [ 54 | sys.executable, 55 | '-m', 56 | 'mypy', 57 | '--config-file', 58 | config_path, 59 | 'uvloop' 60 | ], 61 | check=True, 62 | stdout=subprocess.PIPE, 63 | stderr=subprocess.PIPE, 64 | cwd=edgepath 65 | ) 66 | except subprocess.CalledProcessError as ex: 67 | output = ex.stdout.decode() 68 | output += '\n' 69 | output += ex.stderr.decode() 70 | raise AssertionError( 71 | 'mypy validation failed: {}\n{}'.format(ex, output) 72 | ) from None 73 | -------------------------------------------------------------------------------- /tests/test_testbase.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from uvloop import _testbase as tb 4 | 5 | 6 | class TestBaseTest(unittest.TestCase): 7 | 8 | def test_duplicate_methods(self): 9 | with self.assertRaisesRegex(RuntimeError, 'duplicate test Foo.test_a'): 10 | 11 | class Foo(tb.BaseTestCase): 12 | def test_a(self): 13 | pass 14 | 15 | def test_b(self): 16 | pass 17 | 18 | def test_a(self): # NOQA 19 | pass 20 | 21 | def test_duplicate_methods_parent_1(self): 22 | class FooBase: 23 | def test_a(self): 24 | pass 25 | 26 | with self.assertRaisesRegex(RuntimeError, 27 | 'duplicate test Foo.test_a.*' 28 | 'defined in FooBase'): 29 | 30 | class Foo(FooBase, tb.BaseTestCase): 31 | def test_b(self): 32 | pass 33 | 34 | def test_a(self): 35 | pass 36 | 37 | def test_duplicate_methods_parent_2(self): 38 | class FooBase(tb.BaseTestCase): 39 | def test_a(self): 40 | pass 41 | 42 | with self.assertRaisesRegex(RuntimeError, 43 | 'duplicate test Foo.test_a.*' 44 | 'defined in FooBase'): 45 | 46 | class Foo(FooBase): 47 | def test_b(self): 48 | pass 49 | 50 | def test_a(self): 51 | pass 52 | -------------------------------------------------------------------------------- /uvloop/.gitignore: -------------------------------------------------------------------------------- 1 | *.c 2 | *.html 3 | -------------------------------------------------------------------------------- /uvloop/__init__.py: -------------------------------------------------------------------------------- 1 | import asyncio as __asyncio 2 | import typing as _typing 3 | import sys as _sys 4 | import warnings as _warnings 5 | 6 | from asyncio.events import BaseDefaultEventLoopPolicy as __BasePolicy 7 | 8 | from . import includes as __includes # NOQA 9 | from .loop import Loop as __BaseLoop # NOQA 10 | from ._version import __version__ # NOQA 11 | 12 | 13 | __all__ = ('new_event_loop', 'install', 'EventLoopPolicy') 14 | 15 | 16 | _T = _typing.TypeVar("_T") 17 | 18 | 19 | class Loop(__BaseLoop, __asyncio.AbstractEventLoop): # type: ignore[misc] 20 | pass 21 | 22 | 23 | def new_event_loop() -> Loop: 24 | """Return a new event loop.""" 25 | return Loop() 26 | 27 | 28 | def install() -> None: 29 | """A helper function to install uvloop policy.""" 30 | if _sys.version_info[:2] >= (3, 12): 31 | _warnings.warn( 32 | 'uvloop.install() is deprecated in favor of uvloop.run() ' 33 | 'starting with Python 3.12.', 34 | DeprecationWarning, 35 | stacklevel=1, 36 | ) 37 | __asyncio.set_event_loop_policy(EventLoopPolicy()) 38 | 39 | 40 | if _typing.TYPE_CHECKING: 41 | def run( 42 | main: _typing.Coroutine[_typing.Any, _typing.Any, _T], 43 | *, 44 | loop_factory: _typing.Optional[ 45 | _typing.Callable[[], Loop] 46 | ] = new_event_loop, 47 | debug: _typing.Optional[bool]=None, 48 | ) -> _T: 49 | """The preferred way of running a coroutine with uvloop.""" 50 | else: 51 | def run(main, *, loop_factory=new_event_loop, debug=None, **run_kwargs): 52 | """The preferred way of running a coroutine with uvloop.""" 53 | 54 | async def wrapper(): 55 | # If `loop_factory` is provided we want it to return 56 | # either uvloop.Loop or a subtype of it, assuming the user 57 | # is using `uvloop.run()` intentionally. 58 | loop = __asyncio._get_running_loop() 59 | if not isinstance(loop, Loop): 60 | raise TypeError('uvloop.run() uses a non-uvloop event loop') 61 | return await main 62 | 63 | vi = _sys.version_info[:2] 64 | 65 | if vi <= (3, 10): 66 | # Copied from python/cpython 67 | 68 | if __asyncio._get_running_loop() is not None: 69 | raise RuntimeError( 70 | "asyncio.run() cannot be called from a running event loop") 71 | 72 | if not __asyncio.iscoroutine(main): 73 | raise ValueError( 74 | "a coroutine was expected, got {!r}".format(main) 75 | ) 76 | 77 | loop = loop_factory() 78 | try: 79 | __asyncio.set_event_loop(loop) 80 | if debug is not None: 81 | loop.set_debug(debug) 82 | return loop.run_until_complete(wrapper()) 83 | finally: 84 | try: 85 | _cancel_all_tasks(loop) 86 | loop.run_until_complete(loop.shutdown_asyncgens()) 87 | if hasattr(loop, 'shutdown_default_executor'): 88 | loop.run_until_complete( 89 | loop.shutdown_default_executor() 90 | ) 91 | finally: 92 | __asyncio.set_event_loop(None) 93 | loop.close() 94 | 95 | elif vi == (3, 11): 96 | if __asyncio._get_running_loop() is not None: 97 | raise RuntimeError( 98 | "asyncio.run() cannot be called from a running event loop") 99 | 100 | with __asyncio.Runner( 101 | loop_factory=loop_factory, 102 | debug=debug, 103 | **run_kwargs 104 | ) as runner: 105 | return runner.run(wrapper()) 106 | 107 | else: 108 | assert vi >= (3, 12) 109 | return __asyncio.run( 110 | wrapper(), 111 | loop_factory=loop_factory, 112 | debug=debug, 113 | **run_kwargs 114 | ) 115 | 116 | 117 | def _cancel_all_tasks(loop: __asyncio.AbstractEventLoop) -> None: 118 | # Copied from python/cpython 119 | 120 | to_cancel = __asyncio.all_tasks(loop) 121 | if not to_cancel: 122 | return 123 | 124 | for task in to_cancel: 125 | task.cancel() 126 | 127 | loop.run_until_complete( 128 | __asyncio.gather(*to_cancel, return_exceptions=True) 129 | ) 130 | 131 | for task in to_cancel: 132 | if task.cancelled(): 133 | continue 134 | if task.exception() is not None: 135 | loop.call_exception_handler({ 136 | 'message': 'unhandled exception during asyncio.run() shutdown', 137 | 'exception': task.exception(), 138 | 'task': task, 139 | }) 140 | 141 | 142 | class EventLoopPolicy(__BasePolicy): 143 | """Event loop policy. 144 | 145 | The preferred way to make your application use uvloop: 146 | 147 | >>> import asyncio 148 | >>> import uvloop 149 | >>> asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) 150 | >>> asyncio.get_event_loop() 151 | 152 | """ 153 | 154 | def _loop_factory(self) -> Loop: 155 | return new_event_loop() 156 | 157 | if _typing.TYPE_CHECKING: 158 | # EventLoopPolicy doesn't implement these, but since they are marked 159 | # as abstract in typeshed, we have to put them in so mypy thinks 160 | # the base methods are overridden. This is the same approach taken 161 | # for the Windows event loop policy classes in typeshed. 162 | def get_child_watcher(self) -> _typing.NoReturn: 163 | ... 164 | 165 | def set_child_watcher( 166 | self, watcher: _typing.Any 167 | ) -> _typing.NoReturn: 168 | ... 169 | -------------------------------------------------------------------------------- /uvloop/_noop.py: -------------------------------------------------------------------------------- 1 | def noop() -> None: 2 | """Empty function to invoke CPython ceval loop.""" 3 | return 4 | -------------------------------------------------------------------------------- /uvloop/_version.py: -------------------------------------------------------------------------------- 1 | # This file MUST NOT contain anything but the __version__ assignment. 2 | # 3 | # When making a release, change the value of __version__ 4 | # to an appropriate value, and open a pull request against 5 | # the correct branch (master if making a new feature release). 6 | # The commit message MUST contain a properly formatted release 7 | # log, and the commit must be signed. 8 | # 9 | # The release automation will: build and test the packages for the 10 | # supported platforms, publish the packages on PyPI, merge the PR 11 | # to the target branch, create a Git tag pointing to the commit. 12 | 13 | __version__ = '0.21.0' 14 | -------------------------------------------------------------------------------- /uvloop/cbhandles.pxd: -------------------------------------------------------------------------------- 1 | cdef class Handle: 2 | cdef: 3 | Loop loop 4 | object context 5 | bint _cancelled 6 | 7 | str meth_name 8 | int cb_type 9 | void *callback 10 | object arg1, arg2, arg3, arg4 11 | 12 | object __weakref__ 13 | 14 | readonly _source_traceback 15 | 16 | cdef inline _set_loop(self, Loop loop) 17 | cdef inline _set_context(self, object context) 18 | 19 | cdef inline _run(self) 20 | cdef _cancel(self) 21 | 22 | cdef _format_handle(self) 23 | 24 | 25 | cdef class TimerHandle: 26 | cdef: 27 | object callback 28 | tuple args 29 | bint _cancelled 30 | UVTimer timer 31 | Loop loop 32 | object context 33 | tuple _debug_info 34 | object __weakref__ 35 | object _when 36 | 37 | cdef _run(self) 38 | cdef _cancel(self) 39 | cdef inline _clear(self) 40 | -------------------------------------------------------------------------------- /uvloop/errors.pyx: -------------------------------------------------------------------------------- 1 | cdef str __strerr(int errno): 2 | return strerror(errno).decode() 3 | 4 | 5 | cdef __convert_python_error(int uverr): 6 | # XXX Won't work for Windows: 7 | # From libuv docs: 8 | # Implementation detail: on Unix error codes are the 9 | # negated errno (or -errno), while on Windows they 10 | # are defined by libuv to arbitrary negative numbers. 11 | cdef int oserr = -uverr 12 | 13 | exc = OSError 14 | 15 | if uverr in (uv.UV_EACCES, uv.UV_EPERM): 16 | exc = PermissionError 17 | 18 | elif uverr in (uv.UV_EAGAIN, uv.UV_EALREADY): 19 | exc = BlockingIOError 20 | 21 | elif uverr in (uv.UV_EPIPE, uv.UV_ESHUTDOWN): 22 | exc = BrokenPipeError 23 | 24 | elif uverr == uv.UV_ECONNABORTED: 25 | exc = ConnectionAbortedError 26 | 27 | elif uverr == uv.UV_ECONNREFUSED: 28 | exc = ConnectionRefusedError 29 | 30 | elif uverr == uv.UV_ECONNRESET: 31 | exc = ConnectionResetError 32 | 33 | elif uverr == uv.UV_EEXIST: 34 | exc = FileExistsError 35 | 36 | elif uverr == uv.UV_ENOENT: 37 | exc = FileNotFoundError 38 | 39 | elif uverr == uv.UV_EINTR: 40 | exc = InterruptedError 41 | 42 | elif uverr == uv.UV_EISDIR: 43 | exc = IsADirectoryError 44 | 45 | elif uverr == uv.UV_ESRCH: 46 | exc = ProcessLookupError 47 | 48 | elif uverr == uv.UV_ETIMEDOUT: 49 | exc = TimeoutError 50 | 51 | return exc(oserr, __strerr(oserr)) 52 | 53 | 54 | cdef int __convert_socket_error(int uverr): 55 | cdef int sock_err = 0 56 | 57 | if uverr == uv.UV_EAI_ADDRFAMILY: 58 | sock_err = socket_EAI_ADDRFAMILY 59 | 60 | elif uverr == uv.UV_EAI_AGAIN: 61 | sock_err = socket_EAI_AGAIN 62 | 63 | elif uverr == uv.UV_EAI_BADFLAGS: 64 | sock_err = socket_EAI_BADFLAGS 65 | 66 | elif uverr == uv.UV_EAI_BADHINTS: 67 | sock_err = socket_EAI_BADHINTS 68 | 69 | elif uverr == uv.UV_EAI_CANCELED: 70 | sock_err = socket_EAI_CANCELED 71 | 72 | elif uverr == uv.UV_EAI_FAIL: 73 | sock_err = socket_EAI_FAIL 74 | 75 | elif uverr == uv.UV_EAI_FAMILY: 76 | sock_err = socket_EAI_FAMILY 77 | 78 | elif uverr == uv.UV_EAI_MEMORY: 79 | sock_err = socket_EAI_MEMORY 80 | 81 | elif uverr == uv.UV_EAI_NODATA: 82 | sock_err = socket_EAI_NODATA 83 | 84 | elif uverr == uv.UV_EAI_NONAME: 85 | sock_err = socket_EAI_NONAME 86 | 87 | elif uverr == uv.UV_EAI_OVERFLOW: 88 | sock_err = socket_EAI_OVERFLOW 89 | 90 | elif uverr == uv.UV_EAI_PROTOCOL: 91 | sock_err = socket_EAI_PROTOCOL 92 | 93 | elif uverr == uv.UV_EAI_SERVICE: 94 | sock_err = socket_EAI_SERVICE 95 | 96 | elif uverr == uv.UV_EAI_SOCKTYPE: 97 | sock_err = socket_EAI_SOCKTYPE 98 | 99 | return sock_err 100 | 101 | 102 | cdef convert_error(int uverr): 103 | cdef int sock_err 104 | 105 | if uverr == uv.UV_ECANCELED: 106 | return aio_CancelledError() 107 | 108 | sock_err = __convert_socket_error(uverr) 109 | if sock_err: 110 | msg = system.gai_strerror(sock_err).decode('utf-8') 111 | return socket_gaierror(sock_err, msg) 112 | 113 | return __convert_python_error(uverr) 114 | -------------------------------------------------------------------------------- /uvloop/handles/async_.pxd: -------------------------------------------------------------------------------- 1 | cdef class UVAsync(UVHandle): 2 | cdef: 3 | method_t callback 4 | object ctx 5 | 6 | cdef _init(self, Loop loop, method_t callback, object ctx) 7 | 8 | cdef send(self) 9 | 10 | @staticmethod 11 | cdef UVAsync new(Loop loop, method_t callback, object ctx) 12 | -------------------------------------------------------------------------------- /uvloop/handles/async_.pyx: -------------------------------------------------------------------------------- 1 | @cython.no_gc_clear 2 | cdef class UVAsync(UVHandle): 3 | cdef _init(self, Loop loop, method_t callback, object ctx): 4 | cdef int err 5 | 6 | self._start_init(loop) 7 | 8 | self._handle = PyMem_RawMalloc(sizeof(uv.uv_async_t)) 9 | if self._handle is NULL: 10 | self._abort_init() 11 | raise MemoryError() 12 | 13 | err = uv.uv_async_init(self._loop.uvloop, 14 | self._handle, 15 | __uvasync_callback) 16 | if err < 0: 17 | self._abort_init() 18 | raise convert_error(err) 19 | 20 | self._finish_init() 21 | 22 | self.callback = callback 23 | self.ctx = ctx 24 | 25 | cdef send(self): 26 | cdef int err 27 | 28 | self._ensure_alive() 29 | 30 | err = uv.uv_async_send(self._handle) 31 | if err < 0: 32 | exc = convert_error(err) 33 | self._fatal_error(exc, True) 34 | return 35 | 36 | @staticmethod 37 | cdef UVAsync new(Loop loop, method_t callback, object ctx): 38 | cdef UVAsync handle 39 | handle = UVAsync.__new__(UVAsync) 40 | handle._init(loop, callback, ctx) 41 | return handle 42 | 43 | 44 | cdef void __uvasync_callback( 45 | uv.uv_async_t* handle, 46 | ) noexcept with gil: 47 | if __ensure_handle_data(handle, "UVAsync callback") == 0: 48 | return 49 | 50 | cdef: 51 | UVAsync async_ = handle.data 52 | method_t cb = async_.callback 53 | try: 54 | cb(async_.ctx) 55 | except BaseException as ex: 56 | async_._error(ex, False) 57 | -------------------------------------------------------------------------------- /uvloop/handles/basetransport.pxd: -------------------------------------------------------------------------------- 1 | cdef class UVBaseTransport(UVSocketHandle): 2 | 3 | cdef: 4 | readonly bint _closing 5 | 6 | bint _protocol_connected 7 | bint _protocol_paused 8 | object _protocol_data_received 9 | size_t _high_water 10 | size_t _low_water 11 | 12 | object _protocol 13 | Server _server 14 | object _waiter 15 | 16 | dict _extra_info 17 | 18 | uint32_t _conn_lost 19 | 20 | object __weakref__ 21 | 22 | # All "inline" methods are final 23 | 24 | cdef inline _maybe_pause_protocol(self) 25 | cdef inline _maybe_resume_protocol(self) 26 | 27 | cdef inline _schedule_call_connection_made(self) 28 | cdef inline _schedule_call_connection_lost(self, exc) 29 | 30 | cdef _wakeup_waiter(self) 31 | cdef _call_connection_made(self) 32 | cdef _call_connection_lost(self, exc) 33 | 34 | # Overloads of UVHandle methods: 35 | cdef _fatal_error(self, exc, throw, reason=?) 36 | cdef _close(self) 37 | 38 | cdef inline _set_server(self, Server server) 39 | cdef inline _set_waiter(self, object waiter) 40 | 41 | cdef _set_protocol(self, object protocol) 42 | cdef _clear_protocol(self) 43 | 44 | cdef inline _init_protocol(self) 45 | cdef inline _add_extra_info(self, str name, object obj) 46 | 47 | # === overloads === 48 | 49 | cdef _new_socket(self) 50 | cdef size_t _get_write_buffer_size(self) 51 | 52 | cdef bint _is_reading(self) 53 | cdef _start_reading(self) 54 | cdef _stop_reading(self) 55 | -------------------------------------------------------------------------------- /uvloop/handles/check.pxd: -------------------------------------------------------------------------------- 1 | cdef class UVCheck(UVHandle): 2 | cdef: 3 | Handle h 4 | bint running 5 | 6 | # All "inline" methods are final 7 | 8 | cdef _init(self, Loop loop, Handle h) 9 | 10 | cdef inline stop(self) 11 | cdef inline start(self) 12 | 13 | @staticmethod 14 | cdef UVCheck new(Loop loop, Handle h) 15 | -------------------------------------------------------------------------------- /uvloop/handles/check.pyx: -------------------------------------------------------------------------------- 1 | @cython.no_gc_clear 2 | cdef class UVCheck(UVHandle): 3 | cdef _init(self, Loop loop, Handle h): 4 | cdef int err 5 | 6 | self._start_init(loop) 7 | 8 | self._handle = PyMem_RawMalloc(sizeof(uv.uv_check_t)) 9 | if self._handle is NULL: 10 | self._abort_init() 11 | raise MemoryError() 12 | 13 | err = uv.uv_check_init(self._loop.uvloop, self._handle) 14 | if err < 0: 15 | self._abort_init() 16 | raise convert_error(err) 17 | 18 | self._finish_init() 19 | 20 | self.h = h 21 | self.running = 0 22 | 23 | cdef inline stop(self): 24 | cdef int err 25 | 26 | if not self._is_alive(): 27 | self.running = 0 28 | return 29 | 30 | if self.running == 1: 31 | err = uv.uv_check_stop(self._handle) 32 | self.running = 0 33 | if err < 0: 34 | exc = convert_error(err) 35 | self._fatal_error(exc, True) 36 | return 37 | 38 | cdef inline start(self): 39 | cdef int err 40 | 41 | self._ensure_alive() 42 | 43 | if self.running == 0: 44 | err = uv.uv_check_start(self._handle, 45 | cb_check_callback) 46 | if err < 0: 47 | exc = convert_error(err) 48 | self._fatal_error(exc, True) 49 | return 50 | self.running = 1 51 | 52 | @staticmethod 53 | cdef UVCheck new(Loop loop, Handle h): 54 | cdef UVCheck handle 55 | handle = UVCheck.__new__(UVCheck) 56 | handle._init(loop, h) 57 | return handle 58 | 59 | 60 | cdef void cb_check_callback( 61 | uv.uv_check_t* handle, 62 | ) noexcept with gil: 63 | if __ensure_handle_data(handle, "UVCheck callback") == 0: 64 | return 65 | 66 | cdef: 67 | UVCheck check = handle.data 68 | Handle h = check.h 69 | try: 70 | h._run() 71 | except BaseException as ex: 72 | check._error(ex, False) 73 | -------------------------------------------------------------------------------- /uvloop/handles/fsevent.pxd: -------------------------------------------------------------------------------- 1 | cdef class UVFSEvent(UVHandle): 2 | cdef: 3 | object callback 4 | bint running 5 | 6 | cdef _init(self, Loop loop, object callback, object context) 7 | cdef _close(self) 8 | cdef start(self, char* path, int flags) 9 | cdef stop(self) 10 | 11 | @staticmethod 12 | cdef UVFSEvent new(Loop loop, object callback, object context) 13 | -------------------------------------------------------------------------------- /uvloop/handles/fsevent.pyx: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | 4 | class FileSystemEvent(enum.IntEnum): 5 | RENAME = uv.UV_RENAME 6 | CHANGE = uv.UV_CHANGE 7 | RENAME_CHANGE = RENAME | CHANGE 8 | 9 | 10 | @cython.no_gc_clear 11 | cdef class UVFSEvent(UVHandle): 12 | cdef _init(self, Loop loop, object callback, object context): 13 | cdef int err 14 | 15 | self._start_init(loop) 16 | 17 | self._handle = PyMem_RawMalloc( 18 | sizeof(uv.uv_fs_event_t) 19 | ) 20 | if self._handle is NULL: 21 | self._abort_init() 22 | raise MemoryError() 23 | 24 | err = uv.uv_fs_event_init( 25 | self._loop.uvloop, self._handle 26 | ) 27 | if err < 0: 28 | self._abort_init() 29 | raise convert_error(err) 30 | 31 | self._finish_init() 32 | 33 | self.running = 0 34 | self.callback = callback 35 | if context is None: 36 | context = Context_CopyCurrent() 37 | self.context = context 38 | 39 | cdef start(self, char* path, int flags): 40 | cdef int err 41 | 42 | self._ensure_alive() 43 | 44 | if self.running == 0: 45 | err = uv.uv_fs_event_start( 46 | self._handle, 47 | __uvfsevent_callback, 48 | path, 49 | flags, 50 | ) 51 | if err < 0: 52 | exc = convert_error(err) 53 | self._fatal_error(exc, True) 54 | return 55 | self.running = 1 56 | 57 | cdef stop(self): 58 | cdef int err 59 | 60 | if not self._is_alive(): 61 | self.running = 0 62 | return 63 | 64 | if self.running == 1: 65 | err = uv.uv_fs_event_stop(self._handle) 66 | self.running = 0 67 | if err < 0: 68 | exc = convert_error(err) 69 | self._fatal_error(exc, True) 70 | return 71 | 72 | cdef _close(self): 73 | try: 74 | self.stop() 75 | finally: 76 | UVHandle._close(self) 77 | 78 | def cancel(self): 79 | self._close() 80 | 81 | def cancelled(self): 82 | return self.running == 0 83 | 84 | @staticmethod 85 | cdef UVFSEvent new(Loop loop, object callback, object context): 86 | cdef UVFSEvent handle 87 | handle = UVFSEvent.__new__(UVFSEvent) 88 | handle._init(loop, callback, context) 89 | return handle 90 | 91 | 92 | cdef void __uvfsevent_callback( 93 | uv.uv_fs_event_t* handle, 94 | const char *filename, 95 | int events, 96 | int status, 97 | ) noexcept with gil: 98 | if __ensure_handle_data( 99 | handle, "UVFSEvent callback" 100 | ) == 0: 101 | return 102 | 103 | cdef: 104 | UVFSEvent fs_event = handle.data 105 | Handle h 106 | 107 | try: 108 | h = new_Handle( 109 | fs_event._loop, 110 | fs_event.callback, 111 | (filename, FileSystemEvent(events)), 112 | fs_event.context, 113 | ) 114 | h._run() 115 | except BaseException as ex: 116 | fs_event._error(ex, False) 117 | -------------------------------------------------------------------------------- /uvloop/handles/handle.pxd: -------------------------------------------------------------------------------- 1 | cdef class UVHandle: 2 | cdef: 3 | uv.uv_handle_t *_handle 4 | Loop _loop 5 | readonly _source_traceback 6 | bint _closed 7 | bint _inited 8 | object context 9 | 10 | # Added to enable current UDPTransport implementation, 11 | # which doesn't use libuv handles. 12 | bint _has_handle 13 | 14 | # All "inline" methods are final 15 | 16 | cdef inline _start_init(self, Loop loop) 17 | cdef inline _abort_init(self) 18 | cdef inline _finish_init(self) 19 | 20 | cdef inline bint _is_alive(self) 21 | cdef inline _ensure_alive(self) 22 | 23 | cdef _error(self, exc, throw) 24 | cdef _fatal_error(self, exc, throw, reason=?) 25 | 26 | cdef _warn_unclosed(self) 27 | 28 | cdef _free(self) 29 | cdef _close(self) 30 | 31 | 32 | cdef class UVSocketHandle(UVHandle): 33 | cdef: 34 | # Points to a Python file-object that should be closed 35 | # when the transport is closing. Used by pipes. This 36 | # should probably be refactored somehow. 37 | object _fileobj 38 | object __cached_socket 39 | 40 | # All "inline" methods are final 41 | 42 | cdef _fileno(self) 43 | 44 | cdef _new_socket(self) 45 | cdef inline _get_socket(self) 46 | cdef inline _attach_fileobj(self, object file) 47 | 48 | cdef _open(self, int sockfd) 49 | -------------------------------------------------------------------------------- /uvloop/handles/idle.pxd: -------------------------------------------------------------------------------- 1 | cdef class UVIdle(UVHandle): 2 | cdef: 3 | Handle h 4 | bint running 5 | 6 | # All "inline" methods are final 7 | 8 | cdef _init(self, Loop loop, Handle h) 9 | 10 | cdef inline stop(self) 11 | cdef inline start(self) 12 | 13 | @staticmethod 14 | cdef UVIdle new(Loop loop, Handle h) 15 | -------------------------------------------------------------------------------- /uvloop/handles/idle.pyx: -------------------------------------------------------------------------------- 1 | @cython.no_gc_clear 2 | cdef class UVIdle(UVHandle): 3 | cdef _init(self, Loop loop, Handle h): 4 | cdef int err 5 | 6 | self._start_init(loop) 7 | 8 | self._handle = PyMem_RawMalloc(sizeof(uv.uv_idle_t)) 9 | if self._handle is NULL: 10 | self._abort_init() 11 | raise MemoryError() 12 | 13 | err = uv.uv_idle_init(self._loop.uvloop, self._handle) 14 | if err < 0: 15 | self._abort_init() 16 | raise convert_error(err) 17 | 18 | self._finish_init() 19 | 20 | self.h = h 21 | self.running = 0 22 | 23 | cdef inline stop(self): 24 | cdef int err 25 | 26 | if not self._is_alive(): 27 | self.running = 0 28 | return 29 | 30 | if self.running == 1: 31 | err = uv.uv_idle_stop(self._handle) 32 | self.running = 0 33 | if err < 0: 34 | exc = convert_error(err) 35 | self._fatal_error(exc, True) 36 | return 37 | 38 | cdef inline start(self): 39 | cdef int err 40 | 41 | self._ensure_alive() 42 | 43 | if self.running == 0: 44 | err = uv.uv_idle_start(self._handle, 45 | cb_idle_callback) 46 | if err < 0: 47 | exc = convert_error(err) 48 | self._fatal_error(exc, True) 49 | return 50 | self.running = 1 51 | 52 | @staticmethod 53 | cdef UVIdle new(Loop loop, Handle h): 54 | cdef UVIdle handle 55 | handle = UVIdle.__new__(UVIdle) 56 | handle._init(loop, h) 57 | return handle 58 | 59 | 60 | cdef void cb_idle_callback( 61 | uv.uv_idle_t* handle, 62 | ) noexcept with gil: 63 | if __ensure_handle_data(handle, "UVIdle callback") == 0: 64 | return 65 | 66 | cdef: 67 | UVIdle idle = handle.data 68 | Handle h = idle.h 69 | try: 70 | h._run() 71 | except BaseException as ex: 72 | idle._error(ex, False) 73 | -------------------------------------------------------------------------------- /uvloop/handles/pipe.pxd: -------------------------------------------------------------------------------- 1 | cdef class UnixServer(UVStreamServer): 2 | 3 | cdef bind(self, str path) 4 | 5 | @staticmethod 6 | cdef UnixServer new(Loop loop, object protocol_factory, Server server, 7 | object backlog, 8 | object ssl, 9 | object ssl_handshake_timeout, 10 | object ssl_shutdown_timeout) 11 | 12 | 13 | cdef class UnixTransport(UVStream): 14 | 15 | @staticmethod 16 | cdef UnixTransport new(Loop loop, object protocol, Server server, 17 | object waiter, object context) 18 | 19 | cdef connect(self, char* addr) 20 | 21 | 22 | cdef class ReadUnixTransport(UVStream): 23 | 24 | @staticmethod 25 | cdef ReadUnixTransport new(Loop loop, object protocol, Server server, 26 | object waiter) 27 | 28 | 29 | cdef class WriteUnixTransport(UVStream): 30 | 31 | @staticmethod 32 | cdef WriteUnixTransport new(Loop loop, object protocol, Server server, 33 | object waiter) 34 | -------------------------------------------------------------------------------- /uvloop/handles/pipe.pyx: -------------------------------------------------------------------------------- 1 | cdef __pipe_init_uv_handle(UVStream handle, Loop loop): 2 | cdef int err 3 | 4 | handle._handle = PyMem_RawMalloc(sizeof(uv.uv_pipe_t)) 5 | if handle._handle is NULL: 6 | handle._abort_init() 7 | raise MemoryError() 8 | 9 | # Initialize pipe handle with ipc=0. 10 | # ipc=1 means that libuv will use recvmsg/sendmsg 11 | # instead of recv/send. 12 | err = uv.uv_pipe_init(handle._loop.uvloop, 13 | handle._handle, 14 | 0) 15 | # UV_HANDLE_READABLE allows calling uv_read_start() on this pipe 16 | # even if it is O_WRONLY, see also #317, libuv/libuv#2058 17 | handle._handle.flags |= uv.UV_INTERNAL_HANDLE_READABLE 18 | if err < 0: 19 | handle._abort_init() 20 | raise convert_error(err) 21 | 22 | handle._finish_init() 23 | 24 | 25 | cdef __pipe_open(UVStream handle, int fd): 26 | cdef int err 27 | err = uv.uv_pipe_open(handle._handle, 28 | fd) 29 | if err < 0: 30 | exc = convert_error(err) 31 | raise exc 32 | 33 | 34 | cdef __pipe_get_socket(UVSocketHandle handle): 35 | fileno = handle._fileno() 36 | return PseudoSocket(uv.AF_UNIX, uv.SOCK_STREAM, 0, fileno) 37 | 38 | 39 | @cython.no_gc_clear 40 | cdef class UnixServer(UVStreamServer): 41 | 42 | @staticmethod 43 | cdef UnixServer new(Loop loop, object protocol_factory, Server server, 44 | object backlog, 45 | object ssl, 46 | object ssl_handshake_timeout, 47 | object ssl_shutdown_timeout): 48 | 49 | cdef UnixServer handle 50 | handle = UnixServer.__new__(UnixServer) 51 | handle._init(loop, protocol_factory, server, backlog, 52 | ssl, ssl_handshake_timeout, ssl_shutdown_timeout) 53 | __pipe_init_uv_handle(handle, loop) 54 | return handle 55 | 56 | cdef _new_socket(self): 57 | return __pipe_get_socket(self) 58 | 59 | cdef _open(self, int sockfd): 60 | self._ensure_alive() 61 | __pipe_open(self, sockfd) 62 | self._mark_as_open() 63 | 64 | cdef bind(self, str path): 65 | cdef int err 66 | self._ensure_alive() 67 | err = uv.uv_pipe_bind(self._handle, 68 | path.encode()) 69 | if err < 0: 70 | exc = convert_error(err) 71 | self._fatal_error(exc, True) 72 | return 73 | 74 | self._mark_as_open() 75 | 76 | cdef UVStream _make_new_transport(self, object protocol, object waiter, 77 | object context): 78 | cdef UnixTransport tr 79 | tr = UnixTransport.new(self._loop, protocol, self._server, waiter, 80 | context) 81 | return tr 82 | 83 | cdef _close(self): 84 | sock = self._fileobj 85 | if sock is not None and sock in self._loop._unix_server_sockets: 86 | path = sock.getsockname() 87 | else: 88 | path = None 89 | 90 | UVStreamServer._close(self) 91 | 92 | if path is not None: 93 | prev_ino = self._loop._unix_server_sockets[sock] 94 | del self._loop._unix_server_sockets[sock] 95 | try: 96 | if os_stat(path).st_ino == prev_ino: 97 | os_unlink(path) 98 | except FileNotFoundError: 99 | pass 100 | except OSError as err: 101 | aio_logger.error('Unable to clean up listening UNIX socket ' 102 | '%r: %r', path, err) 103 | 104 | 105 | @cython.no_gc_clear 106 | cdef class UnixTransport(UVStream): 107 | 108 | @staticmethod 109 | cdef UnixTransport new(Loop loop, object protocol, Server server, 110 | object waiter, object context): 111 | 112 | cdef UnixTransport handle 113 | handle = UnixTransport.__new__(UnixTransport) 114 | handle._init(loop, protocol, server, waiter, context) 115 | __pipe_init_uv_handle(handle, loop) 116 | return handle 117 | 118 | cdef _new_socket(self): 119 | return __pipe_get_socket(self) 120 | 121 | cdef _open(self, int sockfd): 122 | __pipe_open(self, sockfd) 123 | 124 | cdef connect(self, char* addr): 125 | cdef _PipeConnectRequest req 126 | req = _PipeConnectRequest(self._loop, self) 127 | req.connect(addr) 128 | 129 | 130 | @cython.no_gc_clear 131 | cdef class ReadUnixTransport(UVStream): 132 | 133 | @staticmethod 134 | cdef ReadUnixTransport new(Loop loop, object protocol, Server server, 135 | object waiter): 136 | cdef ReadUnixTransport handle 137 | handle = ReadUnixTransport.__new__(ReadUnixTransport) 138 | # This is only used in connect_read_pipe() and subprocess_shell/exec() 139 | # directly, we could simply copy the current context. 140 | handle._init(loop, protocol, server, waiter, Context_CopyCurrent()) 141 | __pipe_init_uv_handle(handle, loop) 142 | return handle 143 | 144 | cdef _new_socket(self): 145 | return __pipe_get_socket(self) 146 | 147 | cdef _open(self, int sockfd): 148 | __pipe_open(self, sockfd) 149 | 150 | def get_write_buffer_limits(self): 151 | raise NotImplementedError 152 | 153 | def set_write_buffer_limits(self, high=None, low=None): 154 | raise NotImplementedError 155 | 156 | def get_write_buffer_size(self): 157 | raise NotImplementedError 158 | 159 | def write(self, data): 160 | raise NotImplementedError 161 | 162 | def writelines(self, list_of_data): 163 | raise NotImplementedError 164 | 165 | def write_eof(self): 166 | raise NotImplementedError 167 | 168 | def can_write_eof(self): 169 | raise NotImplementedError 170 | 171 | def abort(self): 172 | raise NotImplementedError 173 | 174 | 175 | @cython.no_gc_clear 176 | cdef class WriteUnixTransport(UVStream): 177 | 178 | @staticmethod 179 | cdef WriteUnixTransport new(Loop loop, object protocol, Server server, 180 | object waiter): 181 | cdef WriteUnixTransport handle 182 | handle = WriteUnixTransport.__new__(WriteUnixTransport) 183 | 184 | # We listen for read events on write-end of the pipe. When 185 | # the read-end is close, the uv_stream_t.read callback will 186 | # receive an error -- we want to silence that error, and just 187 | # close the transport. 188 | handle._close_on_read_error() 189 | 190 | # This is only used in connect_write_pipe() and subprocess_shell/exec() 191 | # directly, we could simply copy the current context. 192 | handle._init(loop, protocol, server, waiter, Context_CopyCurrent()) 193 | __pipe_init_uv_handle(handle, loop) 194 | return handle 195 | 196 | cdef _new_socket(self): 197 | return __pipe_get_socket(self) 198 | 199 | cdef _open(self, int sockfd): 200 | __pipe_open(self, sockfd) 201 | 202 | def pause_reading(self): 203 | raise NotImplementedError 204 | 205 | def resume_reading(self): 206 | raise NotImplementedError 207 | 208 | 209 | cdef class _PipeConnectRequest(UVRequest): 210 | cdef: 211 | UnixTransport transport 212 | uv.uv_connect_t _req_data 213 | 214 | def __cinit__(self, loop, transport): 215 | self.request = &self._req_data 216 | self.request.data = self 217 | self.transport = transport 218 | 219 | cdef connect(self, char* addr): 220 | # uv_pipe_connect returns void 221 | uv.uv_pipe_connect(self.request, 222 | self.transport._handle, 223 | addr, 224 | __pipe_connect_callback) 225 | 226 | cdef void __pipe_connect_callback( 227 | uv.uv_connect_t* req, 228 | int status, 229 | ) noexcept with gil: 230 | cdef: 231 | _PipeConnectRequest wrapper 232 | UnixTransport transport 233 | 234 | wrapper = <_PipeConnectRequest> req.data 235 | transport = wrapper.transport 236 | 237 | if status < 0: 238 | exc = convert_error(status) 239 | else: 240 | exc = None 241 | 242 | try: 243 | transport._on_connect(exc) 244 | except BaseException as ex: 245 | wrapper.transport._fatal_error(ex, False) 246 | finally: 247 | wrapper.on_done() 248 | -------------------------------------------------------------------------------- /uvloop/handles/poll.pxd: -------------------------------------------------------------------------------- 1 | cdef class UVPoll(UVHandle): 2 | cdef: 3 | int fd 4 | Handle reading_handle 5 | Handle writing_handle 6 | 7 | cdef _init(self, Loop loop, int fd) 8 | cdef _close(self) 9 | 10 | cdef inline _poll_start(self, int flags) 11 | cdef inline _poll_stop(self) 12 | 13 | cdef int is_active(self) noexcept 14 | 15 | cdef is_reading(self) 16 | cdef is_writing(self) 17 | 18 | cdef start_reading(self, Handle callback) 19 | cdef start_writing(self, Handle callback) 20 | cdef stop_reading(self) 21 | cdef stop_writing(self) 22 | cdef stop(self) 23 | 24 | @staticmethod 25 | cdef UVPoll new(Loop loop, int fd) 26 | -------------------------------------------------------------------------------- /uvloop/handles/poll.pyx: -------------------------------------------------------------------------------- 1 | @cython.no_gc_clear 2 | cdef class UVPoll(UVHandle): 3 | cdef _init(self, Loop loop, int fd): 4 | cdef int err 5 | 6 | self._start_init(loop) 7 | 8 | self._handle = PyMem_RawMalloc(sizeof(uv.uv_poll_t)) 9 | if self._handle is NULL: 10 | self._abort_init() 11 | raise MemoryError() 12 | 13 | err = uv.uv_poll_init(self._loop.uvloop, 14 | self._handle, fd) 15 | if err < 0: 16 | self._abort_init() 17 | raise convert_error(err) 18 | 19 | self._finish_init() 20 | 21 | self.fd = fd 22 | self.reading_handle = None 23 | self.writing_handle = None 24 | 25 | @staticmethod 26 | cdef UVPoll new(Loop loop, int fd): 27 | cdef UVPoll handle 28 | handle = UVPoll.__new__(UVPoll) 29 | handle._init(loop, fd) 30 | return handle 31 | 32 | cdef int is_active(self) noexcept: 33 | return (self.reading_handle is not None or 34 | self.writing_handle is not None) 35 | 36 | cdef inline _poll_start(self, int flags): 37 | cdef int err 38 | 39 | self._ensure_alive() 40 | 41 | err = uv.uv_poll_start( 42 | self._handle, 43 | flags, 44 | __on_uvpoll_event) 45 | 46 | if err < 0: 47 | exc = convert_error(err) 48 | self._fatal_error(exc, True) 49 | return 50 | 51 | cdef inline _poll_stop(self): 52 | cdef int err 53 | 54 | if not self._is_alive(): 55 | return 56 | 57 | err = uv.uv_poll_stop(self._handle) 58 | if err < 0: 59 | exc = convert_error(err) 60 | self._fatal_error(exc, True) 61 | return 62 | 63 | cdef: 64 | int backend_id 65 | system.epoll_event dummy_event 66 | 67 | if system.PLATFORM_IS_LINUX: 68 | # libuv doesn't remove the FD from epoll immediately 69 | # after uv_poll_stop or uv_poll_close, causing hard 70 | # to debug issue with dup-ed file descriptors causing 71 | # CPU burn in epoll/epoll_ctl: 72 | # https://github.com/MagicStack/uvloop/issues/61 73 | # 74 | # It's safe though to manually call epoll_ctl here, 75 | # after calling uv_poll_stop. 76 | 77 | backend_id = uv.uv_backend_fd(self._loop.uvloop) 78 | if backend_id != -1: 79 | memset(&dummy_event, 0, sizeof(dummy_event)) 80 | system.epoll_ctl( 81 | backend_id, 82 | system.EPOLL_CTL_DEL, 83 | self.fd, 84 | &dummy_event) # ignore errors 85 | 86 | cdef is_reading(self): 87 | return self._is_alive() and self.reading_handle is not None 88 | 89 | cdef is_writing(self): 90 | return self._is_alive() and self.writing_handle is not None 91 | 92 | cdef start_reading(self, Handle callback): 93 | cdef: 94 | int mask = 0 95 | 96 | if self.reading_handle is None: 97 | # not reading right now, setup the handle 98 | 99 | mask = uv.UV_READABLE 100 | if self.writing_handle is not None: 101 | # are we writing right now? 102 | mask |= uv.UV_WRITABLE 103 | 104 | self._poll_start(mask) 105 | else: 106 | self.reading_handle._cancel() 107 | 108 | self.reading_handle = callback 109 | 110 | cdef start_writing(self, Handle callback): 111 | cdef: 112 | int mask = 0 113 | 114 | if self.writing_handle is None: 115 | # not writing right now, setup the handle 116 | 117 | mask = uv.UV_WRITABLE 118 | if self.reading_handle is not None: 119 | # are we reading right now? 120 | mask |= uv.UV_READABLE 121 | 122 | self._poll_start(mask) 123 | else: 124 | self.writing_handle._cancel() 125 | 126 | self.writing_handle = callback 127 | 128 | cdef stop_reading(self): 129 | if self.reading_handle is None: 130 | return False 131 | 132 | self.reading_handle._cancel() 133 | self.reading_handle = None 134 | 135 | if self.writing_handle is None: 136 | self.stop() 137 | else: 138 | self._poll_start(uv.UV_WRITABLE) 139 | 140 | return True 141 | 142 | cdef stop_writing(self): 143 | if self.writing_handle is None: 144 | return False 145 | 146 | self.writing_handle._cancel() 147 | self.writing_handle = None 148 | 149 | if self.reading_handle is None: 150 | self.stop() 151 | else: 152 | self._poll_start(uv.UV_READABLE) 153 | 154 | return True 155 | 156 | cdef stop(self): 157 | if self.reading_handle is not None: 158 | self.reading_handle._cancel() 159 | self.reading_handle = None 160 | 161 | if self.writing_handle is not None: 162 | self.writing_handle._cancel() 163 | self.writing_handle = None 164 | 165 | self._poll_stop() 166 | 167 | cdef _close(self): 168 | if self.is_active(): 169 | self.stop() 170 | 171 | UVHandle._close(self) 172 | 173 | cdef _fatal_error(self, exc, throw, reason=None): 174 | try: 175 | if self.reading_handle is not None: 176 | try: 177 | self.reading_handle._run() 178 | except BaseException as ex: 179 | self._loop._handle_exception(ex) 180 | self.reading_handle = None 181 | 182 | if self.writing_handle is not None: 183 | try: 184 | self.writing_handle._run() 185 | except BaseException as ex: 186 | self._loop._handle_exception(ex) 187 | self.writing_handle = None 188 | 189 | finally: 190 | self._close() 191 | 192 | 193 | cdef void __on_uvpoll_event( 194 | uv.uv_poll_t* handle, 195 | int status, 196 | int events, 197 | ) noexcept with gil: 198 | 199 | if __ensure_handle_data(handle, "UVPoll callback") == 0: 200 | return 201 | 202 | cdef: 203 | UVPoll poll = handle.data 204 | 205 | if status < 0: 206 | exc = convert_error(status) 207 | poll._fatal_error(exc, False) 208 | return 209 | 210 | if ((events & (uv.UV_READABLE | uv.UV_DISCONNECT)) and 211 | poll.reading_handle is not None): 212 | 213 | try: 214 | if UVLOOP_DEBUG: 215 | poll._loop._poll_read_events_total += 1 216 | poll.reading_handle._run() 217 | except BaseException as ex: 218 | if UVLOOP_DEBUG: 219 | poll._loop._poll_read_cb_errors_total += 1 220 | poll._error(ex, False) 221 | # continue code execution 222 | 223 | if ((events & (uv.UV_WRITABLE | uv.UV_DISCONNECT)) and 224 | poll.writing_handle is not None): 225 | 226 | try: 227 | if UVLOOP_DEBUG: 228 | poll._loop._poll_write_events_total += 1 229 | poll.writing_handle._run() 230 | except BaseException as ex: 231 | if UVLOOP_DEBUG: 232 | poll._loop._poll_write_cb_errors_total += 1 233 | poll._error(ex, False) 234 | -------------------------------------------------------------------------------- /uvloop/handles/process.pxd: -------------------------------------------------------------------------------- 1 | cdef class UVProcess(UVHandle): 2 | cdef: 3 | object _returncode 4 | object _pid 5 | 6 | object _errpipe_read 7 | object _errpipe_write 8 | object _preexec_fn 9 | bint _restore_signals 10 | 11 | list _fds_to_close 12 | 13 | # Attributes used to compose uv_process_options_t: 14 | uv.uv_process_options_t options 15 | uv.uv_stdio_container_t[3] iocnt 16 | list __env 17 | char **uv_opt_env 18 | list __args 19 | char **uv_opt_args 20 | char *uv_opt_file 21 | bytes __cwd 22 | 23 | cdef _close_process_handle(self) 24 | 25 | cdef _init(self, Loop loop, list args, dict env, cwd, 26 | start_new_session, 27 | _stdin, _stdout, _stderr, pass_fds, 28 | debug_flags, preexec_fn, restore_signals) 29 | 30 | cdef _after_fork(self) 31 | 32 | cdef char** __to_cstring_array(self, list arr) 33 | cdef _init_args(self, list args) 34 | cdef _init_env(self, dict env) 35 | cdef _init_files(self, _stdin, _stdout, _stderr) 36 | cdef _init_options(self, list args, dict env, cwd, start_new_session, 37 | _stdin, _stdout, _stderr, bint force_fork) 38 | 39 | cdef _close_after_spawn(self, int fd) 40 | 41 | cdef _on_exit(self, int64_t exit_status, int term_signal) 42 | cdef _kill(self, int signum) 43 | 44 | 45 | cdef class UVProcessTransport(UVProcess): 46 | cdef: 47 | list _exit_waiters 48 | list _init_futs 49 | bint _stdio_ready 50 | list _pending_calls 51 | object _protocol 52 | bint _finished 53 | 54 | WriteUnixTransport _stdin 55 | ReadUnixTransport _stdout 56 | ReadUnixTransport _stderr 57 | 58 | object stdin_proto 59 | object stdout_proto 60 | object stderr_proto 61 | 62 | cdef _file_redirect_stdio(self, int fd) 63 | cdef _file_devnull(self) 64 | cdef _file_inpipe(self) 65 | cdef _file_outpipe(self) 66 | 67 | cdef _check_proc(self) 68 | cdef _pipe_connection_lost(self, int fd, exc) 69 | cdef _pipe_data_received(self, int fd, data) 70 | 71 | cdef _call_connection_made(self, waiter) 72 | cdef _try_finish(self) 73 | 74 | @staticmethod 75 | cdef UVProcessTransport new(Loop loop, protocol, args, env, cwd, 76 | start_new_session, 77 | _stdin, _stdout, _stderr, pass_fds, 78 | waiter, 79 | debug_flags, 80 | preexec_fn, restore_signals) 81 | -------------------------------------------------------------------------------- /uvloop/handles/stream.pxd: -------------------------------------------------------------------------------- 1 | cdef class UVStream(UVBaseTransport): 2 | cdef: 3 | uv.uv_shutdown_t _shutdown_req 4 | bint __shutting_down 5 | bint __reading 6 | bint __read_error_close 7 | 8 | bint __buffered 9 | object _protocol_get_buffer 10 | object _protocol_buffer_updated 11 | 12 | bint _eof 13 | list _buffer 14 | size_t _buffer_size 15 | 16 | Py_buffer _read_pybuf 17 | bint _read_pybuf_acquired 18 | 19 | # All "inline" methods are final 20 | 21 | cdef inline _init(self, Loop loop, object protocol, Server server, 22 | object waiter, object context) 23 | 24 | 25 | cdef inline _shutdown(self) 26 | cdef inline _accept(self, UVStream server) 27 | 28 | cdef inline _close_on_read_error(self) 29 | 30 | cdef inline __reading_started(self) 31 | cdef inline __reading_stopped(self) 32 | 33 | # The user API write() and writelines() firstly call _buffer_write() to 34 | # buffer up user data chunks, potentially multiple times in writelines(), 35 | # and then call _initiate_write() to start writing either immediately or in 36 | # the next iteration (loop._queue_write()). 37 | cdef inline _buffer_write(self, object data) 38 | cdef inline _initiate_write(self) 39 | 40 | # _exec_write() is the method that does the actual send, and _try_write() 41 | # is a fast-path used in _exec_write() to send a single chunk. 42 | cdef inline _exec_write(self) 43 | cdef inline _try_write(self, object data) 44 | 45 | cdef _close(self) 46 | 47 | cdef inline _on_accept(self) 48 | cdef inline _on_eof(self) 49 | cdef inline _on_write(self) 50 | cdef inline _on_connect(self, object exc) 51 | -------------------------------------------------------------------------------- /uvloop/handles/streamserver.pxd: -------------------------------------------------------------------------------- 1 | cdef class UVStreamServer(UVSocketHandle): 2 | cdef: 3 | int backlog 4 | object ssl 5 | object ssl_handshake_timeout 6 | object ssl_shutdown_timeout 7 | object protocol_factory 8 | bint opened 9 | Server _server 10 | 11 | # All "inline" methods are final 12 | 13 | cdef inline _init(self, Loop loop, object protocol_factory, 14 | Server server, 15 | object backlog, 16 | object ssl, 17 | object ssl_handshake_timeout, 18 | object ssl_shutdown_timeout) 19 | 20 | cdef inline _mark_as_open(self) 21 | 22 | cdef inline listen(self) 23 | cdef inline _on_listen(self) 24 | 25 | cdef UVStream _make_new_transport(self, object protocol, object waiter, 26 | object context) 27 | -------------------------------------------------------------------------------- /uvloop/handles/streamserver.pyx: -------------------------------------------------------------------------------- 1 | @cython.no_gc_clear 2 | cdef class UVStreamServer(UVSocketHandle): 3 | 4 | def __cinit__(self): 5 | self.opened = 0 6 | self._server = None 7 | self.ssl = None 8 | self.ssl_handshake_timeout = None 9 | self.ssl_shutdown_timeout = None 10 | self.protocol_factory = None 11 | 12 | cdef inline _init(self, Loop loop, object protocol_factory, 13 | Server server, 14 | object backlog, 15 | object ssl, 16 | object ssl_handshake_timeout, 17 | object ssl_shutdown_timeout): 18 | 19 | if not isinstance(backlog, int): 20 | # Don't allow floats 21 | raise TypeError('integer argument expected, got {}'.format( 22 | type(backlog).__name__)) 23 | 24 | if ssl is not None: 25 | if not isinstance(ssl, ssl_SSLContext): 26 | raise TypeError( 27 | 'ssl is expected to be None or an instance of ' 28 | 'ssl.SSLContext, got {!r}'.format(ssl)) 29 | else: 30 | if ssl_handshake_timeout is not None: 31 | raise ValueError( 32 | 'ssl_handshake_timeout is only meaningful with ssl') 33 | if ssl_shutdown_timeout is not None: 34 | raise ValueError( 35 | 'ssl_shutdown_timeout is only meaningful with ssl') 36 | 37 | self.backlog = backlog 38 | self.ssl = ssl 39 | self.ssl_handshake_timeout = ssl_handshake_timeout 40 | self.ssl_shutdown_timeout = ssl_shutdown_timeout 41 | 42 | self._start_init(loop) 43 | self.protocol_factory = protocol_factory 44 | self._server = server 45 | 46 | cdef inline listen(self): 47 | cdef int err 48 | self._ensure_alive() 49 | 50 | if self.protocol_factory is None: 51 | raise RuntimeError('unable to listen(); no protocol_factory') 52 | 53 | if self.opened != 1: 54 | raise RuntimeError('unopened TCPServer') 55 | 56 | self.context = Context_CopyCurrent() 57 | 58 | err = uv.uv_listen( self._handle, 59 | self.backlog, 60 | __uv_streamserver_on_listen) 61 | if err < 0: 62 | exc = convert_error(err) 63 | self._fatal_error(exc, True) 64 | return 65 | 66 | cdef inline _on_listen(self): 67 | cdef UVStream client 68 | 69 | protocol = run_in_context(self.context, self.protocol_factory) 70 | 71 | if self.ssl is None: 72 | client = self._make_new_transport(protocol, None, self.context) 73 | 74 | else: 75 | waiter = self._loop._new_future() 76 | 77 | ssl_protocol = SSLProtocol( 78 | self._loop, protocol, self.ssl, 79 | waiter, 80 | server_side=True, 81 | server_hostname=None, 82 | ssl_handshake_timeout=self.ssl_handshake_timeout, 83 | ssl_shutdown_timeout=self.ssl_shutdown_timeout) 84 | 85 | client = self._make_new_transport(ssl_protocol, None, self.context) 86 | 87 | waiter.add_done_callback( 88 | ft_partial(self.__on_ssl_connected, client)) 89 | 90 | client._accept(self) 91 | 92 | cdef _fatal_error(self, exc, throw, reason=None): 93 | # Overload UVHandle._fatal_error 94 | 95 | self._close() 96 | 97 | if not isinstance(exc, OSError): 98 | 99 | if throw or self._loop is None: 100 | raise exc 101 | 102 | msg = f'Fatal error on server {self.__class__.__name__}' 103 | if reason is not None: 104 | msg = f'{msg} ({reason})' 105 | 106 | self._loop.call_exception_handler({ 107 | 'message': msg, 108 | 'exception': exc, 109 | }) 110 | 111 | cdef inline _mark_as_open(self): 112 | self.opened = 1 113 | 114 | cdef UVStream _make_new_transport(self, object protocol, object waiter, 115 | object context): 116 | raise NotImplementedError 117 | 118 | def __on_ssl_connected(self, transport, fut): 119 | exc = fut.exception() 120 | if exc is not None: 121 | transport._force_close(exc) 122 | 123 | 124 | cdef void __uv_streamserver_on_listen( 125 | uv.uv_stream_t* handle, 126 | int status, 127 | ) noexcept with gil: 128 | 129 | # callback for uv_listen 130 | 131 | if __ensure_handle_data(handle, 132 | "UVStream listen callback") == 0: 133 | return 134 | 135 | cdef: 136 | UVStreamServer stream = handle.data 137 | 138 | if status < 0: 139 | if UVLOOP_DEBUG: 140 | stream._loop._debug_stream_listen_errors_total += 1 141 | 142 | exc = convert_error(status) 143 | stream._fatal_error( 144 | exc, False, "error status in uv_stream_t.listen callback") 145 | return 146 | 147 | try: 148 | stream._on_listen() 149 | except BaseException as exc: 150 | stream._error(exc, False) 151 | -------------------------------------------------------------------------------- /uvloop/handles/tcp.pxd: -------------------------------------------------------------------------------- 1 | cdef class TCPServer(UVStreamServer): 2 | cdef bind(self, system.sockaddr* addr, unsigned int flags=*) 3 | 4 | @staticmethod 5 | cdef TCPServer new(Loop loop, object protocol_factory, Server server, 6 | unsigned int flags, 7 | object backlog, 8 | object ssl, 9 | object ssl_handshake_timeout, 10 | object ssl_shutdown_timeout) 11 | 12 | 13 | cdef class TCPTransport(UVStream): 14 | cdef: 15 | bint __peername_set 16 | bint __sockname_set 17 | system.sockaddr_storage __peername 18 | system.sockaddr_storage __sockname 19 | 20 | cdef bind(self, system.sockaddr* addr, unsigned int flags=*) 21 | cdef connect(self, system.sockaddr* addr) 22 | cdef _set_nodelay(self) 23 | 24 | @staticmethod 25 | cdef TCPTransport new(Loop loop, object protocol, Server server, 26 | object waiter, object context) 27 | -------------------------------------------------------------------------------- /uvloop/handles/tcp.pyx: -------------------------------------------------------------------------------- 1 | cdef __tcp_init_uv_handle(UVStream handle, Loop loop, unsigned int flags): 2 | cdef int err 3 | 4 | handle._handle = PyMem_RawMalloc(sizeof(uv.uv_tcp_t)) 5 | if handle._handle is NULL: 6 | handle._abort_init() 7 | raise MemoryError() 8 | 9 | err = uv.uv_tcp_init_ex(handle._loop.uvloop, 10 | handle._handle, 11 | flags) 12 | if err < 0: 13 | handle._abort_init() 14 | raise convert_error(err) 15 | 16 | handle._finish_init() 17 | 18 | 19 | cdef __tcp_bind(UVStream handle, system.sockaddr* addr, unsigned int flags): 20 | cdef int err 21 | err = uv.uv_tcp_bind(handle._handle, 22 | addr, flags) 23 | if err < 0: 24 | exc = convert_error(err) 25 | raise exc 26 | 27 | 28 | cdef __tcp_open(UVStream handle, int sockfd): 29 | cdef int err 30 | err = uv.uv_tcp_open(handle._handle, 31 | sockfd) 32 | if err < 0: 33 | exc = convert_error(err) 34 | raise exc 35 | 36 | 37 | cdef __tcp_get_socket(UVSocketHandle handle): 38 | cdef: 39 | int buf_len = sizeof(system.sockaddr_storage) 40 | int fileno 41 | int err 42 | system.sockaddr_storage buf 43 | 44 | fileno = handle._fileno() 45 | 46 | err = uv.uv_tcp_getsockname(handle._handle, 47 | &buf, 48 | &buf_len) 49 | if err < 0: 50 | raise convert_error(err) 51 | 52 | return PseudoSocket(buf.ss_family, uv.SOCK_STREAM, 0, fileno) 53 | 54 | 55 | @cython.no_gc_clear 56 | cdef class TCPServer(UVStreamServer): 57 | 58 | @staticmethod 59 | cdef TCPServer new(Loop loop, object protocol_factory, Server server, 60 | unsigned int flags, 61 | object backlog, 62 | object ssl, 63 | object ssl_handshake_timeout, 64 | object ssl_shutdown_timeout): 65 | 66 | cdef TCPServer handle 67 | handle = TCPServer.__new__(TCPServer) 68 | handle._init(loop, protocol_factory, server, backlog, 69 | ssl, ssl_handshake_timeout, ssl_shutdown_timeout) 70 | __tcp_init_uv_handle(handle, loop, flags) 71 | return handle 72 | 73 | cdef _new_socket(self): 74 | return __tcp_get_socket(self) 75 | 76 | cdef _open(self, int sockfd): 77 | self._ensure_alive() 78 | try: 79 | __tcp_open(self, sockfd) 80 | except Exception as exc: 81 | self._fatal_error(exc, True) 82 | else: 83 | self._mark_as_open() 84 | 85 | cdef bind(self, system.sockaddr* addr, unsigned int flags=0): 86 | self._ensure_alive() 87 | try: 88 | __tcp_bind(self, addr, flags) 89 | except Exception as exc: 90 | self._fatal_error(exc, True) 91 | else: 92 | self._mark_as_open() 93 | 94 | cdef UVStream _make_new_transport(self, object protocol, object waiter, 95 | object context): 96 | cdef TCPTransport tr 97 | tr = TCPTransport.new(self._loop, protocol, self._server, waiter, 98 | context) 99 | return tr 100 | 101 | 102 | @cython.no_gc_clear 103 | cdef class TCPTransport(UVStream): 104 | 105 | @staticmethod 106 | cdef TCPTransport new(Loop loop, object protocol, Server server, 107 | object waiter, object context): 108 | 109 | cdef TCPTransport handle 110 | handle = TCPTransport.__new__(TCPTransport) 111 | handle._init(loop, protocol, server, waiter, context) 112 | __tcp_init_uv_handle(handle, loop, uv.AF_UNSPEC) 113 | handle.__peername_set = 0 114 | handle.__sockname_set = 0 115 | handle._set_nodelay() 116 | return handle 117 | 118 | cdef _set_nodelay(self): 119 | cdef int err 120 | self._ensure_alive() 121 | err = uv.uv_tcp_nodelay(self._handle, 1) 122 | if err < 0: 123 | raise convert_error(err) 124 | 125 | cdef _call_connection_made(self): 126 | # asyncio saves peername & sockname when transports are instantiated, 127 | # so that they're accessible even after the transport is closed. 128 | # We are doing the same thing here, except that we create Python 129 | # objects lazily, on request in get_extra_info() 130 | 131 | cdef: 132 | int err 133 | int buf_len 134 | 135 | buf_len = sizeof(system.sockaddr_storage) 136 | err = uv.uv_tcp_getsockname(self._handle, 137 | &self.__sockname, 138 | &buf_len) 139 | if err >= 0: 140 | # Ignore errors, this is an optional thing. 141 | # If something serious is going on, the transport 142 | # will crash later (in roughly the same way how 143 | # an asyncio transport would.) 144 | self.__sockname_set = 1 145 | 146 | buf_len = sizeof(system.sockaddr_storage) 147 | err = uv.uv_tcp_getpeername(self._handle, 148 | &self.__peername, 149 | &buf_len) 150 | if err >= 0: 151 | # Same as few lines above -- we don't really care 152 | # about error case here. 153 | self.__peername_set = 1 154 | 155 | UVBaseTransport._call_connection_made(self) 156 | 157 | def get_extra_info(self, name, default=None): 158 | if name == 'sockname': 159 | if self.__sockname_set: 160 | return __convert_sockaddr_to_pyaddr( 161 | &self.__sockname) 162 | elif name == 'peername': 163 | if self.__peername_set: 164 | return __convert_sockaddr_to_pyaddr( 165 | &self.__peername) 166 | return super().get_extra_info(name, default) 167 | 168 | cdef _new_socket(self): 169 | return __tcp_get_socket(self) 170 | 171 | cdef bind(self, system.sockaddr* addr, unsigned int flags=0): 172 | self._ensure_alive() 173 | __tcp_bind(self, addr, flags) 174 | 175 | cdef _open(self, int sockfd): 176 | self._ensure_alive() 177 | __tcp_open(self, sockfd) 178 | 179 | cdef connect(self, system.sockaddr* addr): 180 | cdef _TCPConnectRequest req 181 | req = _TCPConnectRequest(self._loop, self) 182 | req.connect(addr) 183 | 184 | 185 | cdef class _TCPConnectRequest(UVRequest): 186 | cdef: 187 | TCPTransport transport 188 | uv.uv_connect_t _req_data 189 | 190 | def __cinit__(self, loop, transport): 191 | self.request = &self._req_data 192 | self.request.data = self 193 | self.transport = transport 194 | 195 | cdef connect(self, system.sockaddr* addr): 196 | cdef int err 197 | err = uv.uv_tcp_connect(self.request, 198 | self.transport._handle, 199 | addr, 200 | __tcp_connect_callback) 201 | if err < 0: 202 | exc = convert_error(err) 203 | self.on_done() 204 | raise exc 205 | 206 | 207 | cdef void __tcp_connect_callback( 208 | uv.uv_connect_t* req, 209 | int status, 210 | ) noexcept with gil: 211 | cdef: 212 | _TCPConnectRequest wrapper 213 | TCPTransport transport 214 | 215 | wrapper = <_TCPConnectRequest> req.data 216 | transport = wrapper.transport 217 | 218 | if status < 0: 219 | exc = convert_error(status) 220 | else: 221 | exc = None 222 | 223 | try: 224 | transport._on_connect(exc) 225 | except BaseException as ex: 226 | wrapper.transport._fatal_error(ex, False) 227 | finally: 228 | wrapper.on_done() 229 | -------------------------------------------------------------------------------- /uvloop/handles/timer.pxd: -------------------------------------------------------------------------------- 1 | cdef class UVTimer(UVHandle): 2 | cdef: 3 | method_t callback 4 | object ctx 5 | bint running 6 | uint64_t timeout 7 | uint64_t start_t 8 | 9 | cdef _init(self, Loop loop, method_t callback, object ctx, 10 | uint64_t timeout) 11 | 12 | cdef stop(self) 13 | cdef start(self) 14 | cdef get_when(self) 15 | 16 | @staticmethod 17 | cdef UVTimer new(Loop loop, method_t callback, object ctx, 18 | uint64_t timeout) 19 | -------------------------------------------------------------------------------- /uvloop/handles/timer.pyx: -------------------------------------------------------------------------------- 1 | @cython.no_gc_clear 2 | cdef class UVTimer(UVHandle): 3 | cdef _init(self, Loop loop, method_t callback, object ctx, 4 | uint64_t timeout): 5 | 6 | cdef int err 7 | 8 | self._start_init(loop) 9 | 10 | self._handle = PyMem_RawMalloc(sizeof(uv.uv_timer_t)) 11 | if self._handle is NULL: 12 | self._abort_init() 13 | raise MemoryError() 14 | 15 | err = uv.uv_timer_init(self._loop.uvloop, self._handle) 16 | if err < 0: 17 | self._abort_init() 18 | raise convert_error(err) 19 | 20 | self._finish_init() 21 | 22 | self.callback = callback 23 | self.ctx = ctx 24 | self.running = 0 25 | self.timeout = timeout 26 | self.start_t = 0 27 | 28 | cdef stop(self): 29 | cdef int err 30 | 31 | if not self._is_alive(): 32 | self.running = 0 33 | return 34 | 35 | if self.running == 1: 36 | err = uv.uv_timer_stop(self._handle) 37 | self.running = 0 38 | if err < 0: 39 | exc = convert_error(err) 40 | self._fatal_error(exc, True) 41 | return 42 | 43 | cdef start(self): 44 | cdef int err 45 | 46 | self._ensure_alive() 47 | 48 | if self.running == 0: 49 | # Update libuv internal time. 50 | uv.uv_update_time(self._loop.uvloop) # void 51 | self.start_t = uv.uv_now(self._loop.uvloop) 52 | 53 | err = uv.uv_timer_start(self._handle, 54 | __uvtimer_callback, 55 | self.timeout, 0) 56 | if err < 0: 57 | exc = convert_error(err) 58 | self._fatal_error(exc, True) 59 | return 60 | self.running = 1 61 | 62 | cdef get_when(self): 63 | return self.start_t + self.timeout 64 | 65 | @staticmethod 66 | cdef UVTimer new(Loop loop, method_t callback, object ctx, 67 | uint64_t timeout): 68 | 69 | cdef UVTimer handle 70 | handle = UVTimer.__new__(UVTimer) 71 | handle._init(loop, callback, ctx, timeout) 72 | return handle 73 | 74 | 75 | cdef void __uvtimer_callback( 76 | uv.uv_timer_t* handle, 77 | ) noexcept with gil: 78 | if __ensure_handle_data(handle, "UVTimer callback") == 0: 79 | return 80 | 81 | cdef: 82 | UVTimer timer = handle.data 83 | method_t cb = timer.callback 84 | 85 | timer.running = 0 86 | try: 87 | cb(timer.ctx) 88 | except BaseException as ex: 89 | timer._error(ex, False) 90 | -------------------------------------------------------------------------------- /uvloop/handles/udp.pxd: -------------------------------------------------------------------------------- 1 | cdef class UDPTransport(UVBaseTransport): 2 | cdef: 3 | bint __receiving 4 | int _family 5 | object _address 6 | 7 | cdef _init(self, Loop loop, unsigned int family) 8 | cdef _set_address(self, system.addrinfo *addr) 9 | 10 | cdef _connect(self, system.sockaddr* addr, size_t addr_len) 11 | 12 | cdef _bind(self, system.sockaddr* addr) 13 | cdef open(self, int family, int sockfd) 14 | cdef _set_broadcast(self, bint on) 15 | 16 | cdef inline __receiving_started(self) 17 | cdef inline __receiving_stopped(self) 18 | 19 | cdef _send(self, object data, object addr) 20 | 21 | cdef _on_receive(self, bytes data, object exc, object addr) 22 | cdef _on_sent(self, object exc, object context=*) 23 | -------------------------------------------------------------------------------- /uvloop/includes/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | 3 | # These have to be synced with the stdlib.pxi 4 | import asyncio 5 | import collections 6 | import concurrent.futures 7 | import errno 8 | import functools 9 | import gc 10 | import inspect 11 | import itertools 12 | import os 13 | import signal 14 | import socket 15 | import subprocess 16 | import ssl 17 | import stat 18 | import sys 19 | import threading 20 | import traceback 21 | import time 22 | import warnings 23 | import weakref 24 | -------------------------------------------------------------------------------- /uvloop/includes/compat.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "Python.h" 7 | #include "uv.h" 8 | 9 | 10 | #ifndef EWOULDBLOCK 11 | #define EWOULDBLOCK EAGAIN 12 | #endif 13 | 14 | #ifdef __APPLE__ 15 | #define PLATFORM_IS_APPLE 1 16 | #else 17 | #define PLATFORM_IS_APPLE 0 18 | #endif 19 | 20 | 21 | #ifdef __linux__ 22 | # define PLATFORM_IS_LINUX 1 23 | # include 24 | #else 25 | # define PLATFORM_IS_LINUX 0 26 | # define EPOLL_CTL_DEL 2 27 | struct epoll_event {}; 28 | int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) { 29 | return 0; 30 | }; 31 | #endif 32 | 33 | 34 | PyObject * 35 | MakeUnixSockPyAddr(struct sockaddr_un *addr) 36 | { 37 | if (addr->sun_family != AF_UNIX) { 38 | PyErr_SetString( 39 | PyExc_ValueError, "a UNIX socket addr was expected"); 40 | return NULL; 41 | } 42 | 43 | #ifdef __linux__ 44 | int addrlen = sizeof (struct sockaddr_un); 45 | size_t linuxaddrlen = addrlen - offsetof(struct sockaddr_un, sun_path); 46 | if (linuxaddrlen > 0 && addr->sun_path[0] == 0) { 47 | return PyBytes_FromStringAndSize(addr->sun_path, linuxaddrlen); 48 | } 49 | else 50 | #endif /* linux */ 51 | { 52 | /* regular NULL-terminated string */ 53 | return PyUnicode_DecodeFSDefault(addr->sun_path); 54 | } 55 | } 56 | 57 | 58 | #if PY_VERSION_HEX < 0x03070100 59 | 60 | PyObject * Context_CopyCurrent(void) { 61 | return (PyObject *)PyContext_CopyCurrent(); 62 | }; 63 | 64 | int Context_Enter(PyObject *ctx) { 65 | return PyContext_Enter((PyContext *)ctx); 66 | } 67 | 68 | int Context_Exit(PyObject *ctx) { 69 | return PyContext_Exit((PyContext *)ctx); 70 | } 71 | 72 | #else 73 | 74 | PyObject * Context_CopyCurrent(void) { 75 | return PyContext_CopyCurrent(); 76 | }; 77 | 78 | int Context_Enter(PyObject *ctx) { 79 | return PyContext_Enter(ctx); 80 | } 81 | 82 | int Context_Exit(PyObject *ctx) { 83 | return PyContext_Exit(ctx); 84 | } 85 | 86 | #endif 87 | 88 | /* inlined from cpython/Modules/signalmodule.c 89 | * https://github.com/python/cpython/blob/v3.13.0a6/Modules/signalmodule.c#L1931-L1951 90 | * private _Py_RestoreSignals has been moved to CPython internals in Python 3.13 91 | * https://github.com/python/cpython/pull/106400 */ 92 | 93 | void 94 | _Py_RestoreSignals(void) 95 | { 96 | #ifdef SIGPIPE 97 | PyOS_setsig(SIGPIPE, SIG_DFL); 98 | #endif 99 | #ifdef SIGXFZ 100 | PyOS_setsig(SIGXFZ, SIG_DFL); 101 | #endif 102 | #ifdef SIGXFSZ 103 | PyOS_setsig(SIGXFSZ, SIG_DFL); 104 | #endif 105 | } 106 | -------------------------------------------------------------------------------- /uvloop/includes/consts.pxi: -------------------------------------------------------------------------------- 1 | cdef enum: 2 | UV_STREAM_RECV_BUF_SIZE = 256000 # 250kb 3 | 4 | FLOW_CONTROL_HIGH_WATER = 64 # KiB 5 | FLOW_CONTROL_HIGH_WATER_SSL_READ = 256 # KiB 6 | FLOW_CONTROL_HIGH_WATER_SSL_WRITE = 512 # KiB 7 | 8 | DEFAULT_FREELIST_SIZE = 250 9 | DNS_PYADDR_TO_SOCKADDR_CACHE_SIZE = 2048 10 | 11 | DEBUG_STACK_DEPTH = 10 12 | 13 | 14 | __PROCESS_DEBUG_SLEEP_AFTER_FORK = 1 15 | 16 | 17 | LOG_THRESHOLD_FOR_CONNLOST_WRITES = 5 18 | SSL_READ_MAX_SIZE = 256 * 1024 19 | 20 | 21 | cdef extern from *: 22 | ''' 23 | // Number of seconds to wait for SSL handshake to complete 24 | // The default timeout matches that of Nginx. 25 | #define SSL_HANDSHAKE_TIMEOUT 60.0 26 | 27 | // Number of seconds to wait for SSL shutdown to complete 28 | // The default timeout mimics lingering_time 29 | #define SSL_SHUTDOWN_TIMEOUT 30.0 30 | ''' 31 | 32 | const float SSL_HANDSHAKE_TIMEOUT 33 | const float SSL_SHUTDOWN_TIMEOUT 34 | -------------------------------------------------------------------------------- /uvloop/includes/debug.h: -------------------------------------------------------------------------------- 1 | #ifndef UVLOOP_DEBUG 2 | #define UVLOOP_DEBUG 0 3 | #endif 4 | -------------------------------------------------------------------------------- /uvloop/includes/debug.pxd: -------------------------------------------------------------------------------- 1 | cdef extern from "includes/debug.h": 2 | 3 | cdef int UVLOOP_DEBUG 4 | -------------------------------------------------------------------------------- /uvloop/includes/flowcontrol.pxd: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | 3 | 4 | cdef inline add_flowcontrol_defaults(high, low, int kb): 5 | cdef int h, l 6 | if high is None: 7 | if low is None: 8 | h = kb * 1024 9 | else: 10 | l = low 11 | h = 4 * l 12 | else: 13 | h = high 14 | if low is None: 15 | l = h // 4 16 | else: 17 | l = low 18 | 19 | if not h >= l >= 0: 20 | raise ValueError('high (%r) must be >= low (%r) must be >= 0' % 21 | (h, l)) 22 | 23 | return h, l 24 | -------------------------------------------------------------------------------- /uvloop/includes/fork_handler.h: -------------------------------------------------------------------------------- 1 | #ifndef UVLOOP_FORK_HANDLER_H_ 2 | #define UVLOOP_FORK_HANDLER_H_ 3 | 4 | volatile uint64_t MAIN_THREAD_ID = 0; 5 | volatile int8_t MAIN_THREAD_ID_SET = 0; 6 | 7 | typedef void (*OnForkHandler)(void); 8 | 9 | OnForkHandler __forkHandler = NULL; 10 | 11 | /* Auxiliary function to call global fork handler if defined. 12 | 13 | Note: Fork handler needs to be in C (not cython) otherwise it would require 14 | GIL to be present, but some forks can exec non-python processes. 15 | */ 16 | void handleAtFork(void) { 17 | // Reset the MAIN_THREAD_ID on fork, because the main thread ID is not 18 | // always the same after fork, especially when forked from within a thread. 19 | MAIN_THREAD_ID_SET = 0; 20 | 21 | if (__forkHandler != NULL) { 22 | __forkHandler(); 23 | } 24 | } 25 | 26 | 27 | void setForkHandler(OnForkHandler handler) 28 | { 29 | __forkHandler = handler; 30 | } 31 | 32 | 33 | void resetForkHandler(void) 34 | { 35 | __forkHandler = NULL; 36 | } 37 | 38 | void setMainThreadID(uint64_t id) { 39 | MAIN_THREAD_ID = id; 40 | MAIN_THREAD_ID_SET = 1; 41 | } 42 | #endif 43 | -------------------------------------------------------------------------------- /uvloop/includes/python.pxd: -------------------------------------------------------------------------------- 1 | cdef extern from "Python.h": 2 | int PY_VERSION_HEX 3 | 4 | unicode PyUnicode_FromString(const char *) 5 | 6 | void* PyMem_RawMalloc(size_t n) nogil 7 | void* PyMem_RawRealloc(void *p, size_t n) nogil 8 | void* PyMem_RawCalloc(size_t nelem, size_t elsize) nogil 9 | void PyMem_RawFree(void *p) nogil 10 | 11 | object PyUnicode_EncodeFSDefault(object) 12 | void PyErr_SetInterrupt() nogil 13 | 14 | object PyMemoryView_FromMemory(char *mem, ssize_t size, int flags) 15 | object PyMemoryView_FromObject(object obj) 16 | int PyMemoryView_Check(object obj) 17 | 18 | cdef enum: 19 | PyBUF_WRITE 20 | 21 | 22 | cdef extern from "includes/compat.h": 23 | object Context_CopyCurrent() 24 | int Context_Enter(object) except -1 25 | int Context_Exit(object) except -1 26 | 27 | void PyOS_BeforeFork() 28 | void PyOS_AfterFork_Parent() 29 | void PyOS_AfterFork_Child() 30 | 31 | void _Py_RestoreSignals() 32 | -------------------------------------------------------------------------------- /uvloop/includes/stdlib.pxi: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | 3 | 4 | import asyncio, asyncio.log, asyncio.base_events, \ 5 | asyncio.sslproto, asyncio.coroutines, \ 6 | asyncio.futures, asyncio.transports 7 | import collections.abc 8 | import concurrent.futures 9 | import errno 10 | import functools 11 | import gc 12 | import inspect 13 | import itertools 14 | import os 15 | import signal 16 | import socket 17 | import subprocess 18 | import ssl 19 | import stat 20 | import sys 21 | import threading 22 | import traceback 23 | import time 24 | import warnings 25 | import weakref 26 | 27 | 28 | cdef aio_get_event_loop = asyncio.get_event_loop 29 | cdef aio_CancelledError = asyncio.CancelledError 30 | cdef aio_InvalidStateError = asyncio.InvalidStateError 31 | cdef aio_TimeoutError = asyncio.TimeoutError 32 | cdef aio_Future = asyncio.Future 33 | cdef aio_Task = asyncio.Task 34 | cdef aio_ensure_future = asyncio.ensure_future 35 | cdef aio_gather = asyncio.gather 36 | cdef aio_wait = asyncio.wait 37 | cdef aio_wrap_future = asyncio.wrap_future 38 | cdef aio_logger = asyncio.log.logger 39 | cdef aio_iscoroutine = asyncio.iscoroutine 40 | cdef aio_iscoroutinefunction = asyncio.iscoroutinefunction 41 | cdef aio_BaseProtocol = asyncio.BaseProtocol 42 | cdef aio_Protocol = asyncio.Protocol 43 | cdef aio_isfuture = getattr(asyncio, 'isfuture', None) 44 | cdef aio_get_running_loop = getattr(asyncio, '_get_running_loop', None) 45 | cdef aio_set_running_loop = getattr(asyncio, '_set_running_loop', None) 46 | cdef aio_debug_wrapper = getattr(asyncio.coroutines, 'debug_wrapper', None) 47 | cdef aio_AbstractChildWatcher = asyncio.AbstractChildWatcher 48 | cdef aio_Transport = asyncio.Transport 49 | cdef aio_FlowControlMixin = asyncio.transports._FlowControlMixin 50 | 51 | cdef col_deque = collections.deque 52 | cdef col_Iterable = collections.abc.Iterable 53 | cdef col_Counter = collections.Counter 54 | cdef col_OrderedDict = collections.OrderedDict 55 | 56 | cdef cc_ThreadPoolExecutor = concurrent.futures.ThreadPoolExecutor 57 | cdef cc_Future = concurrent.futures.Future 58 | 59 | cdef errno_EBADF = errno.EBADF 60 | cdef errno_EINVAL = errno.EINVAL 61 | 62 | cdef ft_partial = functools.partial 63 | 64 | cdef gc_disable = gc.disable 65 | 66 | cdef iter_chain = itertools.chain 67 | cdef inspect_isgenerator = inspect.isgenerator 68 | 69 | cdef int has_IPV6_V6ONLY = hasattr(socket, 'IPV6_V6ONLY') 70 | cdef int IPV6_V6ONLY = getattr(socket, 'IPV6_V6ONLY', -1) 71 | cdef int has_SO_REUSEPORT = hasattr(socket, 'SO_REUSEPORT') 72 | cdef int SO_REUSEPORT = getattr(socket, 'SO_REUSEPORT', 0) 73 | cdef int SO_BROADCAST = getattr(socket, 'SO_BROADCAST') 74 | cdef int SOCK_NONBLOCK = getattr(socket, 'SOCK_NONBLOCK', -1) 75 | cdef int socket_AI_CANONNAME = getattr(socket, 'AI_CANONNAME') 76 | 77 | cdef socket_gaierror = socket.gaierror 78 | cdef socket_error = socket.error 79 | cdef socket_timeout = socket.timeout 80 | cdef socket_socket = socket.socket 81 | cdef socket_socketpair = socket.socketpair 82 | cdef socket_getservbyname = socket.getservbyname 83 | cdef socket_AddressFamily = socket.AddressFamily 84 | cdef socket_SocketKind = socket.SocketKind 85 | 86 | cdef int socket_EAI_ADDRFAMILY = getattr(socket, 'EAI_ADDRFAMILY', -1) 87 | cdef int socket_EAI_AGAIN = getattr(socket, 'EAI_AGAIN', -1) 88 | cdef int socket_EAI_BADFLAGS = getattr(socket, 'EAI_BADFLAGS', -1) 89 | cdef int socket_EAI_BADHINTS = getattr(socket, 'EAI_BADHINTS', -1) 90 | cdef int socket_EAI_CANCELED = getattr(socket, 'EAI_CANCELED', -1) 91 | cdef int socket_EAI_FAIL = getattr(socket, 'EAI_FAIL', -1) 92 | cdef int socket_EAI_FAMILY = getattr(socket, 'EAI_FAMILY', -1) 93 | cdef int socket_EAI_MEMORY = getattr(socket, 'EAI_MEMORY', -1) 94 | cdef int socket_EAI_NODATA = getattr(socket, 'EAI_NODATA', -1) 95 | cdef int socket_EAI_NONAME = getattr(socket, 'EAI_NONAME', -1) 96 | cdef int socket_EAI_OVERFLOW = getattr(socket, 'EAI_OVERFLOW', -1) 97 | cdef int socket_EAI_PROTOCOL = getattr(socket, 'EAI_PROTOCOL', -1) 98 | cdef int socket_EAI_SERVICE = getattr(socket, 'EAI_SERVICE', -1) 99 | cdef int socket_EAI_SOCKTYPE = getattr(socket, 'EAI_SOCKTYPE', -1) 100 | 101 | 102 | cdef str os_name = os.name 103 | cdef os_environ = os.environ 104 | cdef os_dup = os.dup 105 | cdef os_set_inheritable = os.set_inheritable 106 | cdef os_get_inheritable = os.get_inheritable 107 | cdef os_close = os.close 108 | cdef os_open = os.open 109 | cdef os_devnull = os.devnull 110 | cdef os_O_RDWR = os.O_RDWR 111 | cdef os_pipe = os.pipe 112 | cdef os_read = os.read 113 | cdef os_remove = os.remove 114 | cdef os_stat = os.stat 115 | cdef os_unlink = os.unlink 116 | cdef os_fspath = os.fspath 117 | 118 | cdef stat_S_ISSOCK = stat.S_ISSOCK 119 | 120 | cdef sys_ignore_environment = sys.flags.ignore_environment 121 | cdef sys_dev_mode = sys.flags.dev_mode 122 | cdef sys_exc_info = sys.exc_info 123 | cdef sys_set_coroutine_wrapper = getattr(sys, 'set_coroutine_wrapper', None) 124 | cdef sys_get_coroutine_wrapper = getattr(sys, 'get_coroutine_wrapper', None) 125 | cdef sys_getframe = sys._getframe 126 | cdef sys_version_info = sys.version_info 127 | cdef sys_getfilesystemencoding = sys.getfilesystemencoding 128 | cdef str sys_platform = sys.platform 129 | 130 | cdef ssl_SSLContext = ssl.SSLContext 131 | cdef ssl_MemoryBIO = ssl.MemoryBIO 132 | cdef ssl_create_default_context = ssl.create_default_context 133 | cdef ssl_SSLError = ssl.SSLError 134 | cdef ssl_SSLAgainErrors = (ssl.SSLWantReadError, ssl.SSLSyscallError) 135 | cdef ssl_SSLZeroReturnError = ssl.SSLZeroReturnError 136 | cdef ssl_CertificateError = ssl.CertificateError 137 | cdef int ssl_SSL_ERROR_WANT_READ = ssl.SSL_ERROR_WANT_READ 138 | cdef int ssl_SSL_ERROR_WANT_WRITE = ssl.SSL_ERROR_WANT_WRITE 139 | cdef int ssl_SSL_ERROR_SYSCALL = ssl.SSL_ERROR_SYSCALL 140 | 141 | cdef threading_Thread = threading.Thread 142 | cdef threading_main_thread = threading.main_thread 143 | 144 | cdef int subprocess_PIPE = subprocess.PIPE 145 | cdef int subprocess_STDOUT = subprocess.STDOUT 146 | cdef int subprocess_DEVNULL = subprocess.DEVNULL 147 | cdef subprocess_SubprocessError = subprocess.SubprocessError 148 | 149 | cdef int signal_NSIG = signal.NSIG 150 | cdef signal_signal = signal.signal 151 | cdef signal_siginterrupt = signal.siginterrupt 152 | cdef signal_set_wakeup_fd = signal.set_wakeup_fd 153 | cdef signal_default_int_handler = signal.default_int_handler 154 | cdef signal_SIG_DFL = signal.SIG_DFL 155 | 156 | cdef time_sleep = time.sleep 157 | cdef time_monotonic = time.monotonic 158 | 159 | cdef tb_StackSummary = traceback.StackSummary 160 | cdef tb_walk_stack = traceback.walk_stack 161 | cdef tb_format_list = traceback.format_list 162 | 163 | cdef warnings_warn = warnings.warn 164 | 165 | cdef weakref_WeakValueDictionary = weakref.WeakValueDictionary 166 | cdef weakref_WeakSet = weakref.WeakSet 167 | 168 | cdef py_inf = float('inf') 169 | 170 | 171 | # Cython doesn't clean-up imported objects properly in Py3 mode, 172 | # so we delete refs to all modules manually (except sys) 173 | del asyncio, concurrent, collections, errno 174 | del functools, inspect, itertools, socket, os, threading 175 | del signal, subprocess, ssl 176 | del time, traceback, warnings, weakref 177 | -------------------------------------------------------------------------------- /uvloop/includes/system.pxd: -------------------------------------------------------------------------------- 1 | from libc.stdint cimport int8_t, uint64_t 2 | 3 | cdef extern from "arpa/inet.h" nogil: 4 | 5 | int ntohl(int) 6 | int htonl(int) 7 | int ntohs(int) 8 | 9 | 10 | cdef extern from "sys/socket.h" nogil: 11 | 12 | struct sockaddr: 13 | unsigned short sa_family 14 | char sa_data[14] 15 | 16 | struct addrinfo: 17 | int ai_flags 18 | int ai_family 19 | int ai_socktype 20 | int ai_protocol 21 | size_t ai_addrlen 22 | sockaddr* ai_addr 23 | char* ai_canonname 24 | addrinfo* ai_next 25 | 26 | struct sockaddr_in: 27 | unsigned short sin_family 28 | unsigned short sin_port 29 | # ... 30 | 31 | struct sockaddr_in6: 32 | unsigned short sin6_family 33 | unsigned short sin6_port 34 | unsigned long sin6_flowinfo 35 | # ... 36 | unsigned long sin6_scope_id 37 | 38 | struct sockaddr_storage: 39 | unsigned short ss_family 40 | # ... 41 | 42 | const char *gai_strerror(int errcode) 43 | 44 | int socketpair(int domain, int type, int protocol, int socket_vector[2]) 45 | 46 | int setsockopt(int socket, int level, int option_name, 47 | const void *option_value, int option_len) 48 | 49 | 50 | cdef extern from "sys/un.h" nogil: 51 | 52 | struct sockaddr_un: 53 | unsigned short sun_family 54 | char* sun_path 55 | # ... 56 | 57 | 58 | cdef extern from "unistd.h" nogil: 59 | 60 | ssize_t write(int fd, const void *buf, size_t count) 61 | void _exit(int status) 62 | 63 | 64 | cdef extern from "pthread.h": 65 | 66 | int pthread_atfork( 67 | void (*prepare)(), 68 | void (*parent)(), 69 | void (*child)()) 70 | 71 | 72 | cdef extern from "includes/compat.h" nogil: 73 | 74 | cdef int EWOULDBLOCK 75 | 76 | cdef int PLATFORM_IS_APPLE 77 | cdef int PLATFORM_IS_LINUX 78 | 79 | struct epoll_event: 80 | # We don't use the fields 81 | pass 82 | 83 | int EPOLL_CTL_DEL 84 | int epoll_ctl(int epfd, int op, int fd, epoll_event *event) 85 | object MakeUnixSockPyAddr(sockaddr_un *addr) 86 | 87 | 88 | cdef extern from "includes/fork_handler.h": 89 | 90 | uint64_t MAIN_THREAD_ID 91 | int8_t MAIN_THREAD_ID_SET 92 | ctypedef void (*OnForkHandler)() 93 | void handleAtFork() 94 | void setForkHandler(OnForkHandler handler) 95 | void resetForkHandler() 96 | void setMainThreadID(uint64_t id) 97 | -------------------------------------------------------------------------------- /uvloop/loop.pxd: -------------------------------------------------------------------------------- 1 | # cython: language_level=3 2 | 3 | 4 | from .includes cimport uv 5 | from .includes cimport system 6 | 7 | from libc.stdint cimport uint64_t, uint32_t, int64_t 8 | 9 | 10 | include "includes/consts.pxi" 11 | 12 | 13 | cdef extern from *: 14 | ctypedef int vint "volatile int" 15 | 16 | 17 | cdef class UVHandle 18 | cdef class UVSocketHandle(UVHandle) 19 | 20 | cdef class UVAsync(UVHandle) 21 | cdef class UVTimer(UVHandle) 22 | cdef class UVIdle(UVHandle) 23 | 24 | cdef class UVBaseTransport(UVSocketHandle) 25 | 26 | ctypedef object (*method_t)(object) 27 | ctypedef object (*method1_t)(object, object) 28 | ctypedef object (*method2_t)(object, object, object) 29 | ctypedef object (*method3_t)(object, object, object, object) 30 | 31 | 32 | cdef class Loop: 33 | cdef: 34 | uv.uv_loop_t *uvloop 35 | 36 | bint _coroutine_debug_set 37 | int _coroutine_origin_tracking_saved_depth 38 | 39 | public slow_callback_duration 40 | 41 | readonly bint _closed 42 | bint _debug 43 | bint _running 44 | bint _stopping 45 | 46 | uint64_t _thread_id 47 | 48 | object _task_factory 49 | object _exception_handler 50 | object _default_executor 51 | object _ready 52 | set _queued_streams, _executing_streams 53 | Py_ssize_t _ready_len 54 | 55 | set _servers 56 | 57 | object _transports 58 | set _processes 59 | dict _fd_to_reader_fileobj 60 | dict _fd_to_writer_fileobj 61 | dict _unix_server_sockets 62 | 63 | set _signals 64 | dict _signal_handlers 65 | object _ssock 66 | object _csock 67 | bint _listening_signals 68 | int _old_signal_wakeup_id 69 | 70 | set _timers 71 | dict _polls 72 | 73 | UVProcess active_process_handler 74 | 75 | UVAsync handler_async 76 | UVIdle handler_idle 77 | UVCheck handler_check__exec_writes 78 | 79 | object _last_error 80 | 81 | cdef object __weakref__ 82 | 83 | object _asyncgens 84 | bint _asyncgens_shutdown_called 85 | 86 | bint _executor_shutdown_called 87 | 88 | char _recv_buffer[UV_STREAM_RECV_BUF_SIZE] 89 | bint _recv_buffer_in_use 90 | 91 | # DEBUG fields 92 | # True when compiled with DEBUG. 93 | # Used only in unittests. 94 | readonly bint _debug_cc 95 | 96 | readonly object _debug_handles_total 97 | readonly object _debug_handles_closed 98 | readonly object _debug_handles_current 99 | 100 | readonly uint64_t _debug_uv_handles_total 101 | readonly uint64_t _debug_uv_handles_freed 102 | 103 | readonly uint64_t _debug_cb_handles_total 104 | readonly uint64_t _debug_cb_handles_count 105 | readonly uint64_t _debug_cb_timer_handles_total 106 | readonly uint64_t _debug_cb_timer_handles_count 107 | 108 | readonly uint64_t _debug_stream_shutdown_errors_total 109 | readonly uint64_t _debug_stream_listen_errors_total 110 | 111 | readonly uint64_t _debug_stream_read_cb_total 112 | readonly uint64_t _debug_stream_read_cb_errors_total 113 | readonly uint64_t _debug_stream_read_eof_total 114 | readonly uint64_t _debug_stream_read_eof_cb_errors_total 115 | readonly uint64_t _debug_stream_read_errors_total 116 | 117 | readonly uint64_t _debug_stream_write_tries 118 | readonly uint64_t _debug_stream_write_errors_total 119 | readonly uint64_t _debug_stream_write_ctx_total 120 | readonly uint64_t _debug_stream_write_ctx_cnt 121 | readonly uint64_t _debug_stream_write_cb_errors_total 122 | 123 | readonly uint64_t _poll_read_events_total 124 | readonly uint64_t _poll_read_cb_errors_total 125 | readonly uint64_t _poll_write_events_total 126 | readonly uint64_t _poll_write_cb_errors_total 127 | 128 | readonly uint64_t _sock_try_write_total 129 | 130 | readonly uint64_t _debug_exception_handler_cnt 131 | 132 | cdef _init_debug_fields(self) 133 | 134 | cdef _on_wake(self) 135 | cdef _on_idle(self) 136 | 137 | cdef __run(self, uv.uv_run_mode) 138 | cdef _run(self, uv.uv_run_mode) 139 | 140 | cdef _close(self) 141 | cdef _stop(self, exc) 142 | cdef uint64_t _time(self) 143 | 144 | cdef inline _queue_write(self, UVStream stream) 145 | cdef _exec_queued_writes(self) 146 | 147 | cdef inline _call_soon(self, object callback, object args, object context) 148 | cdef inline _append_ready_handle(self, Handle handle) 149 | cdef inline _call_soon_handle(self, Handle handle) 150 | 151 | cdef _call_later(self, uint64_t delay, object callback, object args, 152 | object context) 153 | 154 | cdef void _handle_exception(self, object ex) 155 | 156 | cdef inline _is_main_thread(self) 157 | 158 | cdef inline _new_future(self) 159 | cdef inline _check_signal(self, sig) 160 | cdef inline _check_closed(self) 161 | cdef inline _check_thread(self) 162 | 163 | cdef _getaddrinfo(self, object host, object port, 164 | int family, int type, 165 | int proto, int flags, 166 | int unpack) 167 | 168 | cdef _getnameinfo(self, system.sockaddr *addr, int flags) 169 | 170 | cdef _track_transport(self, UVBaseTransport transport) 171 | cdef _fileobj_to_fd(self, fileobj) 172 | cdef _ensure_fd_no_transport(self, fd) 173 | 174 | cdef _track_process(self, UVProcess proc) 175 | cdef _untrack_process(self, UVProcess proc) 176 | 177 | cdef _add_reader(self, fd, Handle handle) 178 | cdef _has_reader(self, fd) 179 | cdef _remove_reader(self, fd) 180 | 181 | cdef _add_writer(self, fd, Handle handle) 182 | cdef _has_writer(self, fd) 183 | cdef _remove_writer(self, fd) 184 | 185 | cdef _sock_recv(self, fut, sock, n) 186 | cdef _sock_recv_into(self, fut, sock, buf) 187 | cdef _sock_sendall(self, fut, sock, data) 188 | cdef _sock_accept(self, fut, sock) 189 | 190 | cdef _sock_connect(self, sock, address) 191 | cdef _sock_connect_cb(self, fut, sock, address) 192 | 193 | cdef _sock_set_reuseport(self, int fd) 194 | 195 | cdef _setup_or_resume_signals(self) 196 | cdef _shutdown_signals(self) 197 | cdef _pause_signals(self) 198 | 199 | cdef _handle_signal(self, sig) 200 | cdef _read_from_self(self) 201 | cdef inline _ceval_process_signals(self) 202 | cdef _invoke_signals(self, bytes data) 203 | 204 | cdef _set_coroutine_debug(self, bint enabled) 205 | 206 | cdef _print_debug_info(self) 207 | 208 | 209 | include "cbhandles.pxd" 210 | 211 | include "handles/handle.pxd" 212 | include "handles/async_.pxd" 213 | include "handles/idle.pxd" 214 | include "handles/check.pxd" 215 | include "handles/timer.pxd" 216 | include "handles/poll.pxd" 217 | include "handles/basetransport.pxd" 218 | include "handles/stream.pxd" 219 | include "handles/streamserver.pxd" 220 | include "handles/tcp.pxd" 221 | include "handles/pipe.pxd" 222 | include "handles/process.pxd" 223 | include "handles/fsevent.pxd" 224 | 225 | include "request.pxd" 226 | include "sslproto.pxd" 227 | 228 | include "handles/udp.pxd" 229 | 230 | include "server.pxd" 231 | -------------------------------------------------------------------------------- /uvloop/lru.pyx: -------------------------------------------------------------------------------- 1 | cdef object _LRU_MARKER = object() 2 | 3 | 4 | @cython.final 5 | cdef class LruCache: 6 | 7 | cdef: 8 | object _dict 9 | int _maxsize 10 | object _dict_move_to_end 11 | object _dict_get 12 | 13 | # We use an OrderedDict for LRU implementation. Operations: 14 | # 15 | # * We use a simple `__setitem__` to push a new entry: 16 | # `entries[key] = new_entry` 17 | # That will push `new_entry` to the *end* of the entries dict. 18 | # 19 | # * When we have a cache hit, we call 20 | # `entries.move_to_end(key, last=True)` 21 | # to move the entry to the *end* of the entries dict. 22 | # 23 | # * When we need to remove entries to maintain `max_size`, we call 24 | # `entries.popitem(last=False)` 25 | # to remove an entry from the *beginning* of the entries dict. 26 | # 27 | # So new entries and hits are always promoted to the end of the 28 | # entries dict, whereas the unused one will group in the 29 | # beginning of it. 30 | 31 | def __init__(self, *, maxsize): 32 | if maxsize <= 0: 33 | raise ValueError( 34 | f'maxsize is expected to be greater than 0, got {maxsize}') 35 | 36 | self._dict = col_OrderedDict() 37 | self._dict_move_to_end = self._dict.move_to_end 38 | self._dict_get = self._dict.get 39 | self._maxsize = maxsize 40 | 41 | cdef get(self, key, default): 42 | o = self._dict_get(key, _LRU_MARKER) 43 | if o is _LRU_MARKER: 44 | return default 45 | self._dict_move_to_end(key) # last=True 46 | return o 47 | 48 | cdef inline needs_cleanup(self): 49 | return len(self._dict) > self._maxsize 50 | 51 | cdef inline cleanup_one(self): 52 | k, _ = self._dict.popitem(last=False) 53 | return k 54 | 55 | def __getitem__(self, key): 56 | o = self._dict[key] 57 | self._dict_move_to_end(key) # last=True 58 | return o 59 | 60 | def __setitem__(self, key, o): 61 | if key in self._dict: 62 | self._dict[key] = o 63 | self._dict_move_to_end(key) # last=True 64 | else: 65 | self._dict[key] = o 66 | while self.needs_cleanup(): 67 | self.cleanup_one() 68 | 69 | def __delitem__(self, key): 70 | del self._dict[key] 71 | 72 | def __contains__(self, key): 73 | return key in self._dict 74 | 75 | def __len__(self): 76 | return len(self._dict) 77 | 78 | def __iter__(self): 79 | return iter(self._dict) 80 | -------------------------------------------------------------------------------- /uvloop/pseudosock.pyx: -------------------------------------------------------------------------------- 1 | cdef class PseudoSocket: 2 | cdef: 3 | int _family 4 | int _type 5 | int _proto 6 | int _fd 7 | object _peername 8 | object _sockname 9 | 10 | def __init__(self, int family, int type, int proto, int fd): 11 | self._family = family 12 | self._type = type 13 | self._proto = proto 14 | self._fd = fd 15 | self._peername = None 16 | self._sockname = None 17 | 18 | cdef _na(self, what): 19 | raise TypeError('transport sockets do not support {}'.format(what)) 20 | 21 | cdef _make_sock(self): 22 | return socket_socket(self._family, self._type, self._proto, self._fd) 23 | 24 | property family: 25 | def __get__(self): 26 | try: 27 | return socket_AddressFamily(self._family) 28 | except ValueError: 29 | return self._family 30 | 31 | property type: 32 | def __get__(self): 33 | try: 34 | return socket_SocketKind(self._type) 35 | except ValueError: 36 | return self._type 37 | 38 | property proto: 39 | def __get__(self): 40 | return self._proto 41 | 42 | def __repr__(self): 43 | s = ("self.request.data is not self: 44 | raise RuntimeError( 45 | '{}.cancel: .request.data is not UVRequest'.format( 46 | self.__class__.__name__)) 47 | 48 | # We only can cancel pending requests. Let's try. 49 | err = uv.uv_cancel(self.request) 50 | if err < 0: 51 | if err == uv.UV_EBUSY: 52 | # Can't close the request -- it's executing (see the first 53 | # comment). Loop will have to wait until the callback 54 | # fires. 55 | pass 56 | elif err == uv.UV_EINVAL: 57 | # From libuv docs: 58 | # 59 | # Only cancellation of uv_fs_t, uv_getaddrinfo_t, 60 | # uv_getnameinfo_t and uv_work_t requests is currently 61 | # supported. 62 | return 63 | else: 64 | ex = convert_error(err) 65 | self.loop._handle_exception(ex) 66 | -------------------------------------------------------------------------------- /uvloop/server.pxd: -------------------------------------------------------------------------------- 1 | cdef class Server: 2 | cdef: 3 | list _servers 4 | list _waiters 5 | int _active_count 6 | Loop _loop 7 | bint _serving 8 | object _serving_forever_fut 9 | object __weakref__ 10 | 11 | cdef _add_server(self, UVStreamServer srv) 12 | cdef _start_serving(self) 13 | cdef _wakeup(self) 14 | 15 | cdef _attach(self) 16 | cdef _detach(self) 17 | 18 | cdef _ref(self) 19 | cdef _unref(self) 20 | -------------------------------------------------------------------------------- /uvloop/server.pyx: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | 4 | cdef class Server: 5 | def __cinit__(self, Loop loop): 6 | self._loop = loop 7 | self._servers = [] 8 | self._waiters = [] 9 | self._active_count = 0 10 | self._serving_forever_fut = None 11 | 12 | cdef _add_server(self, UVStreamServer srv): 13 | self._servers.append(srv) 14 | 15 | cdef _start_serving(self): 16 | if self._serving: 17 | return 18 | 19 | self._serving = 1 20 | for server in self._servers: 21 | (server).listen() 22 | 23 | cdef _wakeup(self): 24 | cdef list waiters 25 | 26 | waiters = self._waiters 27 | self._waiters = None 28 | for waiter in waiters: 29 | if not waiter.done(): 30 | waiter.set_result(waiter) 31 | 32 | cdef _attach(self): 33 | assert self._servers is not None 34 | self._active_count += 1 35 | 36 | cdef _detach(self): 37 | assert self._active_count > 0 38 | self._active_count -= 1 39 | if self._active_count == 0 and self._servers is None: 40 | self._wakeup() 41 | 42 | cdef _ref(self): 43 | # Keep the server object alive while it's not explicitly closed. 44 | self._loop._servers.add(self) 45 | 46 | cdef _unref(self): 47 | self._loop._servers.discard(self) 48 | 49 | # Public API 50 | 51 | @cython.iterable_coroutine 52 | async def __aenter__(self): 53 | return self 54 | 55 | @cython.iterable_coroutine 56 | async def __aexit__(self, *exc): 57 | self.close() 58 | await self.wait_closed() 59 | 60 | def __repr__(self): 61 | return '<%s sockets=%r>' % (self.__class__.__name__, self.sockets) 62 | 63 | def get_loop(self): 64 | return self._loop 65 | 66 | @cython.iterable_coroutine 67 | async def wait_closed(self): 68 | # Do not remove `self._servers is None` below 69 | # because close() method only closes server sockets 70 | # and existing client connections are left open. 71 | if self._servers is None or self._waiters is None: 72 | return 73 | waiter = self._loop._new_future() 74 | self._waiters.append(waiter) 75 | await waiter 76 | 77 | def close(self): 78 | cdef list servers 79 | 80 | if self._servers is None: 81 | return 82 | 83 | try: 84 | servers = self._servers 85 | self._servers = None 86 | self._serving = 0 87 | 88 | for server in servers: 89 | (server)._close() 90 | 91 | if self._active_count == 0: 92 | self._wakeup() 93 | finally: 94 | self._unref() 95 | 96 | def is_serving(self): 97 | return self._serving 98 | 99 | @cython.iterable_coroutine 100 | async def start_serving(self): 101 | self._start_serving() 102 | 103 | @cython.iterable_coroutine 104 | async def serve_forever(self): 105 | if self._serving_forever_fut is not None: 106 | raise RuntimeError( 107 | f'server {self!r} is already being awaited on serve_forever()') 108 | if self._servers is None: 109 | raise RuntimeError(f'server {self!r} is closed') 110 | 111 | self._start_serving() 112 | self._serving_forever_fut = self._loop.create_future() 113 | 114 | try: 115 | await self._serving_forever_fut 116 | except asyncio.CancelledError: 117 | try: 118 | self.close() 119 | await self.wait_closed() 120 | finally: 121 | raise 122 | finally: 123 | self._serving_forever_fut = None 124 | 125 | property sockets: 126 | def __get__(self): 127 | cdef list sockets = [] 128 | 129 | # Guard against `self._servers is None` 130 | if self._servers: 131 | for server in self._servers: 132 | sockets.append( 133 | (server)._get_socket() 134 | ) 135 | 136 | return sockets 137 | -------------------------------------------------------------------------------- /uvloop/sslproto.pxd: -------------------------------------------------------------------------------- 1 | cdef enum SSLProtocolState: 2 | UNWRAPPED = 0 3 | DO_HANDSHAKE = 1 4 | WRAPPED = 2 5 | FLUSHING = 3 6 | SHUTDOWN = 4 7 | 8 | 9 | cdef enum AppProtocolState: 10 | # This tracks the state of app protocol (https://git.io/fj59P): 11 | # 12 | # INIT -cm-> CON_MADE [-dr*->] [-er-> EOF?] -cl-> CON_LOST 13 | # 14 | # * cm: connection_made() 15 | # * dr: data_received() 16 | # * er: eof_received() 17 | # * cl: connection_lost() 18 | 19 | STATE_INIT = 0 20 | STATE_CON_MADE = 1 21 | STATE_EOF = 2 22 | STATE_CON_LOST = 3 23 | 24 | 25 | cdef class _SSLProtocolTransport: 26 | cdef: 27 | Loop _loop 28 | SSLProtocol _ssl_protocol 29 | bint _closed 30 | object context 31 | 32 | 33 | cdef class SSLProtocol: 34 | cdef: 35 | bint _server_side 36 | str _server_hostname 37 | object _sslcontext 38 | 39 | object _extra 40 | 41 | object _write_backlog 42 | size_t _write_buffer_size 43 | 44 | object _waiter 45 | Loop _loop 46 | _SSLProtocolTransport _app_transport 47 | bint _app_transport_created 48 | 49 | object _transport 50 | object _ssl_handshake_timeout 51 | object _ssl_shutdown_timeout 52 | 53 | object _sslobj 54 | object _sslobj_read 55 | object _sslobj_write 56 | object _incoming 57 | object _incoming_write 58 | object _outgoing 59 | object _outgoing_read 60 | char* _ssl_buffer 61 | size_t _ssl_buffer_len 62 | object _ssl_buffer_view 63 | SSLProtocolState _state 64 | size_t _conn_lost 65 | AppProtocolState _app_state 66 | 67 | bint _ssl_writing_paused 68 | bint _app_reading_paused 69 | 70 | size_t _incoming_high_water 71 | size_t _incoming_low_water 72 | bint _ssl_reading_paused 73 | 74 | bint _app_writing_paused 75 | size_t _outgoing_high_water 76 | size_t _outgoing_low_water 77 | 78 | object _app_protocol 79 | bint _app_protocol_is_buffer 80 | object _app_protocol_get_buffer 81 | object _app_protocol_buffer_updated 82 | 83 | object _handshake_start_time 84 | object _handshake_timeout_handle 85 | object _shutdown_timeout_handle 86 | 87 | cdef _set_app_protocol(self, app_protocol) 88 | cdef _wakeup_waiter(self, exc=*) 89 | cdef _get_extra_info(self, name, default=*) 90 | cdef _set_state(self, SSLProtocolState new_state) 91 | 92 | # Handshake flow 93 | 94 | cdef _start_handshake(self) 95 | cdef _check_handshake_timeout(self) 96 | cdef _do_handshake(self) 97 | cdef _on_handshake_complete(self, handshake_exc) 98 | 99 | # Shutdown flow 100 | 101 | cdef _start_shutdown(self, object context=*) 102 | cdef _check_shutdown_timeout(self) 103 | cdef _do_read_into_void(self, object context) 104 | cdef _do_flush(self, object context=*) 105 | cdef _do_shutdown(self, object context=*) 106 | cdef _on_shutdown_complete(self, shutdown_exc) 107 | cdef _abort(self, exc) 108 | 109 | # Outgoing flow 110 | 111 | cdef _write_appdata(self, list_of_data, object context) 112 | cdef _do_write(self) 113 | cdef _process_outgoing(self) 114 | 115 | # Incoming flow 116 | 117 | cdef _do_read(self) 118 | cdef _do_read__buffered(self) 119 | cdef _do_read__copied(self) 120 | cdef _call_eof_received(self, object context=*) 121 | 122 | # Flow control for writes from APP socket 123 | 124 | cdef _control_app_writing(self, object context=*) 125 | cdef size_t _get_write_buffer_size(self) 126 | cdef _set_write_buffer_limits(self, high=*, low=*) 127 | 128 | # Flow control for reads to APP socket 129 | 130 | cdef _pause_reading(self) 131 | cdef _resume_reading(self, object context) 132 | 133 | # Flow control for reads from SSL socket 134 | 135 | cdef _control_ssl_reading(self) 136 | cdef _set_read_buffer_limits(self, high=*, low=*) 137 | cdef size_t _get_read_buffer_size(self) 138 | cdef _fatal_error(self, exc, message=*) 139 | --------------------------------------------------------------------------------