├── .ackrc ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.rst ├── LICENSE ├── MANIFEST.in ├── README.rst ├── docs ├── Makefile ├── source │ ├── api │ │ ├── const.rst │ │ ├── event.rst │ │ ├── greenpool.rst │ │ ├── greenthread.rst │ │ ├── patcher.rst │ │ ├── queue.rst │ │ ├── semaphore.rst │ │ ├── switch.rst │ │ ├── timeout.rst │ │ ├── websocket.rst │ │ └── wsgi.rst │ ├── conf.py │ ├── howitworks.rst │ ├── index.rst │ ├── librarysupport.rst │ └── modules.rst └── theme │ ├── bootstrap │ ├── globaltoc.html │ ├── layout.html │ ├── localtoc.html │ ├── navbar.html │ ├── navbarsearchbox.html │ ├── navbartoc.html │ ├── relations.html │ ├── search.html │ ├── searchbox.html │ ├── searchresults.html │ ├── sourcelink.html │ ├── static │ │ ├── bootstrap-sphinx.css_t │ │ ├── bootstrap-sphinx.js_t │ │ ├── bootstrap_dist │ │ │ ├── css │ │ │ │ ├── bootstrap-theme.css │ │ │ │ ├── bootstrap-theme.css.map │ │ │ │ ├── bootstrap-theme.min.css │ │ │ │ ├── bootstrap.css │ │ │ │ ├── bootstrap.css.map │ │ │ │ └── bootstrap.min.css │ │ │ ├── fonts │ │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ │ └── glyphicons-halflings-regular.woff │ │ │ └── js │ │ │ │ ├── bootstrap.js │ │ │ │ ├── bootstrap.min.js │ │ │ │ └── npm.js │ │ ├── bootswatch │ │ │ ├── cerulean │ │ │ │ ├── bootstrap.css │ │ │ │ └── bootstrap.min.css │ │ │ ├── cosmo │ │ │ │ ├── bootstrap.css │ │ │ │ └── bootstrap.min.css │ │ │ ├── custom │ │ │ │ ├── bootstrap.css │ │ │ │ └── bootstrap.min.css │ │ │ ├── cyborg │ │ │ │ ├── bootstrap.css │ │ │ │ └── bootstrap.min.css │ │ │ ├── darkly │ │ │ │ ├── bootstrap.css │ │ │ │ └── bootstrap.min.css │ │ │ ├── flatly │ │ │ │ ├── bootstrap.css │ │ │ │ └── bootstrap.min.css │ │ │ ├── fonts │ │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ │ └── glyphicons-halflings-regular.woff │ │ │ ├── journal │ │ │ │ ├── bootstrap.css │ │ │ │ └── bootstrap.min.css │ │ │ ├── lumen │ │ │ │ ├── bootstrap.css │ │ │ │ └── bootstrap.min.css │ │ │ ├── paper │ │ │ │ ├── bootstrap.css │ │ │ │ └── bootstrap.min.css │ │ │ ├── readable │ │ │ │ ├── bootstrap.css │ │ │ │ └── bootstrap.min.css │ │ │ ├── sandstone │ │ │ │ ├── bootstrap.css │ │ │ │ └── bootstrap.min.css │ │ │ ├── simplex │ │ │ │ ├── bootstrap.css │ │ │ │ └── bootstrap.min.css │ │ │ ├── slate │ │ │ │ ├── bootstrap.css │ │ │ │ └── bootstrap.min.css │ │ │ ├── spacelab │ │ │ │ ├── bootstrap.css │ │ │ │ └── bootstrap.min.css │ │ │ ├── superhero │ │ │ │ ├── bootstrap.css │ │ │ │ └── bootstrap.min.css │ │ │ ├── united │ │ │ │ ├── bootstrap.css │ │ │ │ └── bootstrap.min.css │ │ │ └── yeti │ │ │ │ ├── bootstrap.css │ │ │ │ └── bootstrap.min.css │ │ ├── fonts │ │ │ ├── dejavusansmono-bold-webfont.woff │ │ │ ├── dejavusansmono-bold-webfont.woff2 │ │ │ ├── dejavusansmono-boldoblique-webfont.woff │ │ │ ├── dejavusansmono-boldoblique-webfont.woff2 │ │ │ ├── dejavusansmono-oblique-webfont.woff │ │ │ ├── dejavusansmono-oblique-webfont.woff2 │ │ │ ├── dejavusansmono-webfont.woff │ │ │ └── dejavusansmono-webfont.woff2 │ │ └── js │ │ │ ├── jquery-1.11.0.min.js │ │ │ └── jquery-fix.js │ └── theme.conf │ └── pygments_solarized_light.py ├── examples ├── cassandra_db.py ├── crawler.py ├── guv ├── guv_simple_server.py ├── logger.py ├── pyuv_cffi ├── pyuv_cffi_example.py ├── threads.py └── wsgi_app.py ├── guv ├── __init__.py ├── compat.py ├── const.py ├── event.py ├── exceptions.py ├── fileobject.py ├── green │ ├── __init__.py │ ├── _socket3.py │ ├── _ssl32.py │ ├── _ssl33.py │ ├── builtin.py │ ├── greenlet_local.py │ ├── lock.py │ ├── os.py │ ├── queue.py │ ├── select.py │ ├── socket.py │ ├── ssl.py │ ├── subprocess.py │ ├── thread.py │ ├── threading.py │ └── time.py ├── greenio.py ├── greenpool.py ├── greenthread.py ├── hubs │ ├── __init__.py │ ├── abc.py │ ├── hub.py │ ├── pyuv_cffi.py │ ├── switch.py │ └── timer.py ├── patcher.py ├── queue.py ├── semaphore.py ├── server.py ├── support │ ├── __init__.py │ ├── cassandra.py │ ├── greendns.py │ ├── gunicorn_worker.py │ └── psycopg2_patcher.py ├── timeout.py ├── util │ ├── __init__.py │ ├── debug.py │ ├── decorators.py │ └── profile.py ├── websocket.py └── wsgi.py ├── pytest.ini ├── pyuv_cffi ├── __init__.py ├── pyuv_cffi.c └── pyuv_cffi_cdef.c ├── requirements.in ├── requirements.txt ├── setup.py ├── tests ├── __init__.py ├── conftest.py ├── test_greenio.py └── test_threads.py └── tox.ini /.ackrc: -------------------------------------------------------------------------------- 1 | --ignore-dir=.idea 2 | --ignore-dir=.tox 3 | --ignore-dir=build 4 | --ignore-dir=guv.egg-info 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg* 2 | *.pyc 3 | build/ 4 | dist/ 5 | examples/*.profile 6 | examples/*.stats 7 | examples/_*.py 8 | pyuv_cffi/uv.h 9 | 10 | # virtual env 11 | venv* 12 | 13 | # documentation 14 | docs/build 15 | 16 | ### Python ### 17 | # Byte-compiled / optimized / DLL files 18 | __pycache__/ 19 | *.py[cod] 20 | 21 | # C extensions 22 | *.so 23 | 24 | # Distribution / packaging 25 | src 26 | .Python 27 | env/ 28 | build/ 29 | develop-eggs/ 30 | dist/ 31 | eggs/ 32 | lib/ 33 | lib64/ 34 | parts/ 35 | sdist/ 36 | var/ 37 | *.egg-info/ 38 | .installed.cfg 39 | *.egg 40 | 41 | # PyInstaller 42 | # Usually these files are written by a python script from a template 43 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 44 | *.manifest 45 | *.spec 46 | 47 | # Installer logs 48 | pip-log.txt 49 | pip-delete-this-directory.txt 50 | 51 | # Unit test / coverage reports 52 | htmlcov/ 53 | .tox/ 54 | .coverage 55 | .cache 56 | nosetests.xml 57 | coverage.xml 58 | 59 | # Translations 60 | *.mo 61 | *.pot 62 | 63 | # Django stuff: 64 | *.log 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | 73 | ### PyCharm ### 74 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 75 | 76 | ## Directory-based project format 77 | .idea/ 78 | # if you remove the above rule, at least ignore user-specific stuff: 79 | # .idea/workspace.xml 80 | # .idea/tasks.xml 81 | # and these sensitive or high-churn files: 82 | # .idea/dataSources.ids 83 | # .idea/dataSources.xml 84 | # .idea/sqlDataSources.xml 85 | # .idea/dynamic.xml 86 | 87 | ## File-based project format 88 | *.ipr 89 | *.iml 90 | *.iws 91 | 92 | ## Additional for IntelliJ 93 | out/ 94 | 95 | # generated by mpeltonen/sbt-idea plugin 96 | .idea_modules/ 97 | 98 | # generated by JIRA plugin 99 | atlassian-ide-plugin.xml 100 | 101 | # generated by Crashlytics plugin (for Android Studio and Intellij) 102 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - "3.4" 5 | - "3.3" 6 | - "3.2" 7 | - "pypy3" 8 | 9 | before_install: 10 | - sudo apt-get -qq update 11 | - sudo apt-get install -qq git-core build-essential autoconf libtool 12 | - sudo apt-get install libffi-dev 13 | - curl -L https://github.com/libuv/libuv/archive/v1.2.1.tar.gz | tar xzf - 14 | - (cd libuv-1.2.1 && ./autogen.sh && ./configure --prefix=/usr && make && sudo make install) 15 | 16 | install: 17 | - pip install -r requirements.txt 18 | 19 | script: 20 | - cd tests 21 | - py.test 22 | 23 | notifications: 24 | email: false -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | How to contribute 2 | ================= 3 | 4 | guv is a volunteer effort. We strive to maintain a high level of code and 5 | documentation quality and depend on bug reports and pull requests. 6 | 7 | If you experience issues using guv, please submit an issue on github. Feature 8 | requests are also welcome through the issue tracker. 9 | 10 | If you are submitting a pull request, please adhere to the following guidelines: 11 | 12 | - Code must be PEP8 compliant, with the following modification: the line length 13 | limit is increased to 100 characters. 14 | - The code must support Python >= 3.2. Please do not include support for Python 15 | 2.x. 16 | - Code must be documented with Sphinx docstrings and inline comments. 17 | - Please use an IDE such as PyCharm to assist with writing high quality code. 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Unless otherwise noted, files in guv are under the following MIT license: 2 | 3 | Copyright (c) 2014 V G 4 | 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to 8 | deal in the Software without restriction, including without limitation the 9 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | sell copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include examples *.py 2 | #recursive-include doc *.rst *.txt *.py Makefile *.png 3 | include MANIFEST.in LICENSE README.rst pytest.ini tox.ini 4 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | guv = greenlets + libuv 2 | ======================= 3 | 4 | |Version| |PyPI| |BuildStatus| 5 | 6 | :Documentation: http://guv.readthedocs.org/ 7 | :Source: http://github.com/veegee/guv 8 | :Keywords: guv, greenlet, gevent, eventlet 9 | 10 | 11 | About 12 | ----- 13 | 14 | guv is a fast networking library and WSGI server (like gevent/eventlet) for 15 | **Python >= 3.2 and pypy3** 16 | 17 | The event loop backend is pyuv_cffi_, which aims to be fully compatible with the 18 | pyuv_ interface. pyuv_cffi is fully supported on CPython and pypy3. libuv_ 19 | >= 1.0.0 is required. 20 | 21 | Asynchronous DNS queries are supported via dnspython3. To forcefully disable 22 | greendns, set the environment variable ``GUV_NO_GREENDNS`` to any value. 23 | 24 | guv currently only runs on POSIX-compliant operating systems, but Windows 25 | support is not far off and can be added in the near future if there is a demand 26 | for this. 27 | 28 | This library is actively maintained and has a zero bug policy. Please submit 29 | issues and pull requests, and bugs will be fixed immediately. 30 | 31 | **This project is under active development and any help is appreciated.** 32 | 33 | 34 | Quickstart 35 | ---------- 36 | 37 | Since guv is currently in alpha release state and under active development, it 38 | is recommended to pull often and install manually:: 39 | 40 | git clone https://github.com/veegee/guv.git 41 | cd guv 42 | python setup.py install 43 | 44 | Note: libuv_ >= 1.0.0 is required. This is the first stable version but is a 45 | recent release and may not be available in Debian/Ubuntu stable repositories, so 46 | you must compile and install manually. 47 | 48 | Serve your WSGI app using guv directly 49 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 50 | 51 | .. code-block:: python 52 | 53 | import guv; guv.monkey_patch() 54 | import guv.wsgi 55 | 56 | app = 57 | 58 | if __name__ == '__main__': 59 | server_sock = guv.listen(('0.0.0.0', 8001)) 60 | guv.wsgi.serve(server_sock, app) 61 | 62 | Serve your WSGI app using guv with gunicorn_ 63 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 64 | 65 | :: 66 | 67 | gunicorn -w 4 -b 127.0.0.1:8001 -k guv.GuvWorker wsgi_app:app 68 | 69 | Note: you can use wrk_ to benchmark the performance of guv. 70 | 71 | Crawl the web: efficiently make multiple "simultaneous" requests 72 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 73 | 74 | .. code-block:: python 75 | 76 | import guv; guv.monkey_patch() 77 | import requests 78 | 79 | def get_url(url): 80 | print('get_url({})'.format(url)) 81 | return requests.get(url) 82 | 83 | def main(): 84 | urls = ['http://gnu.org'] * 10 85 | urls += ['https://eff.org'] * 10 86 | 87 | pool = guv.GreenPool() 88 | results = pool.starmap(get_url, zip(urls)) 89 | 90 | for i, resp in enumerate(results): 91 | print('{}: done, length: {}'.format(i, len(resp.text))) 92 | 93 | if __name__ == '__main__': 94 | main() 95 | 96 | 97 | Guarantees 98 | ---------- 99 | 100 | This library makes the following guarantees: 101 | 102 | * `Semantic versioning`_ is strictly followed 103 | * Compatible with Python >= 3.2.0 and PyPy3 >= 2.3.1 (Python 3.2.5) 104 | 105 | 106 | .. _pyuv: https://github.com/saghul/pyuv 107 | .. _pyuv_cffi: https://github.com/veegee/guv/tree/develop/pyuv_cffi 108 | .. _libuv: https://github.com/libuv/libuv 109 | .. _gunicorn: https://github.com/benoitc/gunicorn 110 | .. _Semantic versioning: http://semver.org 111 | .. _wrk: https://github.com/wg/wrk 112 | 113 | .. |Version| image:: https://img.shields.io/github/tag/veegee/guv.svg 114 | 115 | .. |PyPI| image:: https://img.shields.io/pypi/v/guv.svg 116 | :target: https://pypi.python.org/pypi/guv/ 117 | :alt: Latest Version 118 | 119 | .. |BuildStatus| image:: https://travis-ci.org/veegee/guv.svg?branch=develop 120 | :target: https://travis-ci.org/veegee/guv 121 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Shoutworker.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Shoutworker.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Shoutworker" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Shoutworker" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/source/api/const.rst: -------------------------------------------------------------------------------- 1 | :mod:`guv.const` - constants 2 | ============================ 3 | 4 | 5 | Event Types 6 | ----------- 7 | 8 | .. autodata:: guv.const.READ 9 | :annotation: = 1 10 | 11 | .. autodata:: guv.const.WRITE 12 | :annotation: = 2 13 | -------------------------------------------------------------------------------- /docs/source/api/event.rst: -------------------------------------------------------------------------------- 1 | :mod:`guv.event` - event primitive for greenthreads 2 | =================================================== 3 | 4 | .. automodule:: guv.event 5 | :special-members: __init__ 6 | 7 | -------------------------------------------------------------------------------- /docs/source/api/greenpool.rst: -------------------------------------------------------------------------------- 1 | :mod:`guv.greenpool` - greenthread pools 2 | ======================================== 3 | 4 | .. automodule:: guv.greenpool 5 | :special-members: __init__ 6 | -------------------------------------------------------------------------------- /docs/source/api/greenthread.rst: -------------------------------------------------------------------------------- 1 | :mod:`guv.greenthread` - cooperative threads 2 | ============================================ 3 | 4 | .. automodule:: guv.greenthread 5 | :special-members: __init__ 6 | -------------------------------------------------------------------------------- /docs/source/api/patcher.rst: -------------------------------------------------------------------------------- 1 | :mod:`guv.patcher` - monkey-patching the standard library 2 | ========================================================= 3 | 4 | .. automodule:: guv.patcher 5 | :special-members: __init__ 6 | -------------------------------------------------------------------------------- /docs/source/api/queue.rst: -------------------------------------------------------------------------------- 1 | :mod:`guv.queue` - greenthread-compatible queue 2 | =============================================== 3 | 4 | .. automodule:: guv.queue 5 | :special-members: __init__ 6 | -------------------------------------------------------------------------------- /docs/source/api/semaphore.rst: -------------------------------------------------------------------------------- 1 | :mod:`guv.semaphore` - greenthread-compatible semaphore 2 | ======================================================= 3 | 4 | .. automodule:: guv.semaphore 5 | :special-members: __init__ 6 | 7 | -------------------------------------------------------------------------------- /docs/source/api/switch.rst: -------------------------------------------------------------------------------- 1 | :mod:`guv.hubs.switch` - facilities for cooperative yielding 2 | ============================================================ 3 | 4 | .. automodule:: guv.hubs.switch 5 | -------------------------------------------------------------------------------- /docs/source/api/timeout.rst: -------------------------------------------------------------------------------- 1 | :mod:`guv.timeout` - universal timeouts 2 | ======================================= 3 | 4 | .. automodule:: guv.timeout 5 | :special-members: __init__ 6 | -------------------------------------------------------------------------------- /docs/source/api/websocket.rst: -------------------------------------------------------------------------------- 1 | :mod:`guv.websocket` - websocket server 2 | ======================================= 3 | 4 | .. automodule:: guv.websocket 5 | :special-members: __init__ 6 | -------------------------------------------------------------------------------- /docs/source/api/wsgi.rst: -------------------------------------------------------------------------------- 1 | :mod:`guv.wsgi` - WSGI server 2 | ============================= 3 | 4 | .. automodule:: guv.wsgi 5 | :special-members: __init__ 6 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import re 4 | import inspect 5 | from unittest.mock import MagicMock 6 | 7 | from docutils import nodes 8 | 9 | import sphinx.ext.autodoc 10 | import sphinx 11 | 12 | 13 | # mock pyuv_cffi because readthedocs can't build it 14 | class Mock(MagicMock): 15 | @classmethod 16 | def __getattr__(cls, name): 17 | return Mock() 18 | 19 | sys.modules['pyuv_cffi'] = Mock() 20 | 21 | sys.path.insert(0, os.path.abspath('../theme')) # for Pygments Solarized style 22 | sys.path.insert(0, os.path.abspath('../..')) 23 | import guv 24 | 25 | needs_sphinx = '1.2' 26 | 27 | extensions = [ 28 | 'sphinx.ext.autodoc', 29 | 'sphinx.ext.intersphinx', 30 | 'sphinx.ext.viewcode' 31 | ] 32 | 33 | # configure autodoc member grouping and ordering 34 | sphinx.ext.autodoc.DataDocumenter.member_order = 5 35 | sphinx.ext.autodoc.AttributeDocumenter.member_order = 6 36 | sphinx.ext.autodoc.InstanceAttributeDocumenter.member_order = 7 37 | autodoc_member_order = 'groupwise' 38 | autodoc_default_flags = ['members', 'show-inheritance'] 39 | 40 | intersphinx_mapping = {'http://docs.python.org/3.4': None} 41 | 42 | templates_path = ['_templates'] 43 | source_suffix = '.rst' 44 | 45 | # The master toctree document. 46 | master_doc = 'index' 47 | 48 | # General information about the project. 49 | project = 'guv' 50 | copyright = '2014, V G' 51 | 52 | # The short X.Y version. 53 | version = guv.__version__ 54 | # The full version, including alpha/beta/rc tags. 55 | release = guv.__version__ 56 | 57 | # List of patterns, relative to source directory, that match files and 58 | # directories to ignore when looking for source files. 59 | exclude_patterns = [] 60 | 61 | # default_role = None 62 | 63 | pygments_style = 'pygments_solarized_light.LightStyle' 64 | 65 | # A list of ignored prefixes for module index sorting. 66 | # modindex_common_prefix = [] 67 | 68 | # -- Options for HTML output ---------------------------------------------- 69 | 70 | html_context = {'sphinx_versioninfo': sphinx.version_info} 71 | 72 | html_theme_path = ['../theme'] 73 | html_theme = 'bootstrap' 74 | html_theme_options = { 75 | # Tab name for entire site. (Default: "Site") 76 | 'navbar_site_name': 'guv docs', 77 | 78 | # Render the next and previous page links in navbar. (Default: true) 79 | 'navbar_sidebarrel': False, 80 | 81 | # Render the current pages TOC in the navbar. (Default: true) 82 | # 'navbar_pagenav': True, 83 | 84 | # Tab name for the current pages TOC. (Default: "Page") 85 | 'navbar_pagenav_name': 'Page', 86 | 87 | # Global TOC depth for "site" navbar tab. (Default: 1) 88 | # Switching to -1 shows all levels. 89 | 'globaltoc_depth': 2, 90 | 91 | # Include hidden TOCs in Site navbar? 92 | # 93 | # Note: If this is "false", you cannot have mixed ``:hidden:`` and 94 | # non-hidden ``toctree`` directives in the same page, or else the build 95 | # will break. 96 | # 97 | # Values: "true" (default) or "false" 98 | # 'globaltoc_includehidden': "true", 99 | 100 | # HTML navbar class (Default: "navbar") to attach to
element. 101 | # For black navbar, do "navbar navbar-inverse" 102 | # 'navbar_class': "navbar", 103 | 104 | # Fix navigation bar to top of page? 105 | # Values: "true" (default) or "false" 106 | 'navbar_fixed_top': 'true', 107 | 108 | # Location of link to source. 109 | # Options are "nav" (default), "footer" or anything else to exclude. 110 | 'source_link_position': 'nav', 111 | 112 | # Bootswatch (http://bootswatch.com/) theme. 113 | # 114 | # Options are nothing with "" (default) or the name of a valid theme 115 | # such as "amelia" or "cosmo". 116 | 'bootswatch_theme': 'flatly', 117 | } 118 | 119 | # html_static_path = ['_static'] 120 | 121 | # Custom sidebar templates, maps document names to template names. 122 | # html_sidebars = {} 123 | 124 | # Output file base name for HTML help builder. 125 | htmlhelp_basename = 'guvdoc' 126 | 127 | 128 | def get_field(doc: str, name: str): 129 | """Parse ReST field from document 130 | 131 | :param str doc: string of whole document 132 | :param name: name of field excluding the surrounding `:` 133 | :return: value of field 134 | :rtype: str 135 | """ 136 | match = re.search(':{}: (.*)$'.format(name), doc, re.IGNORECASE | re.MULTILINE) 137 | if match: 138 | return match.group(1).strip() 139 | 140 | 141 | def autodoc_process_signature(app, what, name, obj, options, signature, return_annotation): 142 | doc = str(obj.__doc__) 143 | otype = str(type(obj)) 144 | msg_meth = '{what:11} {otype:23} {name:50} -> {rt}' 145 | msg_class = '{what:11} {otype:23} {name:50} {sig}' 146 | 147 | rt = None 148 | if what == 'method' and not name.endswith('__init__'): 149 | # annotate return type for methods (excluding __init__) 150 | if 'rtype' in doc: 151 | rt = get_field(doc, 'rtype') 152 | print(msg_meth.format(**locals())) 153 | elif obj.__doc__ is not None: 154 | # assume methods with a docstring but undocumented rtype return `None` 155 | rt = 'None' 156 | print(msg_meth.format(**locals())) 157 | else: 158 | # no docstring defined 159 | rt = '?' 160 | print(msg_meth.format(**locals())) 161 | elif type(obj) is property: 162 | # annotate return type for properties 163 | if 'rtype' in doc: 164 | rt = get_field(doc, 'rtype') 165 | print(msg_meth.format(**locals())) 166 | else: 167 | # no docstring defined 168 | rt = '?' 169 | print(msg_meth.format(**locals())) 170 | else: 171 | rt = 'skip' 172 | print(msg_meth.format(**locals())) 173 | 174 | if rt and rt not in ['?', 'skip']: 175 | return signature, rt 176 | 177 | 178 | def autodoc_process_docstring(app, what, name, obj, options, lines): 179 | s_before = None 180 | s_after = None 181 | if type(obj) == property: 182 | s_before = ':annotation:`@property`\n' 183 | # this as a @property 184 | elif what == 'method' and hasattr(obj, '__isabstractmethod__') and obj.__isabstractmethod__: 185 | # this is an @abstractmethod 186 | s_before = ':annotation:`@abstractmethod`\n' 187 | elif what == 'class' and tuple in obj.__bases__ and hasattr(obj, '_fields'): 188 | # this is a namedtuple 189 | lines[0] = '**namedtuple** :namedtuple:`{}`'.format(lines[0]) 190 | 191 | if s_before: 192 | for line in reversed(s_before.split('\n')): 193 | lines.insert(0, line) 194 | if s_after: 195 | for line in s_after.split('\n'): 196 | lines.append(line) 197 | 198 | 199 | def autodoc_skip_member(app, what, name, obj, sphinx_skip, options): 200 | skip = False 201 | doc = obj.__doc__ 202 | if type(obj) is property: 203 | if doc and re.match('Alias for field number \d+$', doc): 204 | # this is a namedtuple property 205 | skip = True 206 | 207 | return sphinx_skip or skip 208 | 209 | 210 | def generic_span_role(name, rawtext, text, lineno, inliner, options={}, content=[]): 211 | """ 212 | :param name: role name used in the document 213 | :param rawtext: entire markup snippet, with role 214 | :param text: text marked with the role 215 | :param lineno: line number where rawtext appears in the input 216 | :param inliner: inliner instance that called us 217 | :param options: directive options for customization 218 | :param content: directive content for customization 219 | :return: list, list 220 | :rtype: list, list 221 | """ 222 | node = nodes.inline(rawtext, text) 223 | node['classes'] = [name] 224 | return [node], [] 225 | 226 | 227 | def setup(app): 228 | # register custom ReST roles 229 | app.add_role('annotation', generic_span_role) 230 | app.add_role('namedtuple', generic_span_role) 231 | 232 | # connect methods to events 233 | app.connect('autodoc-process-signature', autodoc_process_signature) 234 | app.connect('autodoc-process-docstring', autodoc_process_docstring) 235 | app.connect('autodoc-skip-member', autodoc_skip_member) 236 | -------------------------------------------------------------------------------- /docs/source/howitworks.rst: -------------------------------------------------------------------------------- 1 | How guv works 2 | ============= 3 | 4 | 5 | The "old" way of writing servers 6 | -------------------------------- 7 | 8 | The classic server design involves blocking sockets, :func:`select.select`, and 9 | spawning operating system threads for each new client connection. The only 10 | advantage of this method is the simplicity of its design. Although sufficient 11 | for serving a very small number of clients, system resources quickly get maxed 12 | out when spawning a large number of threads frequently, and 13 | :func:`select.select` doesn't scale well to a large number of open file 14 | descriptors. 15 | 16 | An improvement on this design is using a platform-specific ``poll()`` mechanism 17 | such as ``epoll()``, which handles polling a large number of file descriptors 18 | much more efficiently. 19 | 20 | However, the thread issue remains. Typical solutions involve implementing the 21 | "reactor pattern" in an event loop using something like ``epoll()``. The issue 22 | with this approach is that all code runs in a single thread and one must be 23 | careful not to block the thread in any way. Setting the socket file descriptors 24 | to non-blocking mode helps in this aspect, but effectively using this design 25 | pattern is difficult, and requires the cooperation of all parts of the system. 26 | 27 | 28 | Coroutines, event loops, and monkey-patching 29 | -------------------------------------------- 30 | 31 | guv is an elegant solution to all of the problems mentioned above. It allows you 32 | to write highly efficient code that *looks* like it's running in its own thread, 33 | and *looks* like it's blocking. It does this by making use of greenlets_ instead 34 | of operating system threads, and globally monkey-patching system modules to 35 | cooperatively yield while waiting for I/O or other events. greenlets are 36 | extremely light-weight, and all run in a single operating system thread; 37 | switching between greenlets incurs very low overhead. Furthermore, only the 38 | greenlets that need switching to will be switched to when I/O or another event 39 | is ready; guv does not unnecessarily waste resources switching to greenlets that 40 | don't need attention. 41 | 42 | For example, the ``socket`` module is one of the core modules which is 43 | monkey-patched by guv. When using the patched socket module, calls to 44 | ``socket.read()`` on a "blocking" socket will register interest in the file 45 | descriptor, then cooperatively yield to another greenlet instead of blocking the 46 | entire thread. 47 | 48 | In addition, all monkey-patched modules are 100% API-compatible with the 49 | original system modules, so this allows existing networking code to run without 50 | modification as long as standard python modules are used. Code using C 51 | extensions will require simple modifications to cooperate with guv, since it is 52 | not possible to monkey-patch C code which may be making blocking function calls. 53 | 54 | 55 | The hub and :func:`~guv.hubs.switch.trampoline` 56 | --------------------------------------------------- 57 | 58 | The "hub" (:class:`guv.hubs.abc.AbstractHub`) is the core of guv and serves as 59 | the "scheduler" for greenlets. All calls to :func:`spawn` (and related 60 | functions) actually enqueue a request with the hub to spawn the greenlet on the 61 | next event loop iteration. The hub itself is a subclass of 62 | :class:`greenlet.greenlet` 63 | 64 | The hub also manages the underlying event loop (currently libuv only, but 65 | implementations for any event loop library, or even custom event loops can 66 | easily be written). Calls to monkey-patched functions actually register either a 67 | timer or the underlying file descriptor with libuv and switch ("yield") to the 68 | hub greenlet. 69 | 70 | The core function which facilitates the process of registering the file 71 | descriptor of interest and switching to the hub is 72 | :func:`~guv.hubs.switch.trampoline`. Examining the source code of included 73 | green modules reveals that this function is used extensively whenever interest 74 | in I/O events for a file descriptor needs to be registered. Note that this 75 | function does not need to be called by normal application code when writing code 76 | with the guv library; this is only part of the core inner working of guv. 77 | 78 | Another important function provided by guv for working with greenlets is 79 | :func:`~guv.hubs.switch.gyield`. This is a very simple function which simply 80 | yields the current greenlet, and registers a callback to resume on the next 81 | event loop iteration. 82 | 83 | If you require providing support for a library which cannot make use of the 84 | patched python standard socket module (such as the case for C extensions), then 85 | it is necessary to provide a support module which calls either 86 | :func:`~guv.hubs.switch.trampoline()` or :func:`~guv.hubs.switch.gyield` when 87 | there is a possibility that the C code will block for I/O. 88 | 89 | For examples of support modules for common libraries, see the support modules 90 | provided in the :mod:`guv.support` package. 91 | 92 | 93 | .. _greenlets: https://greenlet.readthedocs.org/en/latest/ 94 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | guv Documentation 2 | ================= 3 | 4 | .. note:: 5 | 6 | The documentation is currently in very active developemnt and not yet 7 | complete. Please keep checking back for updates and filing issues for 8 | missing sections or suggestions for enhancement. 9 | 10 | 11 | Contents 12 | -------- 13 | 14 | .. toctree:: 15 | :maxdepth: 2 16 | :titlesonly: 17 | 18 | How does guv work? 19 | librarysupport 20 | modules 21 | 22 | 23 | Introduction 24 | ------------ 25 | 26 | guv is a fast networking library and WSGI server (like gevent/eventlet) for 27 | **Python >= 3.2 and pypy3** 28 | 29 | The event loop backend is pyuv_cffi_, which aims to be fully compatible with the 30 | pyuv_ interface. pyuv_cffi is fully supported on CPython and pypy3. libuv_ 31 | >= 1.0.0 is required. 32 | 33 | Asynchronous DNS queries are supported via dnspython3. To forcefully disable 34 | greendns, set the environment variable ``GUV_NO_GREENDNS`` to any value. 35 | 36 | guv currently only runs on POSIX-compliant operating systems, but Windows 37 | support is not far off and can be added in the near future if there is a demand 38 | for this. 39 | 40 | This library is actively maintained and has a zero bug policy. Please submit 41 | issues and pull requests, and bugs will be fixed immediately. 42 | 43 | **This project is under active development and any help is appreciated.** 44 | 45 | 46 | Quickstart 47 | ---------- 48 | 49 | Since guv is currently in alpha release state and under active development, it 50 | is recommended to pull often and install manually:: 51 | 52 | git clone https://github.com/veegee/guv.git 53 | cd guv 54 | python setup.py install 55 | 56 | Note: libuv_ >= 1.0.0 is required. This is the first stable version but is a 57 | recent release and may not be available in Debian/Ubuntu stable repositories, so 58 | you must compile and install manually. 59 | 60 | Serve your WSGI app using guv directly 61 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 62 | 63 | .. code-block:: python 64 | 65 | import guv; guv.monkey_patch() 66 | import guv.wsgi 67 | 68 | app = 69 | 70 | if __name__ == '__main__': 71 | server_sock = guv.listen(('0.0.0.0', 8001)) 72 | guv.wsgi.serve(server_sock, app) 73 | 74 | Serve your WSGI app using guv with gunicorn_ 75 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 76 | 77 | :: 78 | 79 | gunicorn -w 4 -b 127.0.0.1:8001 -k guv.GuvWorker wsgi_app:app 80 | 81 | Note: you can use wrk_ to benchmark the performance of guv. 82 | 83 | Crawl the web: efficiently make multiple "simultaneous" requests 84 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 85 | 86 | .. code-block:: python 87 | 88 | import guv; guv.monkey_patch() 89 | import requests 90 | 91 | def get_url(url): 92 | print('get_url({})'.format(url)) 93 | return requests.get(url) 94 | 95 | def main(): 96 | urls = ['http://gnu.org'] * 10 97 | urls += ['https://eff.org'] * 10 98 | 99 | pool = guv.GreenPool() 100 | results = pool.starmap(get_url, zip(urls)) 101 | 102 | for i, resp in enumerate(results): 103 | print('{}: done, length: {}'.format(i, len(resp.text))) 104 | 105 | if __name__ == '__main__': 106 | main() 107 | 108 | 109 | Guarantees 110 | ---------- 111 | 112 | This library makes the following guarantees: 113 | 114 | * `Semantic versioning`_ is strictly followed 115 | * Compatible with Python >= 3.2.0 and PyPy3 >= 2.3.1 (Python 3.2.5) 116 | 117 | 118 | Testing 119 | ------- 120 | 121 | guv uses the excellent **tox** and **pytest** frameworks. To run all tests, run 122 | in the project root:: 123 | 124 | $ pip install pytest 125 | $ py.test 126 | 127 | 128 | .. _pyuv: https://github.com/saghul/pyuv 129 | .. _pyuv_cffi: https://github.com/veegee/guv/tree/develop/pyuv_cffi 130 | .. _libuv: https://github.com/libuv/libuv 131 | .. _gunicorn: https://github.com/benoitc/gunicorn 132 | .. _Semantic versioning: http://semver.org 133 | .. _wrk: https://github.com/wg/wrk 134 | -------------------------------------------------------------------------------- /docs/source/librarysupport.rst: -------------------------------------------------------------------------------- 1 | Library Support 2 | =============== 3 | 4 | The goal of guv is to support as many external libraries as possible such that 5 | no modification to application code is necessary. However, note that it is still 6 | required to use certain guv-specific constructs to take advantage of the 7 | concurrency (as demonstrated in the examples directory). 8 | 9 | Quick overview: 10 | 11 | - If your application code and any library dependencies are pure-python and use 12 | only standard library components like :mod:`socket`, :mod:`time`, :mod:`os`, 13 | etc., then your code is guaranteed to be compatible with guv. 14 | - If your application code depends on libraries that make blocking I/O calls 15 | *from external C code* (such as is the case for many popular database 16 | drivers), then a support module must be available to make those specific 17 | libraries cooperative. Such modules can be found in the `guv.support 18 | `_ package and are all 19 | enabled by default if the library is installed. 20 | 21 | .. note:: 22 | 23 | If your code is using only standard library components and is behaving in a 24 | non-cooperative way, this is considered a critical bug, which can be fixed 25 | by greenifying the appropriate standard library modules. Please submit a bug 26 | report to ensure that this issue is fixed as soon as possible. 27 | 28 | 29 | List of Known Compatible Libraries 30 | ---------------------------------- 31 | 32 | **Pure-python libraries are guaranteed to be compatible with no additional 33 | support modules**: 34 | 35 | - All standard library modules which make blocking calls such as I/O calls on 36 | file descriptors (including :mod:`socket`, :mod:`smtplib`, etc) are 37 | automatically supported. 38 | - `boto `_ 39 | - `Cassandra driver `_ 40 | - `gunicorn `_ (use with ``-k 41 | guv.GuvWorker``) 42 | - `pg8000 `_ 43 | - `redis-py `_ 44 | - `requests `_ 45 | - Many more. This list will be expanded as additional libraries are tested and 46 | *confirmed* to be compatible 47 | 48 | **Libraries containing C extensions which are currently supported**: 49 | 50 | - `psycopg2 `_ 51 | 52 | 53 | Writing support modules for external libraries 54 | ---------------------------------------------- 55 | 56 | The idea behind guv is that everything runs in one OS thread (even 57 | monkey-patched :class:`threading.Thread` objects!). Within this single thread, 58 | greenlets are used to switch between various functions efficiently. This means 59 | that any code making blocking calls will block the entire thread and prevent any 60 | other greenlet from running. For this reason, guv provides a monkey-patched 61 | standard library where all functions that can potentially block are replaced 62 | with their "greenified" counterparts that *yield* instead of blocking. The goal 63 | is to ensure that 100% of the standard library is greenified. If you encounter 64 | any part of the standard library that seems to be blocking instead of yielding, 65 | please file a bug report so this can be resolved as soon as possible. 66 | 67 | The issue arises when using modules which make calls to compiled code that 68 | cannot be monkey-patched (for example, through C extensions or CFFI). This is 69 | the case for many popular database drivers or other network code which aim for 70 | maximum performance. 71 | 72 | Some libraries provide mechanisms for the purpose of facilitating creating 73 | support modules for libraries such as guv. An excellent example is the high 74 | quality ``psycopg2`` database driver for PostgreSQL, written as a C extension. 75 | This library provides a very clean mechanism to call a callback before making 76 | any operations which could potentially block. This allows guv to 77 | :func:`~guv.hubs.switch.trampoline` and register the connection's file 78 | descriptor if the I/O operation would block. 79 | 80 | See the `psycopg2 patcher`_ for the implementation. 81 | 82 | However, many libraries do not provide such a mechanism to simplify creating a 83 | support module. In such case, there are several strategies for making these 84 | libraries cooperative. In all cases, the end goal is the same: call 85 | :func:`~guv.hubs.switch.trampoline`, which cooperatively yields and waits for 86 | the file descriptor to be ready for I/O. 87 | 88 | Note: this section is incomplete. 89 | 90 | 91 | .. _psycopg2 patcher: https://github.com/veegee/guv/blob/develop/guv/support/psycopg2_patcher.py 92 | -------------------------------------------------------------------------------- /docs/source/modules.rst: -------------------------------------------------------------------------------- 1 | Module Reference 2 | ================ 3 | 4 | .. toctree:: 5 | :maxdepth: 1 6 | :glob: 7 | 8 | api/* 9 | -------------------------------------------------------------------------------- /docs/theme/bootstrap/globaltoc.html: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /docs/theme/bootstrap/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "basic/layout.html" %} 2 | 3 | {% set theme_css_files = [] %} 4 | {% set bs_span_prefix = "col-md-" %} 5 | 6 | {% if theme_bootswatch_theme %} 7 | {% set theme_css_files = theme_css_files + [ 8 | '_static/bootswatch/' + theme_bootswatch_theme + '/bootstrap.css', 9 | '_static/bootstrap-sphinx.css' 10 | ] %} 11 | {% else %} 12 | {% set theme_css_files = theme_css_files + [ 13 | '_static/bootstrap/css/bootstrap.css', 14 | '_static/bootstrap-sphinx.css' 15 | ] %} 16 | {% endif %} 17 | 18 | {% if not bootswatch_css_custom %} 19 | {% set bootswatch_css_custom = [] %} 20 | {% endif %} 21 | {% set css_files = css_files + theme_css_files + bootswatch_css_custom %} 22 | 23 | {% set script_files = script_files + [ 24 | '_static/js/jquery-1.11.0.min.js', 25 | '_static/js/jquery-fix.js', 26 | '_static/bootstrap_dist/js/bootstrap.min.js', 27 | '_static/bootstrap-sphinx.js' 28 | ] %} 29 | 30 | {%- set render_sidebar = (not embedded) and (not theme_nosidebar|tobool) and sidebars %} 31 | 32 | {%- set bs_content_width = render_sidebar and "9" or "12" %} 33 | 34 | {%- block doctype -%} 35 | 36 | {%- endblock %} 37 | 38 | 39 | {%- macro bsidebar() %} 40 | {%- if render_sidebar %} 41 |
42 | 47 |
48 | {%- endif %} 49 | {%- endmacro %} 50 | 51 | {%- block extrahead %} 52 | 53 | 54 | 55 | 56 | {% endblock %} 57 | 58 | {# Silence the sidebar's, relbar's #} 59 | {% block header %}{% endblock %} 60 | {% block relbar1 %}{% endblock %} 61 | {% block relbar2 %}{% endblock %} 62 | {% block sidebarsourcelink %}{% endblock %} 63 | 64 | {%- block content %} 65 | {% include "navbar.html" %} 66 |
67 |
68 | {%- block sidebar1 %}{{ bsidebar() }}{% endblock %} 69 |
70 | {% block body %}{% endblock %} 71 |
72 | {% block sidebar2 %} {# possible location for sidebar #} {% endblock %} 73 |
74 |
75 | {%- endblock %} 76 | 77 | {%- block footer %} 78 |
79 |
80 |

81 | Back to top 82 | {% if theme_source_link_position == "footer" %} 83 |
84 | {% include "sourcelink.html" %} 85 | {% endif %} 86 |

87 | 88 |

89 | {%- if show_copyright %} 90 | {%- if hasdoc('copyright') %} 91 | {% trans path=pathto('copyright'), copyright=copyright|e %}© 92 | Copyright {{ copyright }}.{% endtrans %}
93 | {%- else %} 94 | {% trans copyright=copyright|e %}© Copyright {{ copyright }}.{% endtrans %}
95 | {%- endif %} 96 | {%- endif %} 97 | {%- if last_updated %} 98 | {% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %} 99 |
100 | {%- endif %} 101 | {%- if show_sphinx %} 102 | {% trans sphinx_version=sphinx_version|e %}Created using 103 | Sphinx {{ sphinx_version }}.{% endtrans %}
104 | {%- endif %} 105 |

106 |
107 |
108 | {%- endblock %} 109 | -------------------------------------------------------------------------------- /docs/theme/bootstrap/localtoc.html: -------------------------------------------------------------------------------- 1 | {{ toc }} 2 | -------------------------------------------------------------------------------- /docs/theme/bootstrap/navbar.html: -------------------------------------------------------------------------------- 1 | 51 | -------------------------------------------------------------------------------- /docs/theme/bootstrap/navbarsearchbox.html: -------------------------------------------------------------------------------- 1 | {%- if pagename != "search" %} 2 | 9 | {%- endif %} 10 | -------------------------------------------------------------------------------- /docs/theme/bootstrap/navbartoc.html: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /docs/theme/bootstrap/relations.html: -------------------------------------------------------------------------------- 1 | {# Switch to icon instead of text in `sm` view size for BS3 only. #} 2 | {%- if prev %} 3 |
  • 4 | 5 | {%- if theme_bootstrap_version == "3" -%}{%- endif -%} 6 | 7 | 8 |
  • 9 | {%- endif %} 10 | {%- if next %} 11 |
  • 12 | 13 | {%- if theme_bootstrap_version == "3" -%}{%- endif -%} 14 | 15 | 16 |
  • 17 | {%- endif %} 18 | -------------------------------------------------------------------------------- /docs/theme/bootstrap/search.html: -------------------------------------------------------------------------------- 1 | {# 2 | basic/search.html 3 | ~~~~~~~~~~~~~~~~~ 4 | 5 | Template for the search page. 6 | 7 | :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. 8 | :license: BSD, see LICENSE for details. 9 | #} 10 | {%- extends "layout.html" %} 11 | {% set title = _('Search') %} 12 | {% set script_files = script_files + ['_static/searchtools.js'] %} 13 | {% block extrahead %} 14 | 17 | {# this is used when loading the search index using $.ajax fails, 18 | such as on Chrome for documents on localhost #} 19 | 20 | {{ super() }} 21 | {% endblock %} 22 | {% block body %} 23 |

    {{ _('Search') }}

    24 |
    25 | 26 |

    27 | {% trans %}Please activate JavaScript to enable the search 28 | functionality.{% endtrans %} 29 |

    30 |
    31 |

    32 | {% trans %}From here you can search these documents. Enter your search 33 | words into the box below and click "search". Note that the search 34 | function will automatically search for all of the words. Pages 35 | containing fewer words won't appear in the result list.{% endtrans %} 36 |

    37 | 38 | {% if theme_bootstrap_version == "3" %} 39 |
    40 |
    41 | 42 |
    43 | 44 | 45 |
    46 | {% else %} 47 | 52 | {% endif %} 53 | 54 | {% if search_performed %} 55 |

    {{ _('Search Results') }}

    56 | {% if not search_results %} 57 |

    {{ _('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.') }}

    58 | {% endif %} 59 | {% endif %} 60 |
    61 | {% if search_results %} 62 |
      63 | {% for href, caption, context in search_results %} 64 |
    • {{ caption }} 65 |
      {{ context|e }}
      66 |
    • 67 | {% endfor %} 68 |
    69 | {% endif %} 70 |
    71 | {% endblock %} 72 | -------------------------------------------------------------------------------- /docs/theme/bootstrap/searchbox.html: -------------------------------------------------------------------------------- 1 | {%- if pagename != "search" %} 2 |
    3 |
    4 | 5 |
    6 | 7 | 8 |
    9 | {%- endif %} 10 | -------------------------------------------------------------------------------- /docs/theme/bootstrap/searchresults.html: -------------------------------------------------------------------------------- 1 | {# 2 | basic/searchresults.html 3 | ~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Template for the body of the search results page. 6 | 7 | :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. 8 | :license: BSD, see LICENSE for details. 9 | #} 10 |

    Search

    11 |

    12 | From here you can search these documents. Enter your search 13 | words into the box below and click "search". 14 |

    15 | 16 | 21 | 22 | {%- if search_performed %} 23 |

    Search Results

    24 | {%- if not search_results %} 25 |

    Your search did not match any results.

    26 | {%- endif %} 27 | {%- endif %} 28 |
    29 | {%- if search_results %} 30 | 37 | {%- endif %} 38 |
    39 | -------------------------------------------------------------------------------- /docs/theme/bootstrap/sourcelink.html: -------------------------------------------------------------------------------- 1 | {%- if show_source and has_source and sourcename %} 2 | 6 | {%- endif %} 7 | -------------------------------------------------------------------------------- /docs/theme/bootstrap/static/bootstrap-sphinx.css_t: -------------------------------------------------------------------------------- 1 | /* 2 | * bootstrap-sphinx.css 3 | * -------------------- 4 | * 5 | * Sphinx stylesheet -- Bootstrap theme 6 | */ 7 | 8 | /* we need this for bootswatch themes */ 9 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,700italic,900,900italic,400italic,300italic,300); 10 | 11 | /* DejaVu Sans Mono 2.3.4, generated with FontSquirrel, with TTFAutohint option */ 12 | @font-face { 13 | font-family: 'DejaVuSansMono'; 14 | src: url('fonts/dejavusansmono-webfont.woff2') format('woff2'), 15 | url('fonts/dejavusansmono-webfont.woff') format('woff'); 16 | font-weight: normal; 17 | font-style: normal; 18 | } 19 | 20 | @font-face { 21 | font-family: 'DejaVuSansMono'; 22 | src: url('fonts/dejavusansmono-oblique-webfont.woff2') format('woff2'), 23 | url('fonts/dejavusansmono-oblique-webfont.woff') format('woff'); 24 | font-weight: normal; 25 | font-style: oblique; 26 | } 27 | 28 | @font-face { 29 | font-family: 'DejaVuSansMono'; 30 | src: url('fonts/dejavusansmono-bold-webfont.woff2') format('woff2'), 31 | url('fonts/dejavusansmono-bold-webfont.woff') format('woff'); 32 | font-weight: bold; 33 | font-style: normal; 34 | } 35 | 36 | @font-face { 37 | font-family: 'DejaVuSansMono'; 38 | src: url('fonts/dejavusansmono-boldoblique-webfont.woff2') format('woff2'), 39 | url('fonts/dejavusansmono-boldoblique-webfont.woff') format('woff'); 40 | font-weight: bold; 41 | font-style: oblique; 42 | } 43 | 44 | .navbar-inverse .brand { 45 | color: #FFF; 46 | } 47 | 48 | .page-top { 49 | top: 0px; 50 | } 51 | 52 | /* @formatter:off */ 53 | {% if theme_navbar_fixed_top|tobool %} 54 | body { 55 | {% if theme_bootswatch_theme %} 56 | padding-top: 60px; 57 | {% else %} 58 | padding-top: 40px; 59 | {% endif %} 60 | } 61 | 62 | .page-top { 63 | {% if theme_bootswatch_theme %} 64 | top: 60px; 65 | {% else %} 66 | top: 40px; 67 | {% endif %} 68 | } 69 | 70 | .navbar-inner { 71 | padding-left: 12px !important; 72 | padding-right: 12px !important; 73 | } 74 | {% endif %} 75 | /* @formatter:on */ 76 | 77 | table { 78 | border: 0; 79 | } 80 | 81 | .highlighttable .code pre { 82 | font-size: 12px; 83 | } 84 | 85 | .highlighttable .linenos pre { 86 | word-break: normal; 87 | font-size: 12px; 88 | } 89 | 90 | a.footnote-reference { 91 | vertical-align: super; 92 | font-size: 75%; 93 | } 94 | 95 | table.footnote td.label { 96 | font-size: 100%; 97 | display: block; 98 | line-height: normal; 99 | background: inherit; 100 | } 101 | 102 | table.footnote { 103 | width: auto; 104 | margin-bottom: 0px; 105 | } 106 | 107 | table.field-list { 108 | width: auto; 109 | } 110 | 111 | .footer { 112 | width: 100%; 113 | border-top: 1px solid #CCC; 114 | padding-top: 10px; 115 | } 116 | 117 | .bs-sidenav form, .bs-sidenav #sourcelink { 118 | padding: 5px 20px; 119 | } 120 | 121 | /* The code below is based on the bootstrap website sidebar */ 122 | 123 | .bs-sidenav.affix { 124 | position: static; 125 | } 126 | 127 | /* First level of nav */ 128 | .bs-sidenav { 129 | margin-top: 30px; 130 | margin-bottom: 30px; 131 | padding-top: 10px; 132 | padding-bottom: 10px; 133 | text-shadow: 0 1px 0 #FFF; 134 | background-color: #F7F5FA; 135 | border-radius: 5px; 136 | } 137 | 138 | /* All levels of nav */ 139 | .bs-sidenav .nav > li > a { 140 | display: block; 141 | color: #716B7A; 142 | padding: 5px 20px; 143 | } 144 | 145 | .bs-sidenav .nav > li > a:hover, 146 | .bs-sidenav .nav > li > a:focus { 147 | text-decoration: none; 148 | background-color: #E5E3E9; 149 | border-right: 1px solid #DBD8E0; 150 | } 151 | 152 | .bs-sidenav .nav > .active > a, 153 | .bs-sidenav .nav > .active:hover > a, 154 | .bs-sidenav .nav > .active:focus > a { 155 | font-weight: bold; 156 | color: #563D7C; 157 | background-color: transparent; 158 | border-right: 1px solid #563D7C; 159 | } 160 | 161 | .bs-sidenav .nav .nav > li > a { 162 | padding-top: 3px; 163 | padding-bottom: 3px; 164 | padding-left: 30px; 165 | font-size: 90%; 166 | } 167 | 168 | .bs-sidenav .nav .nav .nav > li > a { 169 | padding-top: 3px; 170 | padding-bottom: 3px; 171 | padding-left: 40px; 172 | font-size: 90%; 173 | } 174 | 175 | .bs-sidenav .nav .nav .nav .nav > li > a { 176 | padding-top: 3px; 177 | padding-bottom: 3px; 178 | padding-left: 50px; 179 | font-size: 90%; 180 | } 181 | 182 | /* Show and affix the side nav when space allows it */ 183 | @media screen and (min-width: 992px) { 184 | .bs-sidenav .nav > .active > ul { 185 | display: block; 186 | } 187 | 188 | /* Widen the fixed sidenav */ 189 | .bs-sidenav.affix, 190 | .bs-sidenav.affix-bottom { 191 | width: 213px; 192 | } 193 | 194 | .bs-sidenav.affix { 195 | position: fixed; /* Undo the static from mobile first approach */ 196 | top: 80px; 197 | } 198 | 199 | .bs-sidenav.affix-bottom { 200 | position: absolute; /* Undo the static from mobile first approach */ 201 | } 202 | 203 | .bs-sidenav.affix-bottom .bs-sidenav, 204 | .bs-sidenav.affix .bs-sidenav { 205 | margin-top: 0; 206 | margin-bottom: 0; 207 | } 208 | } 209 | 210 | @media screen and (min-width: 1200px) { 211 | /* Widen the fixed sidenav again */ 212 | .bs-sidenav.affix-bottom, 213 | .bs-sidenav.affix { 214 | width: 263px; 215 | } 216 | } 217 | 218 | /* custom styles 219 | * ============= */ 220 | 221 | dt { 222 | font-weight: normal; 223 | } 224 | 225 | /* entire "class" definition */ 226 | dl.class > dt { 227 | font-size: 1.2em; 228 | } 229 | 230 | dl > dd { 231 | padding-left: 40px; 232 | } 233 | 234 | /* adjust spacing for field lists */ 235 | table.field-list tbody td, table.field-list tbody th { 236 | padding: 3px; 237 | } 238 | 239 | table.field-list tbody th { 240 | padding-right: 10px; 241 | } 242 | 243 | div.highlight pre { 244 | /* inherit the background set by the Pygments style */ 245 | /*background: inherit; (disable for now, default looks better) */ 246 | } 247 | 248 | {% if sphinx_versioninfo < (1, 3) %} 249 | /* Sphinx 1.2 250 | * ========== */ 251 | tt { 252 | font-family: DejaVuSansMono, monospace; 253 | } 254 | 255 | dt { 256 | /*font-family: DejaVuSansMono, monospace;*/ 257 | font-weight: normal; 258 | } 259 | 260 | /* code examples */ 261 | pre { 262 | font-family: DejaVuSansMono, monospace; 263 | font-size: 0.9em; 264 | color: #333; 265 | } 266 | 267 | div.highlight pre { 268 | /* inherit the background set by the Pygments style */ 269 | /*background: inherit; (disable for now, default looks better) */ 270 | } 271 | 272 | /* inline literals 273 | * --------------- */ 274 | 275 | /* `cite`, ``inline literal`` (red text) */ 276 | cite, code, tt.docutils.literal { 277 | font-family: DejaVuSansMono, monospace; 278 | font-size: 0.9em; 279 | background-color: #F9F2F4; 280 | border-radius: 4px; 281 | color: #C7254E; 282 | font-weight: normal; 283 | padding: 0 4px; 284 | } 285 | 286 | /* make links in literals the same color as links */ 287 | a tt.docutils.literal { 288 | color: inherit; 289 | } 290 | 291 | {% else %} 292 | /* Sphinx 1.3 293 | * ========== */ 294 | 295 | /* code and preformatted text */ 296 | cite, code, pre { 297 | font-family: DejaVuSansMono, monospace; 298 | font-weight: normal; 299 | font-size: 0.9em; 300 | color: #2C3E50; 301 | } 302 | 303 | /* class/method definitions */ 304 | code.descclassname, code.descname { 305 | padding: 0; 306 | } 307 | 308 | /* module path (in class definition) */ 309 | code.descclassname { 310 | font-size: 1em; 311 | } 312 | 313 | /* definition name */ 314 | code.descname { 315 | font-size: 1.2em; 316 | } 317 | 318 | /* `cite`, ``inline literal`` (red text) */ 319 | cite, code.docutils.literal { 320 | background-color: #F9F2F4; 321 | border-radius: 4px; 322 | color: #C7254E; 323 | font-weight: normal; 324 | padding: 0 4px; 325 | } 326 | 327 | /* make links in literals the same color as links */ 328 | a code.docutils.literal { 329 | color: inherit; 330 | } 331 | 332 | {% endif %} 333 | 334 | /* styles for custom roles 335 | * ----------------------- */ 336 | 337 | /* :annotation:`@property` */ 338 | .annotation { 339 | border-radius: 4px; 340 | padding: 4px; 341 | background-color: #DED; 342 | } 343 | 344 | .namedtuple { 345 | background-color: #F9F2F4; 346 | border-radius: 4px; 347 | color: #2C3E50; 348 | font-family: DejaVuSansMono, monospace; 349 | font-size: 0.9em; 350 | font-weight: normal; 351 | padding: 0 4px; 352 | } 353 | -------------------------------------------------------------------------------- /docs/theme/bootstrap/static/bootstrap-sphinx.js_t: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | /** 3 | * Patch TOC list. 4 | * 5 | * Will mutate the underlying span to have a correct ul for nav. 6 | * 7 | * @param $span: Span containing nested UL's to mutate. 8 | * @param minLevel: Starting level for nested lists. (1: global, 2: local). 9 | */ 10 | var patchToc = function ($ul, minLevel) { 11 | var findA, 12 | patchTables, 13 | $localLi; 14 | 15 | // Find all a "internal" tags, traversing recursively. 16 | findA = function ($elem, level) { 17 | level = level || 0; 18 | var $items = $elem.find("> li > a.internal, > ul, > li > ul"); 19 | 20 | // Iterate everything in order. 21 | $items.each(function (index, item) { 22 | var $item = $(item), 23 | tag = item.tagName.toLowerCase(), 24 | $childrenLi = $item.children('li'), 25 | $parentLi = $($item.parent('li'), $item.parent().parent('li')); 26 | 27 | // Add dropdowns if more children and above minimum level. 28 | if (tag === 'ul' && level >= minLevel && $childrenLi.length > 0) { 29 | $parentLi 30 | .addClass('dropdown-submenu') 31 | .children('a').first().attr('tabindex', -1); 32 | 33 | $item.addClass('dropdown-menu'); 34 | } 35 | 36 | findA($item, level + 1); 37 | }); 38 | }; 39 | 40 | findA($ul); 41 | }; 42 | 43 | /** 44 | * Patch all tables to remove ``docutils`` class and add Bootstrap base 45 | * ``table`` class. 46 | */ 47 | patchTables = function () { 48 | $("table.docutils") 49 | .removeClass("docutils") 50 | .addClass("table") 51 | .attr("border", 0); 52 | }; 53 | 54 | $(window).load(function () { 55 | /* 56 | * Scroll the window to avoid the topnav bar 57 | * https://github.com/twbs/bootstrap/issues/1768 58 | */ 59 | if ($("#navbar.navbar-fixed-top").length > 0) { 60 | var navHeight = $("#navbar").height(), 61 | shiftWindow = function() { scrollBy(0, -navHeight - 10); }; 62 | 63 | if (location.hash) { 64 | setTimeout(shiftWindow, 1); 65 | } 66 | 67 | window.addEventListener("hashchange", shiftWindow); 68 | } 69 | }); 70 | 71 | $(document).ready(function () { 72 | // Add styling, structure to TOC's. 73 | $(".dropdown-menu").each(function () { 74 | $(this).find("ul").each(function (index, item){ 75 | var $item = $(item); 76 | $item.addClass('unstyled'); 77 | }); 78 | }); 79 | 80 | // Global TOC. 81 | if ($("ul.globaltoc li").length) { 82 | patchToc($("ul.globaltoc"), 1); 83 | } else { 84 | // Remove Global TOC. 85 | $(".globaltoc-container").remove(); 86 | } 87 | 88 | // Local TOC. 89 | $(".bs-sidenav ul").addClass("nav nav-list"); 90 | $(".bs-sidenav > ul > li > a").addClass("nav-header"); 91 | 92 | {% if theme_navbar_fixed_top|tobool and theme_bootstrap_version == "3" %} 93 | // back to top 94 | setTimeout(function () { 95 | var $sideBar = $('.bs-sidenav'); 96 | 97 | $sideBar.affix({ 98 | offset: { 99 | top: function () { 100 | var offsetTop = $sideBar.offset().top; 101 | var sideBarMargin = parseInt($sideBar.children(0).css('margin-top'), 10); 102 | var navOuterHeight = $('#navbar').height(); 103 | 104 | return (this.top = offsetTop - navOuterHeight - sideBarMargin); 105 | } 106 | , bottom: function () { 107 | // add 25 because the footer height doesn't seem to be enough 108 | return (this.bottom = $('.footer').outerHeight(true) + 25); 109 | } 110 | } 111 | }); 112 | }, 100); 113 | {% endif %} 114 | 115 | // Local TOC. 116 | patchToc($("ul.localtoc"), 2); 117 | 118 | // Mutate sub-lists (for bs-2.3.0). 119 | $(".dropdown-menu ul").not(".dropdown-menu").each(function () { 120 | var $ul = $(this), 121 | $parent = $ul.parent(), 122 | tag = $parent[0].tagName.toLowerCase(), 123 | $kids = $ul.children().detach(); 124 | 125 | // Replace list with items if submenu header. 126 | if (tag === "ul") { 127 | $ul.replaceWith($kids); 128 | } else if (tag === "li") { 129 | // Insert into previous list. 130 | $parent.after($kids); 131 | $ul.remove(); 132 | } 133 | }); 134 | 135 | // Add divider in page TOC. 136 | $localLi = $("ul.localtoc li"); 137 | if ($localLi.length > 2) { 138 | $localLi.first().after('
  • '); 139 | } 140 | 141 | // Manually add dropdown. 142 | // Appears unnecessary as of: 143 | // https://github.com/ryan-roemer/sphinx-bootstrap-theme/pull/90 144 | // Remove next time around... 145 | // a.dropdown-toggle class needed in globaltoc.html 146 | //$('.dropdown-toggle').dropdown(); 147 | 148 | // Patch tables. 149 | patchTables(); 150 | 151 | // Add Note, Warning styles. (BS v2,3 compatible). 152 | $('.admonition').addClass('alert alert-info') 153 | .filter('.warning, .caution') 154 | .removeClass('alert-info') 155 | .addClass('alert-warning').end() 156 | .filter('.error, .danger') 157 | .removeClass('alert-info') 158 | .addClass('alert-danger alert-error').end(); 159 | 160 | // Inline code styles to Bootstrap style. 161 | $('tt.docutils.literal').not(".xref").each(function (i, e) { 162 | // ignore references 163 | if (!$(e).parent().hasClass("reference")) { 164 | $(e).replaceWith(function () { 165 | return $("").html($(this).html()); 166 | }); 167 | }}); 168 | 169 | // Update sourcelink to remove outerdiv (fixes appearance in navbar). 170 | var $srcLink = $(".nav #sourcelink"); 171 | $srcLink.parent().html($srcLink.html()); 172 | }); 173 | }(window.$jqTheme || window.jQuery)); 174 | -------------------------------------------------------------------------------- /docs/theme/bootstrap/static/bootstrap_dist/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veegee/guv/d7bac2ca6a73cc2059969af08223b82f3e187922/docs/theme/bootstrap/static/bootstrap_dist/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /docs/theme/bootstrap/static/bootstrap_dist/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veegee/guv/d7bac2ca6a73cc2059969af08223b82f3e187922/docs/theme/bootstrap/static/bootstrap_dist/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /docs/theme/bootstrap/static/bootstrap_dist/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veegee/guv/d7bac2ca6a73cc2059969af08223b82f3e187922/docs/theme/bootstrap/static/bootstrap_dist/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /docs/theme/bootstrap/static/bootstrap_dist/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /docs/theme/bootstrap/static/bootswatch/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veegee/guv/d7bac2ca6a73cc2059969af08223b82f3e187922/docs/theme/bootstrap/static/bootswatch/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /docs/theme/bootstrap/static/bootswatch/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veegee/guv/d7bac2ca6a73cc2059969af08223b82f3e187922/docs/theme/bootstrap/static/bootswatch/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /docs/theme/bootstrap/static/bootswatch/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veegee/guv/d7bac2ca6a73cc2059969af08223b82f3e187922/docs/theme/bootstrap/static/bootswatch/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /docs/theme/bootstrap/static/fonts/dejavusansmono-bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veegee/guv/d7bac2ca6a73cc2059969af08223b82f3e187922/docs/theme/bootstrap/static/fonts/dejavusansmono-bold-webfont.woff -------------------------------------------------------------------------------- /docs/theme/bootstrap/static/fonts/dejavusansmono-bold-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veegee/guv/d7bac2ca6a73cc2059969af08223b82f3e187922/docs/theme/bootstrap/static/fonts/dejavusansmono-bold-webfont.woff2 -------------------------------------------------------------------------------- /docs/theme/bootstrap/static/fonts/dejavusansmono-boldoblique-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veegee/guv/d7bac2ca6a73cc2059969af08223b82f3e187922/docs/theme/bootstrap/static/fonts/dejavusansmono-boldoblique-webfont.woff -------------------------------------------------------------------------------- /docs/theme/bootstrap/static/fonts/dejavusansmono-boldoblique-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veegee/guv/d7bac2ca6a73cc2059969af08223b82f3e187922/docs/theme/bootstrap/static/fonts/dejavusansmono-boldoblique-webfont.woff2 -------------------------------------------------------------------------------- /docs/theme/bootstrap/static/fonts/dejavusansmono-oblique-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veegee/guv/d7bac2ca6a73cc2059969af08223b82f3e187922/docs/theme/bootstrap/static/fonts/dejavusansmono-oblique-webfont.woff -------------------------------------------------------------------------------- /docs/theme/bootstrap/static/fonts/dejavusansmono-oblique-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veegee/guv/d7bac2ca6a73cc2059969af08223b82f3e187922/docs/theme/bootstrap/static/fonts/dejavusansmono-oblique-webfont.woff2 -------------------------------------------------------------------------------- /docs/theme/bootstrap/static/fonts/dejavusansmono-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veegee/guv/d7bac2ca6a73cc2059969af08223b82f3e187922/docs/theme/bootstrap/static/fonts/dejavusansmono-webfont.woff -------------------------------------------------------------------------------- /docs/theme/bootstrap/static/fonts/dejavusansmono-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veegee/guv/d7bac2ca6a73cc2059969af08223b82f3e187922/docs/theme/bootstrap/static/fonts/dejavusansmono-webfont.woff2 -------------------------------------------------------------------------------- /docs/theme/bootstrap/static/js/jquery-fix.js: -------------------------------------------------------------------------------- 1 | // No Conflict in later (our) version of jQuery 2 | window.$jqTheme = jQuery.noConflict(true); -------------------------------------------------------------------------------- /docs/theme/bootstrap/theme.conf: -------------------------------------------------------------------------------- 1 | # Bootstrap Theme 2 | [theme] 3 | inherit = basic 4 | stylesheet = basic.css 5 | pygments_style = sphinx 6 | 7 | # Configurable options. 8 | [options] 9 | # Navigation bar title. (Default: ``project`` value) 10 | navbar_title = 11 | 12 | # Tab name for entire site. (Default: "Site") 13 | navbar_site_name = Site 14 | 15 | # A list of tuples containting pages to link to. The value should be 16 | # in the form [(name, page), ..] 17 | navbar_links = 18 | 19 | # Render the next and previous page links in navbar. (Default: true) 20 | navbar_sidebarrel = true 21 | 22 | # Render the current pages TOC in the navbar. (Default: true) 23 | navbar_pagenav = true 24 | 25 | # Tab name for the current pages TOC. (Default: "Page") 26 | navbar_pagenav_name = Page 27 | 28 | # Global TOC depth for "site" navbar tab. (Default: 1) 29 | # Switching to -1 shows all levels. 30 | globaltoc_depth = 1 31 | 32 | # Include hidden TOCs in Site navbar? 33 | # 34 | # Note: If this is "false", you cannot have mixed ``:hidden:`` and 35 | # non-hidden ``toctree`` directives in the same page, or else the build 36 | # will break. 37 | # 38 | # Values: "true" (default) or "false" 39 | globaltoc_includehidden = true 40 | 41 | # HTML navbar class (Default: "navbar") to attach to
    element. 42 | # For black navbar, do "navbar navbar-inverse" 43 | navbar_class = navbar 44 | 45 | # Fix navigation bar to top of page? 46 | # Values: "true" (default) or "false" 47 | navbar_fixed_top = true 48 | 49 | # Location of link to source. 50 | # Options are "nav" (default), "footer" or anything else to exclude. 51 | source_link_position = nav 52 | 53 | # Bootswatch (http://bootswatch.com/) theme. 54 | # 55 | # Options are nothing with "" (default) or the name of a valid theme such as 56 | # "amelia" or "cosmo". 57 | bootswatch_theme = "" 58 | -------------------------------------------------------------------------------- /docs/theme/pygments_solarized_light.py: -------------------------------------------------------------------------------- 1 | from pygments.style import Style 2 | from pygments.token import (Keyword, Name, Comment, String, Error, Text, Number, Operator, Generic, 3 | Whitespace, Other, Literal, Punctuation) 4 | 5 | 6 | class LightStyle(Style): 7 | """ 8 | The Solarized Light style 9 | """ 10 | default_style = "" 11 | 12 | base03 = '#002b36' 13 | base02 = '#073642' 14 | base01 = '#586e75' # emphasized content 15 | base00 = '#657b83' # primary content 16 | base0 = '#839496' 17 | base1 = '#93a1a1' # secondary content 18 | base2 = '#eee8d5' # background highlights 19 | base3 = '#fdf6e3' # background 20 | yellow = '#b58900' 21 | orange = '#cb4b16' 22 | red = '#dc322f' 23 | magenta = '#d33682' 24 | violet = '#6c71c4' 25 | blue = '#268bd2' 26 | cyan = '#2aa198' 27 | green = '#859900' 28 | 29 | background_color = base3 30 | pri = base00 31 | emph = base01 32 | sec = base1 33 | 34 | comment = 'italic ' + sec 35 | 36 | # @formatter:off 37 | styles = { 38 | Text: pri, # class: '' 39 | Whitespace: base3, # class: 'w' 40 | Error: red, # class: 'err' 41 | Other: pri, # class: 'x' 42 | 43 | Comment: comment, # class: 'c' 44 | Comment.Multiline: comment, # class: 'cm' 45 | Comment.Preproc: comment, # class: 'cp' 46 | Comment.Single: comment, # class: 'c1' 47 | Comment.Special: comment, # class: 'cs' 48 | 49 | Keyword: green, # class: 'k' 50 | Keyword.Constant: green, # class: 'kc' 51 | Keyword.Declaration: green, # class: 'kd' 52 | Keyword.Namespace: orange, # class: 'kn' 53 | Keyword.Pseudo: orange, # class: 'kp' 54 | Keyword.Reserved: green, # class: 'kr' 55 | Keyword.Type: green, # class: 'kt' 56 | 57 | Operator: pri, # class: 'o' 58 | Operator.Word: green, # class: 'ow' 59 | 60 | Name: emph, # class: 'n' 61 | Name.Attribute: pri, # class: 'na' 62 | Name.Builtin: blue, # class: 'nb' 63 | Name.Builtin.Pseudo: blue, # class: 'bp' 64 | Name.Class: blue, # class: 'nc' 65 | Name.Constant: yellow, # class: 'no' 66 | Name.Decorator: orange, # class: 'nd' 67 | Name.Entity: orange, # class: 'ni' 68 | Name.Exception: orange, # class: 'ne' 69 | Name.Function: blue, # class: 'nf' 70 | Name.Property: blue, # class: 'py' 71 | Name.Label: pri, # class: 'nc' 72 | Name.Namespace: pri, # class: 'nn' 73 | Name.Other: pri, # class: 'nx' 74 | Name.Tag: green, # class: 'nt' 75 | Name.Variable: orange, # class: 'nv' 76 | Name.Variable.Class: blue, # class: 'vc' 77 | Name.Variable.Global: blue, # class: 'vg' 78 | Name.Variable.Instance: blue, # class: 'vi' 79 | 80 | Number: cyan, # class: 'm' 81 | Number.Float: cyan, # class: 'mf' 82 | Number.Hex: cyan, # class: 'mh' 83 | Number.Integer: cyan, # class: 'mi' 84 | Number.Integer.Long: cyan, # class: 'il' 85 | Number.Oct: cyan, # class: 'mo' 86 | 87 | Literal: pri, # class: 'l' 88 | Literal.Date: pri, # class: 'ld' 89 | 90 | Punctuation: pri, # class: 'p' 91 | 92 | String: cyan, # class: 's' 93 | String.Backtick: cyan, # class: 'sb' 94 | String.Char: cyan, # class: 'sc' 95 | String.Doc: cyan, # class: 'sd' 96 | String.Double: cyan, # class: 's2' 97 | String.Escape: orange, # class: 'se' 98 | String.Heredoc: cyan, # class: 'sh' 99 | String.Interpol: orange, # class: 'si' 100 | String.Other: cyan, # class: 'sx' 101 | String.Regex: cyan, # class: 'sr' 102 | String.Single: cyan, # class: 's1' 103 | String.Symbol: cyan, # class: 'ss' 104 | 105 | Generic: pri, # class: 'g' 106 | Generic.Deleted: pri, # class: 'gd' 107 | Generic.Emph: pri, # class: 'ge' 108 | Generic.Error: pri, # class: 'gr' 109 | Generic.Heading: pri, # class: 'gh' 110 | Generic.Inserted: pri, # class: 'gi' 111 | Generic.Output: pri, # class: 'go' 112 | Generic.Prompt: pri, # class: 'gp' 113 | Generic.Strong: pri, # class: 'gs' 114 | Generic.Subheading: pri, # class: 'gu' 115 | Generic.Traceback: pri, # class: 'gt' 116 | } 117 | # @formatter:on 118 | 119 | -------------------------------------------------------------------------------- /examples/cassandra_db.py: -------------------------------------------------------------------------------- 1 | """Cassandra database example 2 | 3 | This example demonstrates connecting to a Cassandra database and executing a query. Note that 4 | using the database driver remains exactly the same. The only difference is that we're 5 | monkey-patching everything (including the Cassandra driver), making it guv-friendly. 6 | 7 | Adjust this example to your database address, keyspace, and query that you would like to run. 8 | """ 9 | import guv 10 | guv.monkey_patch() 11 | 12 | import logging 13 | import logger 14 | from cassandra import cluster 15 | 16 | logger.configure() 17 | log = logging.getLogger() 18 | 19 | 20 | def do_query(session): 21 | rows = session.execute('SELECT * FROM numbers') 22 | 23 | for row in rows: 24 | log.info(row) 25 | 26 | 27 | def main(): 28 | nodes = ['192.168.20.2'] 29 | 30 | c = cluster.Cluster(nodes, port=9042) 31 | session = c.connect('test') 32 | log.info('Execute commands') 33 | 34 | pool = guv.GreenPool() 35 | for i in range(5): 36 | pool.spawn(do_query, session) 37 | 38 | pool.waitall() 39 | c.shutdown() 40 | 41 | 42 | if __name__ == '__main__': 43 | main() 44 | -------------------------------------------------------------------------------- /examples/crawler.py: -------------------------------------------------------------------------------- 1 | import guv 2 | guv.monkey_patch() 3 | 4 | import requests 5 | 6 | 7 | def get_url(url): 8 | print('get_url({})'.format(url)) 9 | return requests.get(url) 10 | 11 | 12 | def main(): 13 | urls = ['http://reddit.com'] * 10 14 | urls += ['https://twitter.com'] * 10 15 | 16 | pool = guv.GreenPool() 17 | 18 | results = pool.starmap(get_url, zip(urls)) 19 | for i, resp in enumerate(results): 20 | print('{}: done, length: {}'.format(i, len(resp.text))) 21 | 22 | 23 | if __name__ == '__main__': 24 | main() 25 | -------------------------------------------------------------------------------- /examples/guv: -------------------------------------------------------------------------------- 1 | ../guv -------------------------------------------------------------------------------- /examples/guv_simple_server.py: -------------------------------------------------------------------------------- 1 | """Simple low-level network server 2 | 3 | This module demonstrates how to use guv to create fast network servers. In addition, it can be 4 | used to serve valid HTTP (as far as ``wrk`` is concerned) to benchmark concurrency and requests/sec. 5 | 6 | Three basic client handlers are provided: 7 | 8 | - :func:`handle_http_10` acts as an HTTP 1.0 server which sends a static message and closes the 9 | connection (HTTP header ``Connection: close``, which is default for HTTP 1.0). 10 | - :func:`handle_http_11` acts as an HTTP 1.1 server which sends a static message, but keeps the 11 | connection alive (HTTP header ``Connection: keep-alive``, which is default for HTTP 1.1). 12 | - :func:`handle_http` is a slightly more complex client handler which actually reads the client's 13 | request and decides to either close or keep-alive the connection based on the HTTP version and 14 | what the client wants. If the connection is to be kept alive, this handler cooperatively yields 15 | control to other greenlets after every request, which significantly improves request/response 16 | latency (as reported by wrk). 17 | """ 18 | import guv 19 | guv.monkey_patch() 20 | 21 | import guv.server 22 | import guv.hubs 23 | import guv.greenio 24 | from guv import gyield 25 | from guv.support import PYPY 26 | 27 | import logging 28 | 29 | import logger 30 | 31 | logger.configure() 32 | log = logging.getLogger() 33 | 34 | if PYPY: 35 | from http_parser.pyparser import HttpParser 36 | 37 | log.debug('Using pure-Python HTTP parser') 38 | USING_PYPARSER = True 39 | else: 40 | from http_parser.parser import HttpParser 41 | 42 | log.debug('Using fast C HTTP parser') 43 | USING_PYPARSER = False 44 | 45 | 46 | def create_response(body, headers): 47 | """Create a simple HTTP response 48 | 49 | :type body: str 50 | :type headers: dict[str, str] 51 | :rtype: bytes 52 | """ 53 | final_headers = { 54 | 'Connection': 'keep-alive', 55 | 'Content-Type': 'text/plain; charset=utf-8', 56 | 'Content-Encoding': 'UTF-8' 57 | } 58 | 59 | final_headers.update(headers) 60 | 61 | lines = ['HTTP/1.1 200 OK'] 62 | lines.extend(['%s: %s' % (k, v) for k, v in final_headers.items()]) 63 | lines.append('Content-Length: %s' % len(body)) 64 | 65 | resp = ('\r\n'.join(lines)).encode('latin-1') 66 | resp += ('\r\n\r\n' + body).encode(final_headers['Content-Encoding']) 67 | 68 | return resp 69 | 70 | 71 | def handle_http_10(sock, addr): 72 | """Very minimal client handler for HTTP 1.0 (Connection: close) 73 | """ 74 | data = sock.recv(4096) 75 | if not data: 76 | return 77 | resp = create_response('Hello, world!', {'Connection': 'close'}) 78 | sock.sendall(resp) 79 | 80 | sock.close() 81 | 82 | 83 | def handle_http_11(sock, addr): 84 | """Very minimal client handler for HTTP 1.1 (Connection: keep-alive) 85 | """ 86 | while True: 87 | data = sock.recv(4096) 88 | if not data: 89 | break 90 | resp = create_response('Hello, world!', {'Connection': 'keep-alive'}) 91 | sock.sendall(resp) 92 | 93 | sock.close() 94 | 95 | 96 | def handle_http(sock, addr): 97 | """A more complicated handler which detects HTTP headers 98 | """ 99 | 100 | def recv_request(p): 101 | while True: 102 | data = sock.recv(8192) 103 | 104 | if not data: 105 | return False 106 | 107 | nb = len(data) 108 | nparsed = p.execute(data, nb) 109 | assert nparsed == nb 110 | 111 | if USING_PYPARSER and p.is_headers_complete(): 112 | h = p.get_headers() 113 | if not (h.get('content-length') or h.get('transfer-length')): 114 | # pass length=0 to signal end of body 115 | # TODO: pyparser requires this, but not the C parser for some reason 116 | p.execute(data, 0) 117 | return True 118 | 119 | if p.is_message_complete(): 120 | return True 121 | 122 | # main request loop 123 | while True: 124 | p = HttpParser() 125 | 126 | if not recv_request(p): 127 | break 128 | 129 | h = p.get_headers() 130 | ka = p.should_keep_alive() 131 | h_connection = 'keep-alive' if ka else 'close' 132 | 133 | resp = create_response('Hello, world!', {'Connection': h_connection}) 134 | sock.sendall(resp) 135 | 136 | if not ka: 137 | break 138 | else: 139 | # we should keep-alive, but yield to drastically improve overall request/response 140 | # latency 141 | gyield() 142 | 143 | sock.close() 144 | 145 | 146 | handle = handle_http 147 | 148 | 149 | def main(): 150 | try: 151 | log.debug('Start') 152 | server_sock = guv.listen(('0.0.0.0', 8001)) 153 | server = guv.server.Server(server_sock, handle, None, None) 154 | server.start() 155 | except (SystemExit, KeyboardInterrupt): 156 | log.debug('Bye!') 157 | 158 | 159 | if __name__ == '__main__': 160 | main() 161 | -------------------------------------------------------------------------------- /examples/logger.py: -------------------------------------------------------------------------------- 1 | """Logger configuration module 2 | 3 | This looks nicest on a terminal configured with "solarized" colours. 4 | """ 5 | import logging 6 | import sys 7 | 8 | configured = False 9 | 10 | 11 | class ColouredFormatter(logging.Formatter): 12 | RESET = '\x1B[0m' # INFO 13 | RED = '\x1B[31m' # ERROR, CRITICAL, FATAL 14 | GREEN = '\x1B[32m' # INFO 15 | YELLOW = '\x1B[33m' # WARNING 16 | BLUE = '\x1B[34m' # INFO 17 | MAGENTA = '\x1B[35m' # INFO 18 | CYAN = '\x1B[36m' # INFO 19 | WHITE = '\x1B[37m' # INFO 20 | BRGREEN = '\x1B[01;32m' # DEBUG (grey in solarized for terminals) 21 | 22 | def format(self, record, colour=False): 23 | message = super().format(record) 24 | 25 | if not colour: 26 | return message 27 | 28 | level_no = record.levelno 29 | if level_no >= logging.CRITICAL: 30 | colour = self.RED 31 | elif level_no >= logging.ERROR: 32 | colour = self.RED 33 | elif level_no >= logging.WARNING: 34 | colour = self.YELLOW 35 | elif level_no >= 29: 36 | colour = self.MAGENTA 37 | elif level_no >= 28: 38 | colour = self.CYAN 39 | elif level_no >= 27: 40 | colour = self.GREEN 41 | elif level_no >= 26: 42 | colour = self.BLUE 43 | elif level_no >= 25: 44 | colour = self.WHITE 45 | elif level_no >= logging.INFO: 46 | colour = self.RESET 47 | elif level_no >= logging.DEBUG: 48 | colour = self.BRGREEN 49 | else: 50 | colour = self.RESET 51 | 52 | message = colour + message + self.RESET 53 | 54 | return message 55 | 56 | 57 | class ColouredHandler(logging.StreamHandler): 58 | def __init__(self, stream=sys.stdout): 59 | super().__init__(stream) 60 | 61 | def format(self, record, colour=False): 62 | if not isinstance(self.formatter, ColouredFormatter): 63 | self.formatter = ColouredFormatter() 64 | 65 | return self.formatter.format(record, colour) 66 | 67 | def emit(self, record): 68 | stream = self.stream 69 | try: 70 | if hasattr(sys, 'called_from_test') and sys.called_from_test: 71 | msg = self.format(record, True) 72 | else: 73 | msg = self.format(record, stream.isatty()) 74 | stream.write(msg) 75 | stream.write(self.terminator) 76 | self.flush() 77 | except Exception: 78 | self.handleError(record) 79 | 80 | 81 | def configure(): 82 | if configured: 83 | return 84 | 85 | logging.addLevelName(25, 'INFO') 86 | logging.addLevelName(26, 'INFO') 87 | logging.addLevelName(27, 'INFO') 88 | logging.addLevelName(28, 'INFO') 89 | logging.addLevelName(29, 'INFO') 90 | 91 | h = ColouredHandler() 92 | h.formatter = ColouredFormatter('{asctime} {name} {levelname} {message}', 93 | '%Y-%m-%d %H:%M:%S', '{') 94 | if sys.version_info >= (3, 3): 95 | logging.basicConfig(level=logging.DEBUG, handlers=[h]) 96 | else: 97 | # Python 3.2 doesn't support the `handlers` parameter for `logging.basicConfig()` 98 | logging.basicConfig(level=logging.DEBUG) 99 | logging.root.handlers[0] = h 100 | 101 | logging.getLogger('requests').setLevel(logging.WARNING) 102 | 103 | 104 | configure() 105 | -------------------------------------------------------------------------------- /examples/pyuv_cffi: -------------------------------------------------------------------------------- 1 | ../pyuv_cffi -------------------------------------------------------------------------------- /examples/pyuv_cffi_example.py: -------------------------------------------------------------------------------- 1 | """A simple example demonstrating basic usage of pyuv_cffi 2 | 3 | This example creates a timer handle and a signal handle, then starts the loop. The timer callback is 4 | run after 1 second, and repeating every 1 second thereafter. The signal handle registers a listener 5 | for the INT signal and allows us to exit the loop by pressing ctrl-c. 6 | """ 7 | import signal 8 | 9 | from pyuv_cffi import Loop, Timer, Signal 10 | 11 | 12 | def sig_cb(sig_h, sig_num): 13 | print('\nsig_cb({}, {})'.format(sig_h, sig_num)) 14 | sig_h.stop() 15 | sig_h.loop.stop() 16 | 17 | 18 | def timer_cb(timer_h): 19 | print('timer_cb({})'.format(timer_h)) 20 | 21 | 22 | def run(): 23 | loop = Loop() 24 | 25 | timer_h = Timer(loop) 26 | timer_h.start(timer_cb, 1, 1) 27 | 28 | sig_h = Signal(loop) 29 | sig_h.start(sig_cb, signal.SIGINT) 30 | 31 | status = loop.run() 32 | 33 | timer_h.close() # we must stop and free any other handles before freeing the loop 34 | print('loop.run() -> ', status) 35 | 36 | # all handles in pyuv_cffi (including the loop) are automatically freed when they go out of 37 | # scope 38 | 39 | 40 | def main(): 41 | run() 42 | 43 | 44 | if __name__ == '__main__': 45 | main() 46 | -------------------------------------------------------------------------------- /examples/threads.py: -------------------------------------------------------------------------------- 1 | """Threading example 2 | 3 | This example demonstrates the use of the :mod:`threading` module to create thread objects 4 | normally. After monkey-patching, however, these Thread objects are actually wrappers around 5 | GreenThreads (not POSIX threads), so spawning them and switching to them is extremely efficient. 6 | 7 | Remember that calling Thread.start() only schedules the Thread to be started on the beginning of 8 | the next event loop iteration, and returns immediately in the calling Thread. 9 | """ 10 | import guv 11 | guv.monkey_patch() 12 | 13 | from guv import gyield, patcher 14 | import threading 15 | import greenlet 16 | 17 | threading_orig = patcher.original('threading') 18 | 19 | greenlet_ids = {} 20 | 21 | 22 | def debug(i): 23 | print('current thread: {}'.format(threading.current_thread())) 24 | print('{} greenlet_ids: {}'.format(i, greenlet_ids)) 25 | 26 | 27 | def f(): 28 | greenlet_ids[1] = greenlet.getcurrent() 29 | debug(2) 30 | 31 | print('t: 1') 32 | gyield() 33 | print('t: 2') 34 | gyield() 35 | print('t: 3') 36 | 37 | 38 | def main(): 39 | greenlet_ids[0] = greenlet.getcurrent() 40 | debug(1) 41 | 42 | t = threading.Thread(target=f) 43 | t.start() 44 | gyield() # `t` doesn't actually run until we yield 45 | debug(3) 46 | 47 | print('m: 1') 48 | gyield() 49 | print('m: 2') 50 | gyield() 51 | print('m: 3') 52 | 53 | t.join() 54 | 55 | 56 | if __name__ == '__main__': 57 | main() 58 | -------------------------------------------------------------------------------- /examples/wsgi_app.py: -------------------------------------------------------------------------------- 1 | import guv 2 | guv.monkey_patch() 3 | 4 | import guv.wsgi 5 | import logger 6 | 7 | logger.configure() 8 | 9 | 10 | def app(environ, start_response): 11 | """ 12 | This is very basic WSGI app useful for testing the performance of guv and guv.wsgi without 13 | the overhead of a framework such as Flask. However, it can just as easily be any other WSGI app 14 | callable object, such as a Flask or Bottle app. 15 | """ 16 | status = '200 OK' 17 | output = [b'Hello World!'] 18 | content_length = str(len(b''.join(output))) 19 | 20 | response_headers = [('Content-type', 'text/plain'), 21 | ('Content-Length', content_length)] 22 | 23 | start_response(status, response_headers) 24 | 25 | return output 26 | 27 | 28 | if __name__ == '__main__': 29 | server_sock = guv.listen(('0.0.0.0', 8001)) 30 | guv.wsgi.serve(server_sock, app) 31 | -------------------------------------------------------------------------------- /guv/__init__.py: -------------------------------------------------------------------------------- 1 | version_info = (0, 35, 2) 2 | __version__ = '.'.join(map(str, version_info)) 3 | 4 | try: 5 | import pyuv_cffi # only to compile the shared library before monkey-patching 6 | 7 | from . import greenpool 8 | from . import queue 9 | from .hubs.switch import gyield, trampoline 10 | from .greenthread import sleep, spawn, spawn_n, spawn_after, kill 11 | from .greenpool import GreenPool, GreenPile 12 | from .timeout import Timeout, with_timeout 13 | from .patcher import import_patched, monkey_patch 14 | from .server import serve, listen, connect, StopServe, wrap_ssl 15 | 16 | try: 17 | from .support.gunicorn_worker import GuvWorker 18 | except ImportError: 19 | pass 20 | 21 | 22 | except ImportError as e: 23 | # This is to make Debian packaging easier, it ignores import errors of greenlet so that the 24 | # packager can still at least access the version. Also this makes easy_install a little quieter 25 | if 'greenlet' not in str(e): 26 | # any other exception should be printed 27 | import traceback 28 | 29 | traceback.print_exc() 30 | -------------------------------------------------------------------------------- /guv/compat.py: -------------------------------------------------------------------------------- 1 | """ 2 | Monkey-patch the standard library to backport certain features to older Python versions 3 | """ 4 | from . import patcher 5 | 6 | 7 | def patch_time(): 8 | time = patcher.original('time') 9 | if not hasattr(time, 'monotonic'): 10 | time.monotonic = time.time 11 | 12 | import time as new_time 13 | 14 | if not hasattr(new_time, 'monotonic'): 15 | time.monotonic = time.time 16 | 17 | 18 | def patch(): 19 | patch_time() 20 | -------------------------------------------------------------------------------- /guv/const.py: -------------------------------------------------------------------------------- 1 | #: This is equivalent to ``UV_READABLE`` 2 | READ = 1 3 | 4 | #: This is equivalent to ``UV_WRITABLE`` 5 | WRITE = 2 6 | -------------------------------------------------------------------------------- /guv/exceptions.py: -------------------------------------------------------------------------------- 1 | """Errors and Exceptions 2 | 3 | This module is the main module for all errors and exceptions for guv. Platform-specific socket 4 | errors are appropriately configured here. 5 | """ 6 | from errno import (EWOULDBLOCK, EINPROGRESS, EALREADY, EISCONN, EBADF, ENOTCONN, ESHUTDOWN, EAGAIN, 7 | ECONNRESET, EPIPE, EINVAL, ECONNABORTED) 8 | 9 | from . import patcher 10 | 11 | osocket = patcher.original('socket') 12 | 13 | from .support import OS_WINDOWS 14 | 15 | SYSTEM_ERROR = (KeyboardInterrupt, SystemExit, SystemError) 16 | 17 | if OS_WINDOWS: 18 | # winsock sometimes throws ENOTCONN 19 | SOCKET_BLOCKING = {EAGAIN, EWOULDBLOCK} 20 | SOCKET_CLOSED = {ECONNRESET, ESHUTDOWN, ENOTCONN} 21 | CONNECT_ERR = {EINPROGRESS, EALREADY, EWOULDBLOCK, EINVAL} 22 | else: 23 | # oddly, on linux/darwin, an unconnected socket is expected to block, 24 | # so we treat ENOTCONN the same as EWOULDBLOCK 25 | SOCKET_BLOCKING = {EAGAIN, EWOULDBLOCK, ENOTCONN} 26 | SOCKET_CLOSED = {ECONNRESET, ESHUTDOWN, EPIPE} 27 | CONNECT_ERR = {EINPROGRESS, EALREADY, EWOULDBLOCK} 28 | 29 | CONNECT_SUCCESS = {0, EISCONN} 30 | 31 | BAD_SOCK = {EBADF, ECONNABORTED} 32 | BROKEN_SOCK = {EPIPE, ECONNRESET} 33 | 34 | ACCEPT_EXCEPTIONS = {osocket.error} 35 | ACCEPT_ERRNO = {EPIPE, EBADF, ECONNRESET} 36 | 37 | 38 | class IOClosed(IOError): 39 | pass 40 | 41 | 42 | FileObjectClosed = IOError(EBADF, 'Bad file descriptor (FileObject was closed)') 43 | -------------------------------------------------------------------------------- /guv/fileobject.py: -------------------------------------------------------------------------------- 1 | """Greenified file class implementing standard file operations 2 | 3 | Not supported on Windows. 4 | """ 5 | import os 6 | from socket import SocketIO 7 | from errno import EBADF, EAGAIN, EINTR 8 | 9 | import fcntl 10 | from .hubs import get_hub 11 | from .support import PYPY 12 | from .green.os import read, write 13 | from .exceptions import FileObjectClosed 14 | 15 | __all__ = ['FileObjectPosix', 'FileObject'] 16 | 17 | IGNORED_ERRORS = {EAGAIN, EINTR} 18 | 19 | 20 | def set_nonblocking(fd): 21 | """Set the file descriptor to non-blocking mode 22 | 23 | :param int fd: file descriptor 24 | """ 25 | flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0) 26 | if not bool(flags & os.O_NONBLOCK): 27 | fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK) 28 | return True 29 | 30 | 31 | class SocketAdapter: 32 | """Socket-like API on top of a file descriptor. 33 | 34 | The main purpose of it is to re-use _fileobject to create proper cooperative file objects 35 | from file descriptors on POSIX platforms. 36 | """ 37 | 38 | def __init__(self, fileno, mode=None, close=True): 39 | if not isinstance(fileno, int): 40 | raise TypeError('fileno must be int: %r' % fileno) 41 | self._fileno = fileno 42 | self._mode = mode or 'rb' 43 | self._close = close 44 | self._translate = 'U' in self._mode 45 | set_nonblocking(fileno) 46 | self._eat_newline = False 47 | self.hub = get_hub() 48 | io = self.hub.loop.io 49 | self._read_event = io(fileno, 1) 50 | self._write_event = io(fileno, 2) 51 | self._refcount = 1 52 | 53 | def __repr__(self): 54 | if self._fileno is None: 55 | return '<%s at 0x%x closed>' % (self.__class__.__name__, id(self)) 56 | else: 57 | args = (self.__class__.__name__, id(self), getattr(self, '_fileno', 'N/A'), 58 | getattr(self, '_mode', 'N/A')) 59 | return '<%s at 0x%x (%r, %r)>' % args 60 | 61 | def makefile(self, *args, **kwargs): 62 | return SocketIO(self, *args, **kwargs) 63 | 64 | def fileno(self): 65 | result = self._fileno 66 | if result is None: 67 | raise IOError(EBADF, 'Bad file descriptor (SocketAdapter object is closed') 68 | return result 69 | 70 | def detach(self): 71 | x = self._fileno 72 | self._fileno = None 73 | return x 74 | 75 | def _reuse(self): 76 | self._refcount += 1 77 | 78 | def _drop(self): 79 | self._refcount -= 1 80 | if self._refcount <= 0: 81 | self._realclose() 82 | 83 | def close(self): 84 | self._drop() 85 | 86 | def _realclose(self): 87 | # TODO: should we notify the hub that this fd is closed? 88 | fileno = self._fileno 89 | if fileno is not None: 90 | self._fileno = None 91 | if self._close: 92 | os.close(fileno) 93 | 94 | def sendall(self, data): 95 | fileno = self.fileno() 96 | bytes_total = len(data) 97 | bytes_written = 0 98 | while True: 99 | try: 100 | bytes_written += write(fileno, memoryview(data)[bytes_written:]) 101 | except (IOError, OSError) as ex: 102 | code = ex.args[0] 103 | if code not in IGNORED_ERRORS: 104 | raise 105 | if bytes_written >= bytes_total: 106 | return 107 | self.hub.wait(self._write_event) 108 | 109 | def recv(self, size): 110 | while True: 111 | try: 112 | data = read(self.fileno(), size) 113 | except (IOError, OSError) as ex: 114 | code = ex.args[0] 115 | if code not in IGNORED_ERRORS: 116 | raise 117 | else: 118 | if not self._translate or not data: 119 | return data 120 | if self._eat_newline: 121 | self._eat_newline = False 122 | if data.startswith('\n'): 123 | data = data[1:] 124 | if not data: 125 | return self.recv(size) 126 | if data.endswith('\r'): 127 | self._eat_newline = True 128 | return self._translate_newlines(data) 129 | self.hub.wait(self._read_event) 130 | 131 | def _translate_newlines(self, data): 132 | data = data.replace("\r\n", "\n") 133 | data = data.replace("\r", "\n") 134 | return data 135 | 136 | 137 | class FileObjectPosix(SocketIO): 138 | def __init__(self, fobj, mode='rb', bufsize=-1, close=True): 139 | if isinstance(fobj, int): 140 | fileno = fobj 141 | fobj = None 142 | else: 143 | fileno = fobj.fileno() 144 | 145 | sock = SocketAdapter(fileno, mode, close=close) 146 | self._fobj = fobj 147 | self._closed = False 148 | super().__init__(sock, mode) 149 | if PYPY: 150 | sock._drop() 151 | 152 | def __repr__(self): 153 | if self._sock is None: 154 | return '<%s closed>' % self.__class__.__name__ 155 | elif self._fobj is None: 156 | return '<%s %s>' % (self.__class__.__name__, self._sock) 157 | else: 158 | return '<%s %s _fobj=%r>' % (self.__class__.__name__, self._sock, self._fobj) 159 | 160 | def close(self): 161 | if self._closed: 162 | # make sure close() is only ran once when called concurrently 163 | # cannot rely on self._sock for this because we need to keep that until flush() 164 | # is done 165 | return 166 | self._closed = True 167 | sock = self._sock 168 | if sock is None: 169 | return 170 | try: 171 | self.flush() 172 | finally: 173 | if self._fobj is not None or not self._close: 174 | sock.detach() 175 | else: 176 | sock._drop() 177 | self._sock = None 178 | self._fobj = None 179 | 180 | def __getattr__(self, item): 181 | assert item != '_fobj' 182 | if self._fobj is None: 183 | raise FileObjectClosed 184 | return getattr(self._fobj, item) 185 | 186 | 187 | FileObject = FileObjectPosix 188 | -------------------------------------------------------------------------------- /guv/green/__init__.py: -------------------------------------------------------------------------------- 1 | """Greenified modules 2 | 3 | This package contains "greenified" modules which replace standard library modules to work 4 | cooperatively with guv. 5 | """ 6 | -------------------------------------------------------------------------------- /guv/green/_socket3.py: -------------------------------------------------------------------------------- 1 | import socket as socket_orig 2 | 3 | __all__ = socket_orig.__all__ 4 | __patched__ = ['fromfd', 'socketpair', 'ssl', 'socket'] 5 | 6 | from ..patcher import copy_attributes 7 | 8 | copy_attributes(socket_orig, globals(), 9 | ignore=__patched__, srckeys=dir(socket_orig)) 10 | 11 | from ..greenio import socket 12 | 13 | try: 14 | _fromfd_orig = socket_orig.fromfd 15 | 16 | def fromfd(*args): 17 | return socket(_fromfd_orig(*args)) 18 | except AttributeError: 19 | pass 20 | 21 | try: 22 | _socketpair_orig = socket_orig.socketpair 23 | 24 | def socketpair(*args): 25 | one, two = _socketpair_orig(*args) 26 | return socket(one), socket(two) 27 | except AttributeError: 28 | pass 29 | -------------------------------------------------------------------------------- /guv/green/builtin.py: -------------------------------------------------------------------------------- 1 | """ 2 | In order to detect a filehandle that's been closed, our only clue may be the operating system 3 | returning the same filehandle in response to some other operation. 4 | 5 | The builtins 'file' and 'open' are patched to collaborate with the notify_opened protocol. 6 | """ 7 | 8 | builtins_orig = __builtins__ 9 | 10 | from .. import hubs 11 | from ..patcher import copy_attributes 12 | 13 | __all__ = dir(builtins_orig) 14 | __patched__ = ['open'] 15 | 16 | copy_attributes(builtins_orig, globals(), ignore=__patched__, srckeys=dir(builtins_orig)) 17 | 18 | # TODO: should this even be here? 19 | hubs.get_hub() 20 | 21 | __original_open = open 22 | __opening = False 23 | 24 | 25 | def open(*args): 26 | global __opening 27 | result = __original_open(*args) 28 | if not __opening: 29 | # This is incredibly ugly. 'open' is used under the hood by the import process. So, ensure 30 | # we don't wind up in an infinite loop. 31 | __opening = True 32 | hubs.notify_opened(result.fileno()) 33 | __opening = False 34 | return result 35 | -------------------------------------------------------------------------------- /guv/green/greenlet_local.py: -------------------------------------------------------------------------------- 1 | """greenlet-local objects 2 | """ 3 | from weakref import WeakKeyDictionary 4 | from copy import copy 5 | from .lock import RLock 6 | from greenlet import getcurrent 7 | 8 | __all__ = ["local"] 9 | 10 | 11 | class _localbase: 12 | __slots__ = '_local__args', '_local__lock', '_local__dicts' 13 | 14 | def __new__(cls, *args, **kw): 15 | self = object.__new__(cls) 16 | object.__setattr__(self, '_local__args', (args, kw)) 17 | object.__setattr__(self, '_local__lock', RLock()) 18 | dicts = WeakKeyDictionary() 19 | object.__setattr__(self, '_local__dicts', dicts) 20 | 21 | # We need to create the greenlet dict in anticipation of 22 | # __init__ being called, to make sure we don't call it again ourselves. 23 | dict = object.__getattribute__(self, '__dict__') 24 | dicts[getcurrent()] = dict 25 | return self 26 | 27 | 28 | def _init_locals(self): 29 | d = {} 30 | dicts = object.__getattribute__(self, '_local__dicts') 31 | dicts[getcurrent()] = d 32 | object.__setattr__(self, '__dict__', d) 33 | 34 | # we have a new instance dict, so call out __init__ if we have one 35 | cls = type(self) 36 | if cls.__init__ is not object.__init__: 37 | args, kw = object.__getattribute__(self, '_local__args') 38 | cls.__init__(self, *args, **kw) 39 | 40 | 41 | class local(_localbase): 42 | def __getattribute__(self, name): 43 | d = object.__getattribute__(self, '_local__dicts').get(getcurrent()) 44 | if d is None: 45 | # it's OK to acquire the lock here and not earlier, because the above code won't 46 | # switch out 47 | # however, subclassed __init__ might switch, so we do need to acquire the lock here 48 | lock = object.__getattribute__(self, '_local__lock') 49 | lock.acquire() 50 | try: 51 | _init_locals(self) 52 | return object.__getattribute__(self, name) 53 | finally: 54 | lock.release() 55 | else: 56 | object.__setattr__(self, '__dict__', d) 57 | return object.__getattribute__(self, name) 58 | 59 | def __setattr__(self, name, value): 60 | if name == '__dict__': 61 | raise AttributeError( 62 | "%r object attribute '__dict__' is read-only" % self.__class__.__name__) 63 | d = object.__getattribute__(self, '_local__dicts').get(getcurrent()) 64 | if d is None: 65 | lock = object.__getattribute__(self, '_local__lock') 66 | lock.acquire() 67 | try: 68 | _init_locals(self) 69 | return object.__setattr__(self, name, value) 70 | finally: 71 | lock.release() 72 | else: 73 | object.__setattr__(self, '__dict__', d) 74 | return object.__setattr__(self, name, value) 75 | 76 | def __delattr__(self, name): 77 | if name == '__dict__': 78 | raise AttributeError( 79 | "%r object attribute '__dict__' is read-only" % self.__class__.__name__) 80 | d = object.__getattribute__(self, '_local__dicts').get(getcurrent()) 81 | if d is None: 82 | lock = object.__getattribute__(self, '_local__lock') 83 | lock.acquire() 84 | try: 85 | _init_locals(self) 86 | return object.__delattr__(self, name) 87 | finally: 88 | lock.release() 89 | else: 90 | object.__setattr__(self, '__dict__', d) 91 | return object.__delattr__(self, name) 92 | 93 | def __copy__(self): 94 | currentId = getcurrent() 95 | d = object.__getattribute__(self, '_local__dicts').get(currentId) 96 | duplicate = copy(d) 97 | 98 | cls = type(self) 99 | if cls.__init__ is not object.__init__: 100 | args, kw = object.__getattribute__(self, '_local__args') 101 | instance = cls(*args, **kw) 102 | else: 103 | instance = cls() 104 | 105 | object.__setattr__(instance, '_local__dicts', { 106 | currentId: duplicate 107 | }) 108 | 109 | return instance 110 | -------------------------------------------------------------------------------- /guv/green/lock.py: -------------------------------------------------------------------------------- 1 | from ..semaphore import Semaphore 2 | import greenlet 3 | 4 | __all__ = ['RLock'] 5 | 6 | 7 | class RLock(object): 8 | green = True 9 | 10 | def __init__(self): 11 | self._block = Semaphore(1) 12 | self._owner = None 13 | self._count = 0 14 | 15 | def __repr__(self): 16 | return "<%s at %s _block=%s _count=%r _owner=%r)>" % ( 17 | self.__class__.__name__, 18 | hex(id(self)), 19 | self._block, 20 | self._count, 21 | self._owner) 22 | 23 | def acquire(self, blocking=1): 24 | me = greenlet.getcurrent() 25 | if self._owner is me: 26 | self._count = self._count + 1 27 | return 1 28 | rc = self._block.acquire(blocking) 29 | if rc: 30 | self._owner = me 31 | self._count = 1 32 | return rc 33 | 34 | def __enter__(self): 35 | return self.acquire() 36 | 37 | def release(self): 38 | if self._owner is not greenlet.getcurrent(): 39 | raise RuntimeError("cannot release un-aquired lock") 40 | self._count = count = self._count - 1 41 | if not count: 42 | self._owner = None 43 | self._block.release() 44 | 45 | def __exit__(self, typ, value, tb): 46 | self.release() 47 | 48 | # Internal methods used by condition variables 49 | 50 | def _acquire_restore(self, count_owner): 51 | count, owner = count_owner 52 | self._block.acquire() 53 | self._count = count 54 | self._owner = owner 55 | 56 | def _release_save(self): 57 | count = self._count 58 | self._count = 0 59 | owner = self._owner 60 | self._owner = None 61 | self._block.release() 62 | return (count, owner) 63 | 64 | def _is_owned(self): 65 | return self._owner is greenlet.getcurrent() 66 | 67 | -------------------------------------------------------------------------------- /guv/green/os.py: -------------------------------------------------------------------------------- 1 | import os as os_orig 2 | import errno 3 | import socket 4 | 5 | from ..exceptions import IOClosed 6 | from ..support import get_errno 7 | from .. import hubs, greenthread 8 | from ..patcher import copy_attributes 9 | from ..const import READ, WRITE 10 | 11 | __all__ = os_orig.__all__ 12 | __patched__ = ['read', 'write', 'wait', 'waitpid', 'open'] 13 | 14 | copy_attributes(os_orig, globals(), ignore=__patched__, srckeys=dir(os_orig)) 15 | 16 | __open = os_orig.open 17 | __read = os_orig.read 18 | __write = os_orig.write 19 | __waitpid = os_orig.waitpid 20 | 21 | 22 | def read(fd, n): 23 | """Wrap os.read 24 | """ 25 | while True: 26 | try: 27 | return __read(fd, n) 28 | except (OSError, IOError) as e: 29 | if get_errno(e) != errno.EAGAIN: 30 | raise 31 | except socket.error as e: 32 | if get_errno(e) == errno.EPIPE: 33 | return '' 34 | raise 35 | try: 36 | hubs.trampoline(fd, READ) 37 | except IOClosed: 38 | return '' 39 | 40 | 41 | def write(fd, data): 42 | """Wrap os.write 43 | """ 44 | while True: 45 | try: 46 | return __write(fd, data) 47 | except (OSError, IOError) as e: 48 | if get_errno(e) != errno.EAGAIN: 49 | raise 50 | except socket.error as e: 51 | if get_errno(e) != errno.EPIPE: 52 | raise 53 | hubs.trampoline(fd, WRITE) 54 | 55 | 56 | def wait(): 57 | """Wait for completion of a child process 58 | 59 | :return: (pid, status) 60 | """ 61 | return waitpid(0, 0) 62 | 63 | 64 | def waitpid(pid, options): 65 | """Wait for completion of a given child process 66 | 67 | :return: (pid, status) 68 | """ 69 | if options & os_orig.WNOHANG != 0: 70 | return __waitpid(pid, options) 71 | else: 72 | new_options = options | os_orig.WNOHANG 73 | while True: 74 | rpid, status = __waitpid(pid, new_options) 75 | if rpid and status >= 0: 76 | return rpid, status 77 | greenthread.sleep(0.01) 78 | 79 | 80 | def open(file, flags, mode=0o777): 81 | """Wrap os.open 82 | 83 | This behaves identically, but collaborates with the hub's notify_opened protocol. 84 | """ 85 | fd = __open(file, flags, mode) 86 | hubs.notify_opened(fd) 87 | return fd 88 | -------------------------------------------------------------------------------- /guv/green/queue.py: -------------------------------------------------------------------------------- 1 | from .. import queue 2 | 3 | __all__ = ['Empty', 'Full', 'LifoQueue', 'PriorityQueue', 'Queue'] 4 | 5 | __patched__ = ['LifoQueue', 'PriorityQueue', 'Queue'] 6 | 7 | # these classes exist to paper over the major operational difference between 8 | # guv.queue.Queue and the stdlib equivalents 9 | 10 | 11 | class Queue(queue.Queue): 12 | def __init__(self, maxsize=0): 13 | if maxsize == 0: 14 | maxsize = None 15 | super(Queue, self).__init__(maxsize) 16 | 17 | 18 | class PriorityQueue(queue.PriorityQueue): 19 | def __init__(self, maxsize=0): 20 | if maxsize == 0: 21 | maxsize = None 22 | super(PriorityQueue, self).__init__(maxsize) 23 | 24 | 25 | class LifoQueue(queue.LifoQueue): 26 | def __init__(self, maxsize=0): 27 | if maxsize == 0: 28 | maxsize = None 29 | super(LifoQueue, self).__init__(maxsize) 30 | 31 | 32 | Empty = queue.Empty 33 | Full = queue.Full 34 | -------------------------------------------------------------------------------- /guv/green/select.py: -------------------------------------------------------------------------------- 1 | import select as select_orig 2 | 3 | error = select_orig.error 4 | from greenlet import getcurrent 5 | 6 | from ..hubs import get_hub 7 | from ..const import READ, WRITE 8 | 9 | ERROR = 'error' 10 | 11 | __patched__ = ['select'] 12 | 13 | 14 | def get_fileno(obj): 15 | # The purpose of this function is to exactly replicate 16 | # the behavior of the select module when confronted with 17 | # abnormal filenos; the details are extensively tested in 18 | # the stdlib test/test_select.py. 19 | try: 20 | f = obj.fileno 21 | except AttributeError: 22 | if not isinstance(obj, int): 23 | raise TypeError("Expected int or long, got " + type(obj)) 24 | return obj 25 | else: 26 | rv = f() 27 | if not isinstance(rv, int): 28 | raise TypeError("Expected int or long, got " + type(rv)) 29 | return rv 30 | 31 | 32 | def select(read_list, write_list, error_list, timeout=None): 33 | # error checking like this is required by the stdlib unit tests 34 | if timeout is not None: 35 | try: 36 | timeout = float(timeout) 37 | except ValueError: 38 | raise TypeError('Expected number for timeout') 39 | 40 | hub = get_hub() 41 | timers = [] 42 | current = getcurrent() 43 | assert hub is not current, 'do not call blocking functions from the mainloop' 44 | files = {} # dict of socket objects or file descriptor integers 45 | for r in read_list: 46 | files[get_fileno(r)] = {READ: r} 47 | for w in write_list: 48 | files.setdefault(get_fileno(w), {})[WRITE] = w 49 | for e in error_list: 50 | files.setdefault(get_fileno(e), {})[ERROR] = e 51 | 52 | listeners = [] 53 | 54 | def on_read(d): 55 | original = files[get_fileno(d)][READ] 56 | current.switch(([original], [], [])) 57 | 58 | def on_write(d): 59 | original = files[get_fileno(d)][WRITE] 60 | current.switch(([], [original], [])) 61 | 62 | def on_error(d, _err=None): 63 | original = files[get_fileno(d)][ERROR] 64 | current.switch(([], [], [original])) 65 | 66 | def on_timeout2(): 67 | current.switch(([], [], [])) 68 | 69 | def on_timeout(): 70 | timers.append(hub.schedule_call_global(0, on_timeout2)) 71 | 72 | if timeout is not None: 73 | timers.append(hub.schedule_call_global(timeout, on_timeout)) 74 | try: 75 | for fd, v in files.items(): 76 | if v.get(READ): 77 | listeners.append(hub.add(READ, fd, on_read, on_error, (fd,))) 78 | if v.get(WRITE): 79 | listeners.append(hub.add(WRITE, fd, on_write, on_error, (fd,))) 80 | try: 81 | return hub.switch() 82 | finally: 83 | for l in listeners: 84 | hub.remove(l) 85 | finally: 86 | for t in timers: 87 | t.cancel() 88 | -------------------------------------------------------------------------------- /guv/green/socket.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | 4 | log = logging.getLogger('guv') 5 | 6 | from .. import patcher 7 | from ..green import _socket3 as gsocket 8 | 9 | __all__ = gsocket.__all__ 10 | __patched__ = gsocket.__patched__ + ['gethostbyname', 'getaddrinfo', 'create_connection'] 11 | 12 | from ..patcher import copy_attributes 13 | 14 | copy_attributes(gsocket, globals(), srckeys=dir(gsocket)) 15 | 16 | # explicitly define globals to silence IDE errors 17 | socket = gsocket.socket 18 | 19 | socket_orig = patcher.original('socket') 20 | SOCK_STREAM = socket_orig.SOCK_STREAM 21 | _GLOBAL_DEFAULT_TIMEOUT = socket_orig._GLOBAL_DEFAULT_TIMEOUT 22 | error = socket_orig.error 23 | 24 | if os.environ.get('GUV_NO_GREENDNS') is None: 25 | try: 26 | from ..support import greendns 27 | 28 | gethostbyname = greendns.gethostbyname 29 | getaddrinfo = greendns.getaddrinfo 30 | gethostbyname_ex = greendns.gethostbyname_ex 31 | getnameinfo = greendns.getnameinfo 32 | __patched__ = __patched__ + ['gethostbyname_ex', 'getnameinfo'] 33 | 34 | log.debug('Patcher: using greendns module for non-blocking DNS querying') 35 | except ImportError as ex: 36 | greendns = None 37 | log.warn('Patcher: dnspython3 not found, falling back to blocking DNS querying'.format(ex)) 38 | 39 | 40 | def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT, source_address=None): 41 | """Connect to `address` and return the socket object 42 | """ 43 | host, port = address 44 | err = None 45 | for res in getaddrinfo(host, port, 0, SOCK_STREAM): 46 | af, socktype, proto, canonname, sa = res 47 | sock = None 48 | try: 49 | sock = socket(af, socktype, proto) 50 | if timeout is not _GLOBAL_DEFAULT_TIMEOUT: 51 | sock.settimeout(timeout) 52 | if source_address: 53 | sock.bind(source_address) 54 | sock.connect(sa) 55 | return sock 56 | 57 | except error as e: 58 | err = e 59 | if sock is not None: 60 | sock.close() 61 | 62 | if err is not None: 63 | raise err 64 | -------------------------------------------------------------------------------- /guv/green/ssl.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from ..patcher import copy_attributes 3 | 4 | if sys.version_info >= (3, 3): 5 | from . import _ssl33 as _ssl3 6 | else: 7 | # Python 3.2 8 | from . import _ssl32 as _ssl3 9 | 10 | copy_attributes(_ssl3, globals(), srckeys=dir(_ssl3)) 11 | __patched__ = _ssl3.__patched__ 12 | -------------------------------------------------------------------------------- /guv/green/subprocess.py: -------------------------------------------------------------------------------- 1 | import errno 2 | import time 3 | from types import FunctionType 4 | 5 | from .. import patcher, sleep 6 | from ..green import select 7 | from ..fileobject import FileObject 8 | 9 | patcher.inject('subprocess', globals(), ('select', select)) 10 | import subprocess as subprocess_orig 11 | 12 | if getattr(subprocess_orig, 'TimeoutExpired', None) is None: 13 | # python < 3.3 needs this defined 14 | class TimeoutExpired(Exception): 15 | """This exception is raised when the timeout expires while waiting for a child process 16 | """ 17 | 18 | def __init__(self, timeout, cmd, output=None): 19 | self.cmd = cmd 20 | self.output = output 21 | 22 | def __str__(self): 23 | return 'Command "{}" timed out after {} seconds'.format(self.cmd, self.timeout) 24 | 25 | 26 | class Popen(subprocess_orig.Popen): 27 | """Greenified :class:`subprocess.Popen` 28 | """ 29 | 30 | def __init__(self, args, bufsize=0, *argss, **kwds): 31 | if subprocess_orig.mswindows: 32 | raise Exception('Greenified Popen not supported on Windows') 33 | 34 | self.args = args 35 | super().__init__(args, 0, *argss, **kwds) 36 | 37 | for attr in 'stdin', 'stdout', 'stderr': 38 | pipe = getattr(self, attr) 39 | if pipe is not None and not type(pipe) == FileObject: 40 | wrapped_pipe = FileObject(pipe, pipe.mode, bufsize) 41 | setattr(self, attr, wrapped_pipe) 42 | 43 | def wait(self, timeout=None, check_interval=0.01): 44 | # Instead of a blocking OS call, this version of wait() uses logic 45 | # borrowed from the guv 0.2 processes.Process.wait() method. 46 | if timeout is not None: 47 | endtime = time.time() + timeout 48 | try: 49 | while True: 50 | status = self.poll() 51 | if status is not None: 52 | return status 53 | if timeout is not None and time.time() > endtime: 54 | raise TimeoutExpired(self.args, timeout) 55 | sleep(check_interval) 56 | except OSError as e: 57 | if e.errno == errno.ECHILD: 58 | # no child process, this happens if the child process 59 | # already died and has been cleaned up 60 | return -1 61 | else: 62 | raise 63 | 64 | # don't want to rewrite the original _communicate() method, we 65 | # just want a version that uses guv.green.select.select() 66 | # instead of select.select(). 67 | _communicate = FunctionType(subprocess_orig.Popen._communicate.__code__, globals()) 68 | try: 69 | _communicate_with_select = FunctionType( 70 | subprocess_orig.Popen._communicate_with_select.__code__, globals()) 71 | _communicate_with_poll = FunctionType( 72 | subprocess_orig.Popen._communicate_with_poll.__code__, globals()) 73 | except AttributeError: 74 | pass 75 | 76 | __init__.__doc__ = super().__init__.__doc__ 77 | wait.__doc__ = super().wait.__doc__ 78 | 79 | # Borrow `subprocess.call()` and `check_call()`, but patch them so they reference 80 | # the patched `Popen` class rather than `subprocess.Popen` 81 | call = FunctionType(subprocess_orig.call.__code__, globals()) 82 | check_call = FunctionType(subprocess_orig.check_call.__code__, globals()) 83 | -------------------------------------------------------------------------------- /guv/green/thread.py: -------------------------------------------------------------------------------- 1 | """Greenified _thread 2 | """ 3 | import greenlet 4 | import _thread as _thread_orig 5 | 6 | from .. import greenthread 7 | from ..semaphore import Semaphore as LockType 8 | from . import lock as _lock 9 | 10 | __patched__ = ['get_ident', 'start_new_thread', 'allocate_lock', 11 | 'exit', 'interrupt_main', 'stack_size', '_local', 12 | 'LockType', '_count', '_set_sentinel', 'RLock'] 13 | 14 | RLock = _lock.RLock 15 | 16 | error = _thread_orig.error 17 | __threadcount = 0 18 | 19 | 20 | def _set_sentinel(): 21 | return allocate_lock() 22 | 23 | 24 | TIMEOUT_MAX = _thread_orig.TIMEOUT_MAX 25 | 26 | 27 | def _count(): 28 | return __threadcount 29 | 30 | 31 | def get_ident(gr=None): 32 | if gr is None: 33 | return id(greenlet.getcurrent()) 34 | else: 35 | return id(gr) 36 | 37 | 38 | def __thread_body(func, args, kwargs): 39 | global __threadcount 40 | __threadcount += 1 41 | try: 42 | func(*args, **kwargs) 43 | finally: 44 | __threadcount -= 1 45 | 46 | 47 | def start_new_thread(function, args=(), kwargs=None): 48 | kwargs = kwargs or {} 49 | g = greenthread.spawn(__thread_body, function, args, kwargs) 50 | return get_ident(g) 51 | 52 | 53 | def allocate_lock(*a): 54 | return LockType(1) 55 | 56 | 57 | def exit(): 58 | raise greenlet.GreenletExit 59 | 60 | 61 | def interrupt_main(): 62 | curr = greenlet.getcurrent() 63 | if curr.parent and not curr.parent.dead: 64 | curr.parent.throw(KeyboardInterrupt()) 65 | else: 66 | raise KeyboardInterrupt() 67 | 68 | 69 | if hasattr(_thread_orig, 'stack_size'): 70 | __original_stack_size__ = _thread_orig.stack_size 71 | 72 | def stack_size(size=None): 73 | if size is None: 74 | return __original_stack_size__() 75 | if size > __original_stack_size__(): 76 | return __original_stack_size__(size) 77 | else: 78 | pass 79 | # not going to decrease stack_size, because otherwise other greenlets in 80 | # this thread will suffer 81 | -------------------------------------------------------------------------------- /guv/green/threading.py: -------------------------------------------------------------------------------- 1 | """Greenified threading module 2 | """ 3 | import greenlet 4 | import logging 5 | 6 | from .. import patcher, event, semaphore 7 | from ..greenthread import spawn 8 | from . import time, thread, greenlet_local, lock as _lock 9 | 10 | log = logging.getLogger('guv') 11 | 12 | threading_orig = patcher.original('threading') 13 | 14 | __patched__ = ['_start_new_thread', '_allocate_lock', '_get_ident', '_sleep', '_after_fork', 15 | '_shutdown', '_set_sentinel', 16 | 'local', 'stack_size', 'currentThread', 'current_thread', 'Lock', 'RLock', 'Event', 17 | 'Semaphore', 'BoundedSemaphore', 'Thread', 'Condition'] 18 | 19 | patcher.inject('threading', globals(), ('_thread', thread), ('time', time)) 20 | 21 | local = greenlet_local.local 22 | Event = event.TEvent 23 | Semaphore = semaphore.Semaphore 24 | BoundedSemaphore = semaphore.BoundedSemaphore 25 | Lock = semaphore.Semaphore 26 | RLock = _lock.RLock 27 | get_ident = thread.get_ident 28 | _start_new_thread = thread.start_new_thread 29 | _allocate_lock = thread.allocate_lock 30 | _set_sentinel = thread._set_sentinel 31 | 32 | # active Thread objects dict[greenlet.greenlet: Thread] 33 | _active_threads = {} 34 | 35 | 36 | def active_count() -> int: 37 | return len(_active_threads) 38 | 39 | 40 | def enumerate(): 41 | return list(_active_threads.values()) 42 | 43 | 44 | def main_thread(): 45 | assert isinstance(_main_thread, Thread) 46 | return _main_thread 47 | 48 | 49 | def settrace(): 50 | raise NotImplemented('Not implemented for greenlets') 51 | 52 | 53 | def setprofile(): 54 | raise NotImplemented('Not implemented for greenlets') 55 | 56 | 57 | def current_thread() -> Thread: 58 | g = greenlet.getcurrent() 59 | assert isinstance(g, greenlet.greenlet) 60 | 61 | if g and g not in _active_threads: 62 | # This greenlet was spawned outside of the threading module, so in order to have the same 63 | # semantics as the original threading module, the "main thread" should be returned. 64 | return _main_thread 65 | 66 | return _active_threads[g] 67 | 68 | 69 | def _cleanup(g): 70 | """Clean up GreenThread 71 | 72 | This function is called when the underlying GreenThread object of a "green" Thread exits. 73 | """ 74 | del _active_threads[g] 75 | 76 | 77 | class Thread: 78 | def __init__(self, group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None): 79 | #: :type: GreenThread 80 | self._gt = None 81 | self._name = name or 'Thread' 82 | self.daemon = True 83 | self.target = target or self.run 84 | self.args = args 85 | self.kwargs = kwargs 86 | 87 | def __repr__(self): 88 | return '<(green) Thread (%s, %r)>' % (self._name, self._gt) 89 | 90 | def start(self): 91 | self._gt = spawn(self.target, *self.args, **self.kwargs) 92 | self._gt.link(_cleanup) 93 | 94 | _active_threads[self._gt] = self 95 | 96 | def run(self): 97 | pass 98 | 99 | def join(self, timeout=None): 100 | # FIXME: add support for timeouts 101 | return self._gt.wait() 102 | 103 | def get_name(self): 104 | return self._name 105 | 106 | def set_name(self, value): 107 | self._name = str(value) 108 | 109 | def is_alive(self): 110 | return bool(self._gt) 111 | 112 | def is_daemon(self): 113 | return self.daemon 114 | 115 | def set_daemon(self, daemonic): 116 | self.daemon = daemonic 117 | 118 | name = property(get_name, set_name) 119 | ident = property(lambda self: id(self._g)) 120 | 121 | getName = get_name 122 | setName = set_name 123 | isAlive = is_alive 124 | isDaemon = is_daemon 125 | setDaemon = set_daemon 126 | 127 | 128 | _main_thread = Thread() 129 | _main_thread.name = 'MainThread' 130 | _main_thread._gt = greenlet.getcurrent() 131 | 132 | _active_threads[greenlet.getcurrent()] = _main_thread 133 | -------------------------------------------------------------------------------- /guv/green/time.py: -------------------------------------------------------------------------------- 1 | """Greenified :mod:`time` module 2 | 3 | The only thing that needs to be patched from :mod:`time` is :func:`time.sleep` to yield instead 4 | of block the thread. 5 | """ 6 | import time as time_orig 7 | from ..patcher import copy_attributes 8 | 9 | __patched__ = ['sleep'] 10 | copy_attributes(time_orig, globals(), ignore=__patched__, srckeys=dir(time_orig)) 11 | from .. import greenthread 12 | 13 | sleep = greenthread.sleep 14 | -------------------------------------------------------------------------------- /guv/greenpool.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | import greenlet 3 | 4 | from . import event, greenthread, queue, semaphore 5 | 6 | __all__ = ['GreenPool', 'GreenPile'] 7 | 8 | DEBUG = True 9 | 10 | 11 | class GreenPool: 12 | """Pool of greenlets/GreenThreads 13 | 14 | This class manages a pool of greenlets/GreenThreads 15 | """ 16 | 17 | def __init__(self, size=1000): 18 | """ 19 | :param size: maximum number of active greenlets 20 | """ 21 | self.size = size 22 | self.coroutines_running = set() 23 | self.sem = semaphore.Semaphore(size) 24 | self.no_coros_running = event.Event() 25 | 26 | def resize(self, new_size): 27 | """Change the max number of greenthreads doing work at any given time 28 | 29 | If resize is called when there are more than *new_size* greenthreads already working on 30 | tasks, they will be allowed to complete but no new tasks will be allowed to get launched 31 | until enough greenthreads finish their tasks to drop the overall quantity below *new_size*. 32 | Until then, the return value of free() will be negative. 33 | """ 34 | size_delta = new_size - self.size 35 | self.sem.counter += size_delta 36 | self.size = new_size 37 | 38 | def running(self): 39 | """Return the number of greenthreads that are currently executing functions in the GreenPool 40 | """ 41 | return len(self.coroutines_running) 42 | 43 | def free(self): 44 | """Return the number of greenthreads available for use 45 | 46 | If zero or less, the next call to :meth:`spawn` or :meth:`spawn_n` will block the calling 47 | greenthread until a slot becomes available.""" 48 | return self.sem.counter 49 | 50 | def spawn(self, function, *args, **kwargs): 51 | """Run the *function* with its arguments in its own green thread 52 | 53 | Returns the :class:`GreenThread ` object that is running 54 | the function, which can be used to retrieve the results. 55 | 56 | If the pool is currently at capacity, ``spawn`` will block until one of the running 57 | greenthreads completes its task and frees up a slot. 58 | 59 | This function is reentrant; *function* can call ``spawn`` on the same pool without risk of 60 | deadlocking the whole thing. 61 | """ 62 | # if reentering an empty pool, don't try to wait on a coroutine freeing 63 | # itself -- instead, just execute in the current coroutine 64 | current = greenlet.getcurrent() 65 | if self.sem.locked() and current in self.coroutines_running: 66 | # a bit hacky to use the GT without switching to it 67 | gt = greenthread.GreenThread(current) 68 | gt.main(function, *args, **kwargs) 69 | return gt 70 | else: 71 | self.sem.acquire() 72 | gt = greenthread.spawn(function, *args, **kwargs) 73 | if not self.coroutines_running: 74 | self.no_coros_running = event.Event() 75 | self.coroutines_running.add(gt) 76 | gt.link(self._spawn_done) 77 | return gt 78 | 79 | def _spawn_n_impl(self, func, args, kwargs, coro): 80 | try: 81 | try: 82 | func(*args, **kwargs) 83 | except (KeyboardInterrupt, SystemExit, greenlet.GreenletExit): 84 | raise 85 | except: 86 | if DEBUG: 87 | traceback.print_exc() 88 | finally: 89 | if coro is None: 90 | return 91 | else: 92 | coro = greenlet.getcurrent() 93 | self._spawn_done(coro) 94 | 95 | def spawn_n(self, function, *args, **kwargs): 96 | """Create a greenthread to run the `function` like :meth:`spawn`, but return None 97 | 98 | The difference is that :meth:`spawn_n` returns None; the results of `function` are not 99 | retrievable. 100 | """ 101 | # if reentering an empty pool, don't try to wait on a coroutine freeing 102 | # itself -- instead, just execute in the current coroutine 103 | current = greenlet.getcurrent() 104 | if self.sem.locked() and current in self.coroutines_running: 105 | self._spawn_n_impl(function, args, kwargs, None) 106 | else: 107 | self.sem.acquire() 108 | g = greenthread.spawn_n(self._spawn_n_impl, function, args, kwargs, True) 109 | if not self.coroutines_running: 110 | self.no_coros_running = event.Event() 111 | self.coroutines_running.add(g) 112 | 113 | def waitall(self): 114 | """Wait until all greenthreads in the pool are finished working 115 | """ 116 | assert greenlet.getcurrent() not in self.coroutines_running, \ 117 | "Calling waitall() from within one of the " \ 118 | "GreenPool's greenthreads will never terminate." 119 | if self.running(): 120 | self.no_coros_running.wait() 121 | 122 | def _spawn_done(self, coro): 123 | self.sem.release() 124 | if coro is not None: 125 | self.coroutines_running.remove(coro) 126 | # if done processing (no more work is waiting for processing), we can finish off any 127 | # waitall() calls that might be pending 128 | if self.sem.balance == self.size: 129 | self.no_coros_running.send(None) 130 | 131 | def waiting(self): 132 | """Return the number of greenthreads waiting to spawn. 133 | """ 134 | if self.sem.balance < 0: 135 | return -self.sem.balance 136 | else: 137 | return 0 138 | 139 | def _do_map(self, func, it, gi): 140 | for args in it: 141 | gi.spawn(func, *args) 142 | gi.spawn(return_stop_iteration) 143 | 144 | def starmap(self, function, iterable): 145 | """Apply each item in `iterable` to `function` 146 | 147 | Each item in `iterable` must be an iterable itself, passed to the function as expanded 148 | positional arguments. This behaves the same way as :func:`itertools.starmap`, except that 149 | `func` is executed in a separate green thread for each item, with the concurrency limited by 150 | the pool's size. In operation, starmap consumes a constant amount of memory, proportional to 151 | the size of the pool, and is thus suited for iterating over extremely long input lists. 152 | """ 153 | if function is None: 154 | function = lambda *args: args 155 | 156 | gi = GreenMap(self.size) 157 | greenthread.spawn_n(self._do_map, function, iterable, gi) 158 | return gi 159 | 160 | 161 | def return_stop_iteration(): 162 | return StopIteration() 163 | 164 | 165 | class GreenPile: 166 | """An abstraction representing a set of I/O-related tasks 167 | 168 | Construct a GreenPile with an existing GreenPool object. The GreenPile will then use that 169 | pool's concurrency as it processes its jobs. There can be many GreenPiles associated with a 170 | single GreenPool. 171 | 172 | A GreenPile can also be constructed standalone, not associated with any GreenPool. To do this, 173 | construct it with an integer size parameter instead of a GreenPool. 174 | 175 | It is not advisable to iterate over a GreenPile in a different greenlet than the one which is 176 | calling spawn. The iterator will exit early in that situation. 177 | """ 178 | 179 | def __init__(self, size_or_pool=1000): 180 | """ 181 | :param size_or_pool: either an existing GreenPool object, or the size a new one to create 182 | :type size_or_pool: int or GreenPool 183 | """ 184 | if isinstance(size_or_pool, GreenPool): 185 | self.pool = size_or_pool 186 | else: 187 | self.pool = GreenPool(size_or_pool) 188 | self.waiters = queue.LightQueue() 189 | self.used = False 190 | self.counter = 0 191 | 192 | def spawn(self, func, *args, **kwargs): 193 | """Run `func` in its own GreenThread 194 | 195 | The Result is available by iterating over the GreenPile object. 196 | 197 | :param Callable func: function to call 198 | :param args: positional args to pass to `func` 199 | :param kwargs: keyword args to pass to `func` 200 | """ 201 | self.used = True 202 | self.counter += 1 203 | try: 204 | gt = self.pool.spawn(func, *args, **kwargs) 205 | self.waiters.put(gt) 206 | except: 207 | self.counter -= 1 208 | raise 209 | 210 | def __iter__(self): 211 | return self 212 | 213 | def next(self): 214 | """Wait for the next result, suspending the current GreenThread until it is available 215 | 216 | :raise StopIteration: when there are no more results. 217 | """ 218 | if self.counter == 0 and self.used: 219 | raise StopIteration() 220 | try: 221 | return self.waiters.get().wait() 222 | finally: 223 | self.counter -= 1 224 | 225 | __next__ = next 226 | 227 | 228 | # this is identical to GreenPile but it blocks on spawn if the results 229 | # aren't consumed, and it doesn't generate its own StopIteration exception, 230 | # instead relying on the spawning process to send one in when it's done 231 | class GreenMap(GreenPile): 232 | def __init__(self, size_or_pool): 233 | super(GreenMap, self).__init__(size_or_pool) 234 | self.waiters = queue.LightQueue(maxsize=self.pool.size) 235 | 236 | def next(self): 237 | try: 238 | val = self.waiters.get().wait() 239 | if isinstance(val, StopIteration): 240 | raise val 241 | else: 242 | return val 243 | finally: 244 | self.counter -= 1 245 | 246 | __next__ = next 247 | -------------------------------------------------------------------------------- /guv/greenthread.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | import sys 3 | import greenlet 4 | 5 | from . import event, hubs 6 | from .support import reraise 7 | 8 | __all__ = ['sleep', 'spawn', 'spawn_n', 'kill', 'spawn_after', 'GreenThread'] 9 | 10 | 11 | def sleep(seconds=0): 12 | """Yield control to the hub until at least `seconds` have elapsed 13 | 14 | :param float seconds: time to sleep for 15 | """ 16 | hub = hubs.get_hub() 17 | current = greenlet.getcurrent() 18 | assert hub is not current, 'do not call blocking functions from the hub' 19 | timer = hub.schedule_call_global(seconds, current.switch) 20 | try: 21 | hub.switch() 22 | finally: 23 | timer.cancel() 24 | 25 | 26 | def spawn_n(func, *args, **kwargs): 27 | """Spawn a greenlet 28 | 29 | Execution control returns immediately to the caller; the created greenlet is scheduled to be run 30 | at the start of the next event loop iteration, after other scheduled greenlets, but before 31 | greenlets waiting for I/O events. 32 | 33 | This is faster than :func:`spawn`, but it is not possible to retrieve the return value of 34 | the greenlet, or whether it raised any exceptions. It is fastest if there are no keyword 35 | arguments. 36 | 37 | If an exception is raised in the function, a stack trace is printed; the print can be 38 | disabled by calling :func:`guv.debug.hub_exceptions` with False. 39 | 40 | :return: greenlet object 41 | :rtype: greenlet.greenlet 42 | """ 43 | hub = hubs.get_hub() 44 | g = greenlet.greenlet(func, parent=hub) 45 | hub.schedule_call_now(g.switch, *args, **kwargs) 46 | return g 47 | 48 | 49 | def spawn(func, *args, **kwargs): 50 | """Spawn a GreenThread 51 | 52 | Execution control returns immediately to the caller; the created GreenThread is scheduled to 53 | be run at the start of the next event loop iteration, after other scheduled greenlets, 54 | but before greenlets waiting for I/O events. 55 | 56 | :return: GreenThread object which can be used to retrieve the return value of the function 57 | :rtype: GreenThread 58 | """ 59 | hub = hubs.get_hub() 60 | g = GreenThread(hub) 61 | hub.schedule_call_now(g.switch, func, *args, **kwargs) 62 | return g 63 | 64 | 65 | def spawn_after(seconds, func, *args, **kwargs): 66 | """Spawn a GreenThread after `seconds` have elapsed 67 | 68 | Execution control returns immediately to the caller. 69 | 70 | To cancel the spawn and prevent *func* from being called, call :meth:`GreenThread.cancel` on the 71 | returned GreenThread. This will not abort the function if it's already started running, which is 72 | generally the desired behavior. If terminating *func* regardless of whether it's started or not 73 | is the desired behavior, call :meth:`GreenThread.kill`. 74 | 75 | :return: GreenThread object which can be used to retrieve the return value of the function 76 | :rtype: GreenThread 77 | """ 78 | hub = hubs.get_hub() 79 | g = GreenThread(hub) 80 | hub.schedule_call_global(seconds, g.switch, func, *args, **kwargs) 81 | return g 82 | 83 | 84 | def _spawn_n(seconds, func, args, kwargs): 85 | hub = hubs.get_hub() 86 | g = greenlet.greenlet(func, parent=hub) 87 | t = hub.schedule_call_global(seconds, g.switch, *args, **kwargs) 88 | return t, g 89 | 90 | 91 | class GreenThread(greenlet.greenlet): 92 | """The GreenThread class is a type of Greenlet which has the additional property of being able 93 | to retrieve the return value of the main function. Do not construct GreenThread objects 94 | directly; call :func:`spawn` to get one. 95 | """ 96 | 97 | def __init__(self, parent): 98 | """ 99 | :param parent: parent greenlet 100 | :type parent: greenlet.greenlet 101 | """ 102 | greenlet.greenlet.__init__(self, self.main, parent) 103 | self._exit_event = event.Event() 104 | self._resolving_links = False 105 | 106 | def wait(self): 107 | """Return the result of the main function of this GreenThread 108 | 109 | If the result is a normal return value, :meth:`wait` returns it. If it raised an exception, 110 | :meth:`wait` will raise the same exception (though the stack trace will unavoidably contain 111 | some frames from within the GreenThread module). 112 | """ 113 | return self._exit_event.wait() 114 | 115 | def link(self, func, *curried_args, **curried_kwargs): 116 | """Set up a function to be called with the results of the GreenThread 117 | 118 | The function must have the following signature:: 119 | 120 | func(gt, [curried args/kwargs]) 121 | 122 | When the GreenThread finishes its run, it calls *func* with itself and with the `curried 123 | arguments `_ supplied at link-time. If the function 124 | wants to retrieve the result of the GreenThread, it should call wait() on its first 125 | argument. 126 | 127 | Note that *func* is called within execution context of the GreenThread, so it is possible to 128 | interfere with other linked functions by doing things like switching explicitly to another 129 | GreenThread. 130 | """ 131 | self._exit_funcs = getattr(self, '_exit_funcs', deque()) 132 | self._exit_funcs.append((func, curried_args, curried_kwargs)) 133 | if self._exit_event.ready(): 134 | self._resolve_links() 135 | 136 | def unlink(self, func, *curried_args, **curried_kwargs): 137 | """Remove linked function set by :meth:`link` 138 | 139 | Remove successfully return True, otherwise False 140 | """ 141 | if not getattr(self, '_exit_funcs', None): 142 | return False 143 | try: 144 | self._exit_funcs.remove((func, curried_args, curried_kwargs)) 145 | return True 146 | except ValueError: 147 | return False 148 | 149 | def main(self, function, *args, **kwargs): 150 | try: 151 | result = function(*args, **kwargs) 152 | except: 153 | self._exit_event.send_exception(*sys.exc_info()) 154 | self._resolve_links() 155 | raise 156 | else: 157 | self._exit_event.send(result) 158 | self._resolve_links() 159 | 160 | def _resolve_links(self): 161 | # ca and ckw are the curried function arguments 162 | if self._resolving_links: 163 | return 164 | self._resolving_links = True 165 | try: 166 | exit_funcs = getattr(self, '_exit_funcs', deque()) 167 | while exit_funcs: 168 | f, ca, ckw = exit_funcs.popleft() 169 | f(self, *ca, **ckw) 170 | finally: 171 | self._resolving_links = False 172 | 173 | def kill(self, *throw_args): 174 | """Kill the GreenThread using :func:`kill` 175 | 176 | After being killed all calls to :meth:`wait` will raise `throw_args` (which default to 177 | :class:`greenlet.GreenletExit`). 178 | """ 179 | return kill(self, *throw_args) 180 | 181 | def cancel(self, *throw_args): 182 | """Kill the GreenThread using :func:`kill`, but only if it hasn't already started running 183 | 184 | After being canceled, all calls to :meth:`wait` will raise `throw_args` (which default to 185 | :class:`greenlet.GreenletExit`). 186 | """ 187 | return cancel(self, *throw_args) 188 | 189 | 190 | def cancel(g, *throw_args): 191 | """Cancel the target greenlet/GreenThread if it hasn't already started 192 | 193 | This is like :func:`kill`, but only has an effect if the target greenlet/GreenThread has not 194 | yet started. 195 | """ 196 | if not g: 197 | kill(g, *throw_args) 198 | 199 | 200 | def kill(g, *throw_args): 201 | """Terminate the target greenlet/GreenThread by raising an exception into it 202 | 203 | Whatever that GreenThread might be doing, be it waiting for I/O or another primitive, it sees an 204 | exception right away. 205 | 206 | By default, this exception is GreenletExit, but a specific exception may be specified. 207 | `throw_args` should be the same as the arguments to raise; either an exception instance or an 208 | exc_info tuple. 209 | 210 | Calling :func:`kill` causes the calling greenlet to cooperatively yield. 211 | 212 | :param g: target greenlet/GreenThread to kill 213 | :type g: greenlet.greenlet or GreenThread 214 | """ 215 | if g.dead: 216 | return 217 | 218 | hub = hubs.get_hub() 219 | if not g: 220 | # greenlet hasn't started yet and therefore throw won't work on its own; semantically we 221 | # want it to be as though the main method never got called 222 | def just_raise(*a, **kw): 223 | if throw_args: 224 | reraise(throw_args[0], throw_args[1], throw_args[2]) 225 | else: 226 | raise greenlet.GreenletExit() 227 | 228 | g.run = just_raise 229 | if isinstance(g, GreenThread): 230 | # it's a GreenThread object, so we want to call its main method to take advantage of 231 | # the notification 232 | try: 233 | g.main(just_raise, (), {}) 234 | except: 235 | pass 236 | 237 | current = greenlet.getcurrent() 238 | if current is not hub: 239 | # arrange to wake the caller back up immediately 240 | hub.schedule_call_now(current.switch) 241 | 242 | g.throw(*throw_args) 243 | -------------------------------------------------------------------------------- /guv/hubs/__init__.py: -------------------------------------------------------------------------------- 1 | from .switch import trampoline 2 | from .hub import get_default_hub, use_hub, get_hub, notify_opened 3 | 4 | __all__ = ['use_hub', 'get_hub', 'get_default_hub', 'trampoline'] 5 | -------------------------------------------------------------------------------- /guv/hubs/abc.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod 2 | import greenlet 3 | import sys 4 | import traceback 5 | from greenlet import GreenletExit 6 | 7 | from ..const import READ, WRITE 8 | from ..exceptions import SYSTEM_ERROR 9 | 10 | NOT_ERROR = (GreenletExit, SystemExit) 11 | 12 | 13 | class AbstractTimer(metaclass=ABCMeta): 14 | """Timer interface 15 | 16 | This is required for anything depending on this interface, such as :func:`hubs.trampoline`. 17 | """ 18 | 19 | @abstractmethod 20 | def cancel(self): 21 | """Cancel the timer 22 | """ 23 | pass 24 | 25 | 26 | class AbstractListener(metaclass=ABCMeta): 27 | def __init__(self, evtype, fd): 28 | """ 29 | :param str evtype: the constant hubs.READ or hubs.WRITE 30 | :param int fd: fileno 31 | """ 32 | assert evtype in [READ, WRITE] 33 | self.evtype = evtype 34 | self.fd = fd 35 | self.greenlet = greenlet.getcurrent() 36 | 37 | def __repr__(self): 38 | return '{0}({1.evtype}, {1.fd})'.format(type(self).__name__, self) 39 | 40 | __str__ = __repr__ 41 | 42 | 43 | class AbstractHub(greenlet.greenlet, metaclass=ABCMeta): 44 | def __init__(self): 45 | super().__init__() 46 | self.listeners = {READ: {}, WRITE: {}} 47 | self.Listener = AbstractListener 48 | self.stopping = False 49 | 50 | self._debug_exceptions = True 51 | 52 | @abstractmethod 53 | def run(self, *args, **kwargs): 54 | """Run event loop 55 | """ 56 | 57 | @abstractmethod 58 | def abort(self): 59 | """Stop the runloop 60 | """ 61 | pass 62 | 63 | @abstractmethod 64 | def schedule_call_now(self, cb, *args, **kwargs): 65 | """Schedule a callable to be called on the next event loop iteration 66 | 67 | This is faster than calling :meth:`schedule_call_global(0, ...)` 68 | 69 | :param Callable cb: callback to call after timer fires 70 | :param args: positional arguments to pass to the callback 71 | :param kwargs: keyword arguments to pass to the callback 72 | """ 73 | pass 74 | 75 | @abstractmethod 76 | def schedule_call_global(self, seconds, cb, *args, **kwargs): 77 | """Schedule a callable to be called after 'seconds' seconds have elapsed. The timer will NOT 78 | be canceled if the current greenlet has exited before the timer fires. 79 | 80 | :param float seconds: number of seconds to wait 81 | :param Callable cb: callback to call after timer fires 82 | :param args: positional arguments to pass to the callback 83 | :param kwargs: keyword arguments to pass to the callback 84 | :return: timer object that can be cancelled 85 | :rtype: hubs.abc.Timer 86 | """ 87 | pass 88 | 89 | @abstractmethod 90 | def add(self, evtype, fd, cb, tb, cb_args=()): 91 | """Signal the hub to watch the given file descriptor for an I/O event 92 | 93 | When the file descriptor is ready for the specified I/O event type, `cb` is called with 94 | the specified `cb_args`. 95 | 96 | :param int evtype: either the constant READ or WRITE 97 | :param int fd: file number of the file of interest 98 | :param cb: callback which will be called when the file is ready for reading/writing 99 | :param tb: throwback used to signal (into the greenlet) that the file was closed 100 | :param tuple cb_args: (optional) callback positional arguments 101 | :return: listener 102 | :rtype: self.Listener 103 | """ 104 | pass 105 | 106 | @abstractmethod 107 | def remove(self, listener): 108 | """Remove listener 109 | 110 | This method safely stops and removes the listener, as well as performs any necessary 111 | cleanup related to the listener. 112 | 113 | :param listener: listener to remove 114 | :type listener: self.Listener 115 | """ 116 | pass 117 | 118 | def switch(self): 119 | """Switch to the hub greenlet 120 | """ 121 | assert greenlet.getcurrent() is not self, 'Cannot switch to the hub from the hub' 122 | return super().switch() 123 | 124 | def notify_opened(self, fd): 125 | """Mark the specified file descriptor as recently opened 126 | 127 | When the OS returns a file descriptor from an `open()` (or something similar), this may be 128 | the only indication we have that the FD has been closed and then recycled. We let the hub 129 | know that the old file descriptor is dead; any stuck listeners will be disabled and notified 130 | in turn. 131 | 132 | :param int fd: file descriptor 133 | :return: True if found else false 134 | """ 135 | found = False 136 | 137 | # remove any existing listeners for this file descriptor 138 | for bucket in self.listeners.values(): 139 | if fd in bucket: 140 | found = True 141 | listener = bucket[fd] 142 | self.remove(listener) 143 | 144 | return found 145 | 146 | def _add_listener(self, listener): 147 | """Add listener to internal dictionary 148 | 149 | :type listener: abc.AbstractListener 150 | :raise RuntimeError: if attempting to add multiple listeners with the same `evtype` and `fd` 151 | """ 152 | evtype = listener.evtype 153 | fd = listener.fd 154 | 155 | bucket = self.listeners[evtype] 156 | if fd in bucket: 157 | raise RuntimeError('Multiple {evtype} on {fd} not supported' 158 | .format(evtype=evtype, fd=fd)) 159 | else: 160 | bucket[fd] = listener 161 | 162 | def _remove_listener(self, listener): 163 | """Remove listener 164 | 165 | :param listener: listener to remove 166 | :type listener: self.Listener 167 | """ 168 | self.listeners[listener.evtype][listener.fd] = None 169 | del self.listeners[listener.evtype][listener.fd] 170 | 171 | def _squelch_exception(self, exc_info): 172 | if self._debug_exceptions and not issubclass(exc_info[0], NOT_ERROR): 173 | traceback.print_exception(*exc_info) 174 | sys.stderr.flush() 175 | 176 | if issubclass(exc_info[0], SYSTEM_ERROR): 177 | self._handle_system_error(exc_info) 178 | 179 | def _handle_system_error(self, exc_info): 180 | current = greenlet.getcurrent() 181 | if current is self or current is self.parent: 182 | self.parent.throw(*exc_info) 183 | -------------------------------------------------------------------------------- /guv/hubs/hub.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import importlib 3 | import os 4 | 5 | from .. import patcher 6 | 7 | _threading = patcher.original('threading') 8 | _threadlocal = _threading.local() 9 | 10 | log = logging.getLogger('guv') 11 | 12 | # set hub_name to a valid loop backend name from an environment variable; None to use default 13 | hub_name = os.environ.get('GUV_HUB') 14 | 15 | 16 | def notify_opened(fd): 17 | """Mark the specified file descriptor as recently opened 18 | 19 | Some file descriptors may be closed 'silently' - that is, by the garbage collector, by an 20 | external library, etc. When the OS returns a file descriptor from an `open()` (or something 21 | similar), this may be the only indication we have that the FD has been closed and then recycled. 22 | We let the hub know that the old file descriptor is dead; any stuck listeners will be disabled 23 | and notified in turn. 24 | 25 | :param int fd: file descriptor 26 | """ 27 | hub = get_hub() 28 | hub.notify_opened(fd) 29 | 30 | 31 | def get_default_hub(): 32 | """Get default hub implementation 33 | """ 34 | names = [hub_name] if hub_name else ['pyuv_cffi', 'pyuv', 'epoll'] 35 | 36 | for name in names: 37 | try: 38 | module = importlib.import_module('guv.hubs.{}'.format(name)) 39 | log.debug('Hub: use {}'.format(name)) 40 | return module 41 | except ImportError: 42 | # try the next possible hub 43 | pass 44 | 45 | 46 | def use_hub(mod=None): 47 | """Use the module :var:`mod`, containing a class called Hub, as the event hub 48 | """ 49 | if not mod: 50 | mod = get_default_hub() 51 | 52 | if hasattr(_threadlocal, 'hub'): 53 | del _threadlocal.hub 54 | 55 | if hasattr(mod, 'Hub'): 56 | _threadlocal.Hub = mod.Hub 57 | else: 58 | _threadlocal.Hub = mod 59 | 60 | 61 | def get_hub(): 62 | """Get the current event hub singleton object 63 | 64 | .. note :: |internal| 65 | """ 66 | try: 67 | hub = _threadlocal.hub 68 | except AttributeError: 69 | # instantiate a Hub 70 | try: 71 | _threadlocal.Hub 72 | except AttributeError: 73 | use_hub() 74 | 75 | hub = _threadlocal.hub = _threadlocal.Hub() 76 | return hub 77 | -------------------------------------------------------------------------------- /guv/hubs/pyuv_cffi.py: -------------------------------------------------------------------------------- 1 | """Loop implementation using pyuv_cffi 2 | 3 | To ensure compatibility with pyuv, doing `import pyuv as pyuv_cffi` must cause the loop to behave in 4 | exactly the same way as with pyuv_cffi. 5 | 6 | Notes: 7 | 8 | - The loop is free to exit (:meth:`Loop.run` is free to return) when there are no non-internal 9 | handles/callbacks remaining - that is, when there are no more Poll/Timer handles and no more 10 | callbacks scheduled. 11 | - The loop has four internal handles at all times: Signal (to watch for SIGINT), Prepare (to run 12 | scheduled callbacks), Idle (to ensure a zero-timeout poll when callbacks are scheduled), and 13 | Check (to unref the Prepare handle if there are no remaining scheduled callbacks). The Signal and 14 | Check handles are unreferenced since they are static and will not keep the loop alive. The Idle 15 | handle is *only* active if callbacks are scheduled and will not keep the loop alive. The Prepare 16 | handle is therefore the only remaining handle which has any control over whether or not the loop 17 | exits when no other handles are active. Therefore, the Prepare handle must only be unreferenced 18 | when there are no callbacks scheduled and *must* be referenced at all other times. 19 | """ 20 | import signal 21 | import logging 22 | import greenlet 23 | import sys 24 | 25 | from guv.hubs.abc import AbstractListener 26 | import pyuv_cffi 27 | from . import abc 28 | 29 | log = logging.getLogger('guv') 30 | 31 | 32 | class UvFdListener(AbstractListener): 33 | def __init__(self, evtype, fd, handle): 34 | """ 35 | :param handle: pyuv_cffi Handle object 36 | :type handle: pyuv_cffi.Handle 37 | """ 38 | super().__init__(evtype, fd) 39 | self.handle = handle 40 | 41 | 42 | class Timer(abc.AbstractTimer): 43 | def __init__(self, timer_handle): 44 | """ 45 | :type timer_handle: pyuv.Timer 46 | """ 47 | self.timer_handle = timer_handle 48 | 49 | def cancel(self): 50 | if not self.timer_handle.closed: 51 | self.timer_handle.stop() 52 | self.timer_handle.close() 53 | 54 | 55 | class Hub(abc.AbstractHub): 56 | def __init__(self): 57 | super().__init__() 58 | self.Listener = UvFdListener 59 | self.stopping = False 60 | self.running = False 61 | self.callbacks = [] 62 | 63 | #: :type: pyuv.Loop 64 | self.loop = pyuv_cffi.Loop.default_loop() 65 | 66 | # create a signal handle to listen for SIGINT 67 | self.sig_h = pyuv_cffi.Signal(self.loop) 68 | self.sig_h.start(self.signal_received, signal.SIGINT) 69 | self.sig_h.ref = False # don't keep loop alive just for this handle 70 | 71 | # create a uv_idle handle to allow non-I/O callbacks to get called quickly 72 | self.idle_h = pyuv_cffi.Idle(self.loop) 73 | 74 | # create a prepare handle to fire immediate callbacks every loop iteration 75 | self.prepare_h = pyuv_cffi.Prepare(self.loop) 76 | self.prepare_h.start(self._fire_callbacks) 77 | 78 | # create a check handle to unref the prepare handle and allow the loop to exit if necessary 79 | self.check_h = pyuv_cffi.Check(self.loop) 80 | self.check_h.start(self._check_cb) 81 | self.check_h.ref = False 82 | 83 | def _idle_cb(self, idle_h): 84 | idle_h.stop() 85 | 86 | def _check_cb(self, check_h): 87 | """ 88 | The Prepare handle's only purpose is to run scheduled callbacks. If there are no 89 | remaining scheduled callbacks, then it must be unreferenced so it does not keep the loop 90 | alive after all handles and callbacks have been completed. 91 | """ 92 | self.prepare_h.ref = bool(self.callbacks) 93 | 94 | def run(self): 95 | assert self is greenlet.getcurrent() 96 | 97 | if self.stopping: 98 | return 99 | 100 | if self.running: 101 | raise RuntimeError("The hub's runloop is already running") 102 | 103 | log.debug('Start runloop') 104 | try: 105 | self.running = True 106 | self.stopping = False 107 | self.loop.run(pyuv_cffi.UV_RUN_DEFAULT) 108 | finally: 109 | self.running = False 110 | self.stopping = False 111 | 112 | def abort(self): 113 | print() 114 | log.debug('Abort loop') 115 | if self.running: 116 | self.stopping = True 117 | 118 | self.loop.stop() 119 | 120 | def _fire_callbacks(self, prepare_h): 121 | """Fire immediate callbacks 122 | 123 | This is called by `self.prepare_h` and calls callbacks scheduled by methods such as 124 | :meth:`schedule_call_now()` or `gyield()`. 125 | """ 126 | callbacks = self.callbacks 127 | self.callbacks = [] 128 | for cb, args, kwargs in callbacks: 129 | try: 130 | cb(*args, **kwargs) 131 | except: 132 | self._squelch_exception(sys.exc_info()) 133 | 134 | # Check if more callbacks have been scheduled by the callbacks that were just executed. 135 | # Since these may be non-I/O callbacks (such as calls to `gyield()` or 136 | # `schedule_call_now()`, start a uv_idle_t handle so that libuv can do a zero-timeout poll 137 | # and quickly start another loop iteration. 138 | if self.callbacks: 139 | self.idle_h.start(self._idle_cb) 140 | 141 | # If no callbacks are scheduled, unref the prepare_h to allow the loop to automatically 142 | # exit safely. 143 | prepare_h.ref = bool(self.callbacks) 144 | 145 | def schedule_call_now(self, cb, *args, **kwargs): 146 | self.callbacks.append((cb, args, kwargs)) 147 | 148 | def schedule_call_global(self, seconds, cb, *args, **kwargs): 149 | def timer_callback(timer_h): 150 | try: 151 | cb(*args, **kwargs) 152 | except: 153 | self._squelch_exception(sys.exc_info()) 154 | 155 | # required for cleanup 156 | if not timer_h.closed: 157 | timer_h.stop() 158 | timer_h.close() 159 | 160 | timer_handle = pyuv_cffi.Timer(self.loop) 161 | timer_handle.start(timer_callback, seconds, 0) 162 | 163 | return Timer(timer_handle) 164 | 165 | def add(self, evtype, fd, cb, tb, cb_args=()): 166 | poll_h = pyuv_cffi.Poll(self.loop, fd) 167 | listener = UvFdListener(evtype, fd, poll_h) 168 | 169 | def poll_cb(poll_h, events, errno): 170 | """Read callback for pyuv 171 | 172 | pyuv requires a callback with this signature 173 | 174 | :type poll_h: pyuv.Poll 175 | :type events: int 176 | :type errno: int 177 | """ 178 | try: 179 | cb(*cb_args) 180 | except: 181 | self._squelch_exception(sys.exc_info()) 182 | 183 | try: 184 | self.remove(listener) 185 | except Exception as e: 186 | sys.stderr.write('Exception while removing listener: {}\n'.format(e)) 187 | sys.stderr.flush() 188 | 189 | self._add_listener(listener) 190 | 191 | # start the pyuv Poll object 192 | # note that UV_READABLE and UV_WRITABLE correspond to const.READ and const.WRITE 193 | poll_h.start(evtype, poll_cb) 194 | 195 | # self.debug() 196 | return listener 197 | 198 | def remove(self, listener): 199 | """Remove listener 200 | 201 | :param listener: listener to remove 202 | :type listener: self.Listener 203 | """ 204 | super()._remove_listener(listener) 205 | # log.debug('call w.handle.stop(), fd: {}'.format(listener.handle.fileno())) 206 | 207 | # initiate correct cleanup sequence (these three statements are critical) 208 | listener.handle.ref = False 209 | listener.handle.stop() 210 | listener.handle.close() 211 | listener.handle = None 212 | # self.debug() 213 | 214 | def signal_received(self, sig_handle, signo): 215 | """Signal handler for pyuv.Signal 216 | 217 | pyuv.Signal requies a callback with the following signature:: 218 | 219 | Callable(signal_handle, signal_num) 220 | 221 | :type sig_handle: pyuv.Signal 222 | :type signo: int 223 | """ 224 | if signo == signal.SIGINT: 225 | sig_handle.stop() 226 | self.abort() 227 | self.parent.throw(KeyboardInterrupt) 228 | -------------------------------------------------------------------------------- /guv/hubs/switch.py: -------------------------------------------------------------------------------- 1 | import greenlet 2 | 3 | from .hub import get_hub 4 | from ..timeout import Timeout 5 | 6 | __all__ = ['gyield', 'trampoline'] 7 | 8 | 9 | def gyield(switch_back=True): 10 | """Yield to other greenlets 11 | 12 | This is a cooperative yield which suspends the current greenlet and allows other greenlets to 13 | run by switching to the hub. 14 | 15 | - If `switch_back` is True (default), the current greenlet is resumed at the beginning of the 16 | next event loop iteration, before the loop polls for I/O and calls any I/O callbacks. This 17 | is the intended use for this function the vast majority of the time. 18 | - If `switch_back` is False, the hub will will never resume the current greenlet (use with 19 | caution). This is mainly useful for situations where other greenlets (not the hub) are 20 | responsible for switching back to this greenlet. An example is the Event class, 21 | where waiters are switched to when the event is ready. 22 | 23 | :param bool switch_back: automatically switch back to this greenlet on the next event loop cycle 24 | """ 25 | current = greenlet.getcurrent() 26 | hub = get_hub() 27 | if switch_back: 28 | hub.schedule_call_now(current.switch) 29 | hub.switch() 30 | 31 | 32 | def trampoline(fd, evtype, timeout=None, timeout_exc=Timeout): 33 | """Jump from the current greenlet to the hub and wait until the given file descriptor is ready 34 | for I/O, or the specified timeout elapses 35 | 36 | If the specified `timeout` elapses before the socket is ready to read or write, `timeout_exc` 37 | will be raised instead of :func:`trampoline()` returning normally. 38 | 39 | When the specified file descriptor is ready for I/O, the hub internally calls the callback to 40 | switch back to the current (this) greenlet. 41 | 42 | Conditions: 43 | 44 | - must not be called from the hub greenlet (can be called from any other greenlet) 45 | - `evtype` must be either :attr:`~guv.const.READ` or :attr:`~guv.const.WRITE` (not possible to 46 | watch for both simultaneously) 47 | 48 | :param int fd: file descriptor 49 | :param int evtype: either the constant :attr:`~guv.const.READ` or :attr:`~guv.const.WRITE` 50 | :param float timeout: (optional) maximum time to wait in seconds 51 | :param Exception timeout_exc: (optional) timeout Exception class 52 | """ 53 | #: :type: AbstractHub 54 | hub = get_hub() 55 | current = greenlet.getcurrent() 56 | 57 | assert hub is not current, 'do not call blocking functions from the mainloop' 58 | assert isinstance(fd, int) 59 | 60 | timer = None 61 | if timeout is not None: 62 | def _timeout(exc): 63 | # timeout has passed 64 | current.throw(exc) 65 | 66 | timer = hub.schedule_call_global(timeout, _timeout, timeout_exc) 67 | 68 | try: 69 | # add a watcher for this file descriptor 70 | listener = hub.add(evtype, fd, current.switch, current.throw) 71 | 72 | # switch to the hub 73 | try: 74 | return hub.switch() 75 | finally: 76 | # log.debug('(trampoline finally) remove listener for fd: {}'.format(fd)) 77 | hub.remove(listener) 78 | finally: 79 | if timer is not None: 80 | timer.cancel() 81 | -------------------------------------------------------------------------------- /guv/hubs/timer.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | import io 3 | import greenlet 4 | import time 5 | import functools 6 | 7 | from . import abc 8 | from guv import compat 9 | 10 | _g_debug = False # if true, captures a stack trace for each timer when constructed, this is useful 11 | # for debugging leaking timers, to find out where the timer was set up 12 | 13 | compat.patch() 14 | 15 | 16 | @functools.total_ordering 17 | class Timer(abc.AbstractTimer): 18 | """Simple Timer class for setting up a callback to be called after the specified amount of time 19 | has passed 20 | 21 | Calling the timer object will call the callback 22 | """ 23 | 24 | def __init__(self, seconds, cb, *args, **kwargs): 25 | """ 26 | :param float seconds: minimum number of seconds to wait before calling 27 | :param Callable cb: callback to call when the timer has expired 28 | :param args: positional arguments to pass to cb 29 | :param kwargs: keyword arguments to pass to cb 30 | 31 | This timer will not be run unless it is scheduled calling :meth:`schedule`. 32 | """ 33 | self.seconds = seconds 34 | self.absolute_time = time.monotonic() + seconds # absolute time to fire the timer 35 | self.tpl = cb, args, kwargs 36 | 37 | self.called = False 38 | 39 | if _g_debug: 40 | self.traceback = io.StringIO() 41 | traceback.print_stack(file=self.traceback) 42 | 43 | @property 44 | def pending(self): 45 | return not self.called 46 | 47 | def __repr__(self): 48 | secs = getattr(self, 'seconds', None) 49 | cb, args, kw = getattr(self, 'tpl', (None, None, None)) 50 | retval = "Timer(%s, %s, *%s, **%s)" % ( 51 | secs, cb, args, kw) 52 | if _g_debug and hasattr(self, 'traceback'): 53 | retval += '\n' + self.traceback.getvalue() 54 | return retval 55 | 56 | def copy(self): 57 | cb, args, kw = self.tpl 58 | return self.__class__(self.seconds, cb, *args, **kw) 59 | 60 | def cancel(self): 61 | self.called = True 62 | 63 | def __call__(self, *args): 64 | if not self.called: 65 | self.called = True 66 | cb, args, kw = self.tpl 67 | try: 68 | cb(*args, **kw) 69 | finally: 70 | try: 71 | del self.tpl 72 | except AttributeError: 73 | pass 74 | 75 | def __lt__(self, other): 76 | """ 77 | No default ordering in Python 3.x; heapq uses < 78 | 79 | :type other: Timer 80 | """ 81 | if isinstance(other, float): 82 | return self.absolute_time < other 83 | else: 84 | return self.absolute_time < other.absolute_time 85 | 86 | 87 | class LocalTimer(Timer): 88 | def __init__(self, seconds, cb, *args, **kwargs): 89 | self.greenlet = greenlet.getcurrent() 90 | super().__init__(seconds, cb, *args, **kwargs) 91 | 92 | @property 93 | def pending(self): 94 | if self.greenlet is None or self.greenlet.dead: 95 | return False 96 | return not self.called 97 | 98 | def __call__(self, *args): 99 | if not self.called: 100 | self.called = True 101 | if self.greenlet is not None and self.greenlet.dead: 102 | return 103 | cb, args, kw = self.tpl 104 | cb(*args, **kw) 105 | 106 | def cancel(self): 107 | self.greenlet = None 108 | super().cancel() 109 | -------------------------------------------------------------------------------- /guv/server.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import logging 3 | from abc import ABCMeta, abstractmethod 4 | 5 | from . import greenpool, patcher, greenthread 6 | from .green import socket, ssl 7 | from .hubs import get_hub 8 | 9 | original_socket = patcher.original('socket') 10 | 11 | log = logging.getLogger('guv') 12 | 13 | 14 | def serve(sock, handle, concurrency=1000): 15 | pool = greenpool.GreenPool(concurrency) 16 | server = Server(sock, handle, pool, 'spawn_n') 17 | server.start() 18 | 19 | 20 | def listen(addr, family=socket.AF_INET, backlog=511): 21 | server_sock = socket.socket(family, socket.SOCK_STREAM) 22 | 23 | if sys.platform[:3] != 'win': 24 | server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 25 | 26 | server_sock.bind(addr) 27 | server_sock.listen(backlog) 28 | 29 | return server_sock 30 | 31 | 32 | def connect(addr, family=socket.AF_INET, bind=None): 33 | """Convenience function for opening client sockets. 34 | 35 | :param addr: Address of the server to connect to. For TCP sockets, this is a (host, 36 | port) tuple. 37 | :param family: Socket family, optional. See :mod:`socket` documentation for available families. 38 | :param bind: Local address to bind to, optional. 39 | :return: The connected green socket object. 40 | """ 41 | sock = socket.socket(family, socket.SOCK_STREAM) 42 | if bind is not None: 43 | sock.bind(bind) 44 | sock.connect(addr) 45 | return sock 46 | 47 | 48 | def wrap_ssl(sock, *a, **kw): 49 | """Convenience function for converting a regular socket into an SSL socket 50 | 51 | The preferred idiom is to call wrap_ssl directly on the creation method, e.g., 52 | ``wrap_ssl(connect(addr))`` or ``wrap_ssl(listen(addr), server_side=True)``. This way there is 53 | no "naked" socket sitting around to accidentally corrupt the SSL session. 54 | 55 | :return Green SSL socket 56 | """ 57 | return ssl.wrap_socket(sock, *a, **kw) 58 | 59 | 60 | class StopServe(Exception): 61 | """Exception class used for quitting :func:`~guv.serve` gracefully 62 | """ 63 | pass 64 | 65 | 66 | class AbstractServer(metaclass=ABCMeta): 67 | def __init__(self, server_sock, client_handler_cb, pool=None, spawn=None): 68 | """ 69 | If pool and spawn are None (default), bare greenlets will be used and the spawn mechanism 70 | will be greenlet.switch(). This is the simplest and most direct way to spawn greenlets to 71 | handle client requests, however it is not the most stable. 72 | 73 | If more control is desired over client handlers, specify a greenlet pool class such as 74 | `GreenPool`, and specify a spawn mechanism. If specifying a pool class, the the name of the 75 | spawn method must be passed as well. 76 | 77 | The signature of client_handler_cb is as follows:: 78 | 79 | Callable(sock: socket, addr: tuple[str, int]) -> None 80 | 81 | :param pool: greenlet pool class or None 82 | :param spawn: 'spawn', or 'spawn_n', or None 83 | :type spawn: str or None 84 | """ 85 | self.server_sock = server_sock 86 | self.client_handler_cb = client_handler_cb 87 | 88 | if pool is None: 89 | # use bare greenlets 90 | self.pool = None 91 | log.debug('Server: use fast spawn_n') 92 | else: 93 | # create a pool instance 94 | self.pool = pool 95 | self.spawn = getattr(self.pool, spawn) 96 | log.debug('Server: use {}.{}'.format(pool, spawn)) 97 | 98 | self.hub = get_hub() 99 | 100 | self.address = server_sock.getsockname()[:2] 101 | 102 | @abstractmethod 103 | def start(self): 104 | """Start the server 105 | """ 106 | pass 107 | 108 | @abstractmethod 109 | def stop(self): 110 | """Stop the server 111 | """ 112 | 113 | def handle_error(self, msg, level=logging.ERROR, exc_info=True): 114 | log.log(level, '{0}: {1} --> closing'.format(self, msg), exc_info=exc_info) 115 | self.stop() 116 | 117 | def _spawn(self, client_sock, addr): 118 | """Spawn a client handler using the appropriate spawn mechanism 119 | 120 | :param client_sock: client socket 121 | :type client_sock: socket.socket 122 | :param addr: address tuple 123 | :type addr: tuple[str, int] 124 | """ 125 | if self.pool is None: 126 | greenthread.spawn_n(self.client_handler_cb, client_sock, addr) 127 | else: 128 | self.spawn(self.client_handler_cb, client_sock, addr) 129 | 130 | 131 | class Server(AbstractServer): 132 | """Standard server implementation not directly dependent on pyuv 133 | """ 134 | 135 | def start(self): 136 | log.debug('{0.__class__.__name__} started on {0.address}'.format(self)) 137 | while True: 138 | try: 139 | client_sock, addr = self.server_sock.accept() 140 | self._spawn(client_sock, addr) 141 | except StopServe: 142 | log.debug('{0} stopped'.format(self)) 143 | return 144 | 145 | def stop(self): 146 | log.debug('{0}: stopping'.format(self)) 147 | -------------------------------------------------------------------------------- /guv/support/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | PYPY = hasattr(sys, 'pypy_version_info') 4 | 5 | OS_WINDOWS = sys.platform == 'win32' 6 | 7 | 8 | def get_errno(e): 9 | """Get the error code out of socket.error objects 10 | """ 11 | return e.args[0] 12 | 13 | 14 | def reraise(tp, value, tb=None): 15 | if value is None: 16 | value = tp() 17 | if value.__traceback__ is not tb: 18 | raise value.with_traceback(tb) 19 | raise value 20 | -------------------------------------------------------------------------------- /guv/support/cassandra.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | import logging 3 | 4 | from cassandra import OperationTimedOut 5 | from cassandra.connection import Connection, ConnectionShutdown 6 | from cassandra.protocol import RegisterMessage 7 | import guv.hubs 8 | from guv.green import socket, ssl 9 | from guv.event import TEvent 10 | from guv.queue import Queue 11 | from guv.support import get_errno 12 | from guv.exceptions import CONNECT_ERR 13 | 14 | log = logging.getLogger(__name__) 15 | 16 | 17 | class GuvConnection(Connection): 18 | """ 19 | An implementation of :class:`.Connection` that utilizes ``guv``. 20 | """ 21 | 22 | _total_reqd_bytes = 0 23 | _read_watcher = None 24 | _write_watcher = None 25 | _socket = None 26 | 27 | @classmethod 28 | def factory(cls, *args, **kwargs): 29 | timeout = kwargs.pop('timeout', 5.0) 30 | conn = cls(*args, **kwargs) 31 | conn.connected_event.wait(timeout) 32 | if conn.last_error: 33 | raise conn.last_error 34 | elif not conn.connected_event.is_set(): 35 | conn.close() 36 | raise OperationTimedOut('Timed out creating connection') 37 | else: 38 | return conn 39 | 40 | def __init__(self, *args, **kwargs): 41 | guv.hubs.get_hub() 42 | 43 | super().__init__(*args, **kwargs) 44 | 45 | self.connected_event = TEvent() 46 | self._write_queue = Queue() 47 | 48 | self._callbacks = {} 49 | self._push_watchers = defaultdict(set) 50 | 51 | sockerr = None 52 | addresses = socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM) 53 | for (af, socktype, proto, canonname, sockaddr) in addresses: 54 | try: 55 | self._socket = socket.socket(af, socktype, proto) 56 | if self.ssl_options: 57 | self._socket = ssl.wrap_socket(self._socket, **self.ssl_options) 58 | self._socket.settimeout(1.0) 59 | self._socket.connect(sockaddr) 60 | sockerr = None 61 | break 62 | except socket.error as err: 63 | sockerr = err 64 | if sockerr: 65 | msg = 'Tried connecting to {}. Last error: {}'.format([a[4] for a in addresses], 66 | sockerr.strerror) 67 | raise socket.error(sockerr.errno, msg) 68 | 69 | if self.sockopts: 70 | for args in self.sockopts: 71 | self._socket.setsockopt(*args) 72 | 73 | self._read_watcher = guv.spawn(self.handle_read) 74 | self._write_watcher = guv.spawn(self.handle_write) 75 | self._send_options_message() 76 | 77 | log.debug('Create Cassandra GuvConnection ({})'.format(id(self))) 78 | 79 | def close(self): 80 | with self.lock: 81 | if self.is_closed: 82 | return 83 | self.is_closed = True 84 | 85 | log.debug('Closing connection (%s) to %s' % (id(self), self.host)) 86 | if self._read_watcher: 87 | self._read_watcher.kill() 88 | if self._write_watcher: 89 | self._write_watcher.kill() 90 | if self._socket: 91 | self._socket.close() 92 | log.debug('Closed socket to %s' % (self.host,)) 93 | 94 | if not self.is_defunct: 95 | self.error_all_callbacks(ConnectionShutdown('Connection to %s was closed' % self.host)) 96 | # don't leave in-progress operations hanging 97 | self.connected_event.set() 98 | 99 | def handle_close(self): 100 | log.debug('connection closed by server') 101 | self.close() 102 | 103 | def handle_write(self): 104 | while True: 105 | try: 106 | next_msg = self._write_queue.get() 107 | # trampoline with WRITE here causes a core dump (why???) issue #13 108 | # log.debug('Trampoline with fd: {}, WRITE'.format(self._socket.fileno())) 109 | # trampoline(self._socket.fileno(), WRITE) 110 | except Exception as e: 111 | if not self.is_closed: 112 | log.debug('Exception during write trampoline() for %s: %s', self, e) 113 | self.defunct(e) 114 | return 115 | 116 | try: 117 | self._socket.sendall(next_msg) 118 | except socket.error as err: 119 | log.debug('Exception during socket sendall for %s: %s', self, err) 120 | self.defunct(err) 121 | return # leave the write loop 122 | 123 | def handle_read(self): 124 | while True: 125 | # try: 126 | # # log.debug('Trampoline with fd: {}, READ'.format(self._socket.fileno())) 127 | # # trampoline(self._socket.fileno(), READ) 128 | # pass 129 | # except Exception as exc: 130 | # if not self.is_closed: 131 | # log.debug("Exception during read trampoline() for %s: %s", self, exc) 132 | # self.defunct(exc) 133 | # return 134 | 135 | try: 136 | while True: 137 | buf = self._socket.recv(self.in_buffer_size) 138 | self._iobuf.write(buf) 139 | if len(buf) < self.in_buffer_size: 140 | break 141 | except socket.error as err: 142 | if not get_errno(err) in CONNECT_ERR: 143 | log.debug('Exception during socket recv for %s: %s', self, err) 144 | self.defunct(err) 145 | return # leave the read loop 146 | 147 | if self._iobuf.tell(): 148 | self.process_io_buffer() 149 | else: 150 | log.debug('Connection %s closed by server', self) 151 | self.close() 152 | return 153 | 154 | def push(self, data): 155 | chunk_size = self.out_buffer_size 156 | for i in range(0, len(data), chunk_size): 157 | self._write_queue.put(data[i:i + chunk_size]) 158 | 159 | def register_watcher(self, event_type, callback, register_timeout=None): 160 | self._push_watchers[event_type].add(callback) 161 | self.wait_for_response(RegisterMessage(event_list=[event_type]), 162 | timeout=register_timeout) 163 | 164 | def register_watchers(self, type_callback_dict, register_timeout=None): 165 | for event_type, callback in type_callback_dict.items(): 166 | self._push_watchers[event_type].add(callback) 167 | 168 | self.wait_for_response(RegisterMessage(event_list=type_callback_dict.keys()), 169 | timeout=register_timeout) 170 | -------------------------------------------------------------------------------- /guv/support/psycopg2_patcher.py: -------------------------------------------------------------------------------- 1 | """A wait callback to allow psycopg2 cooperation with guv 2 | 3 | Use :func:`make_psycopg_green()` to enable guv support in psycopg2. 4 | """ 5 | 6 | import psycopg2 7 | from psycopg2 import extensions 8 | 9 | from ..hubs import trampoline 10 | from ..const import READ, WRITE 11 | 12 | 13 | def psycopg2_wait_cb(conn, timeout=-1): 14 | """A wait callback to trigger a yield while waiting for the database to respond 15 | """ 16 | while True: 17 | state = conn.poll() 18 | if state == extensions.POLL_OK: 19 | break 20 | elif state == extensions.POLL_READ: 21 | trampoline(conn.fileno(), READ) 22 | elif state == extensions.POLL_WRITE: 23 | trampoline(conn.fileno(), WRITE) 24 | else: 25 | raise psycopg2.OperationalError('Bad result from poll: {}'.format(state)) 26 | 27 | 28 | def make_psycopg_green(): 29 | """Configure psycopg2 to call our wait function 30 | """ 31 | if not hasattr(extensions, 'set_wait_callback'): 32 | raise ImportError('support for coroutines not available in this psycopg version ({})' 33 | .format(psycopg2.__version__)) 34 | 35 | extensions.set_wait_callback(psycopg2_wait_cb) 36 | 37 | -------------------------------------------------------------------------------- /guv/timeout.py: -------------------------------------------------------------------------------- 1 | import greenlet 2 | 3 | from .hubs.hub import get_hub 4 | 5 | __all__ = ['Timeout', 'with_timeout'] 6 | 7 | _NONE = object() 8 | 9 | # deriving from BaseException so that "except Exception as e" doesn't catch 10 | # Timeout exceptions. 11 | 12 | 13 | class Timeout(BaseException): 14 | """Raise `exception` in the current greenthread after `timeout` seconds. 15 | 16 | When `exception` is omitted or ``None``, the :class:`Timeout` instance itself is raised. If 17 | `seconds` is None, the timer is not scheduled, and is only useful if you're planning to raise it 18 | directly. 19 | 20 | Timeout objects are context managers, and so can be used in with statements. When used in a with 21 | statement, if `exception` is ``False``, the timeout is still raised, but the context manager 22 | suppresses it, so the code outside the with-block won't see it. 23 | """ 24 | 25 | def __init__(self, seconds=None, exception=None): 26 | """ 27 | :param float seconds: timeout seconds 28 | :param exception: exception to raise when timeout occurs 29 | """ 30 | self.seconds = seconds 31 | self.exception = exception 32 | self.timer = None 33 | self.start() 34 | 35 | def start(self): 36 | """Schedule the timeout. This is called on construction, so 37 | it should not be called explicitly, unless the timer has been 38 | canceled.""" 39 | assert not self.pending, \ 40 | '%r is already started; to restart it, cancel it first' % self 41 | if self.seconds is None: # "fake" timeout (never expires) 42 | self.timer = None 43 | elif self.exception is None or isinstance(self.exception, bool): # timeout that raises self 44 | self.timer = get_hub().schedule_call_global( 45 | self.seconds, greenlet.getcurrent().throw, self) 46 | else: # regular timeout with user-provided exception 47 | self.timer = get_hub().schedule_call_global( 48 | self.seconds, greenlet.getcurrent().throw, self.exception) 49 | return self 50 | 51 | @property 52 | def pending(self): 53 | """True if the timeout is scheduled to be raised 54 | """ 55 | if self.timer is not None: 56 | return self.timer.pending 57 | else: 58 | return False 59 | 60 | def cancel(self): 61 | """If the timeout is pending, cancel it 62 | 63 | If not using Timeouts in ``with`` statements, always call cancel() in a ``finally`` after 64 | the block of code that is getting timed out. If not canceled, the timeout will be raised 65 | later on, in some unexpected section of the application. 66 | """ 67 | if self.timer is not None: 68 | self.timer.cancel() 69 | self.timer = None 70 | 71 | def __repr__(self): 72 | classname = self.__class__.__name__ 73 | if self.pending: 74 | pending = ' pending' 75 | else: 76 | pending = '' 77 | if self.exception is None: 78 | exception = '' 79 | else: 80 | exception = ' exception=%r' % self.exception 81 | return '<%s at %s seconds=%s%s%s>' % ( 82 | classname, hex(id(self)), self.seconds, exception, pending) 83 | 84 | def __str__(self): 85 | if self.seconds is None: 86 | return '' 87 | if self.seconds == 1: 88 | suffix = '' 89 | else: 90 | suffix = 's' 91 | if self.exception is None or self.exception is True: 92 | return '%s second%s' % (self.seconds, suffix) 93 | elif self.exception is False: 94 | return '%s second%s (silent)' % (self.seconds, suffix) 95 | else: 96 | return '%s second%s (%s)' % (self.seconds, suffix, self.exception) 97 | 98 | def __enter__(self): 99 | if self.timer is None: 100 | self.start() 101 | return self 102 | 103 | def __exit__(self, typ, value, tb): 104 | self.cancel() 105 | if value is self and self.exception is False: 106 | return True 107 | 108 | 109 | def with_timeout(seconds, function, *args, **kwds): 110 | """Wrap a call to some (yielding) function with a timeout 111 | 112 | If the called function fails to return before the timeout, cancel it and return a flag value. 113 | """ 114 | timeout_value = kwds.pop("timeout_value", _NONE) 115 | timeout = Timeout(seconds) 116 | try: 117 | try: 118 | return function(*args, **kwds) 119 | except Timeout as ex: 120 | if ex is timeout and timeout_value is not _NONE: 121 | return timeout_value 122 | raise 123 | finally: 124 | timeout.cancel() 125 | -------------------------------------------------------------------------------- /guv/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veegee/guv/d7bac2ca6a73cc2059969af08223b82f3e187922/guv/util/__init__.py -------------------------------------------------------------------------------- /guv/util/debug.py: -------------------------------------------------------------------------------- 1 | """Module containing utilities and functions for better debugging guv-powered applications 2 | """ 3 | import os 4 | import sys 5 | import linecache 6 | import re 7 | import inspect 8 | import gc 9 | import greenlet 10 | import traceback 11 | import logging 12 | 13 | __all__ = ['spew', 'unspew', 'format_hub_listeners', 'format_hub_timers', 14 | 'hub_listener_stacks', 'hub_exceptions', 15 | 'hub_prevent_multiple_readers', 'hub_timer_stacks', 16 | 'hub_blocking_detection'] 17 | 18 | _token_splitter = re.compile('\W+') 19 | 20 | log = logging.getLogger('guv') 21 | 22 | 23 | def print_greenlet_strace(): 24 | num_greenlets = 0 25 | 26 | for ob in gc.get_objects(): 27 | if not isinstance(ob, greenlet.greenlet): 28 | continue 29 | 30 | if not ob: 31 | continue 32 | 33 | num_greenlets += 1 34 | print(''.join(traceback.format_stack(ob.gr_frame))) 35 | 36 | print('greenlet count: {}'.format(num_greenlets)) 37 | 38 | 39 | class Spew(object): 40 | def __init__(self, trace_names=None, show_values=True): 41 | self.trace_names = trace_names 42 | self.show_values = show_values 43 | 44 | def __call__(self, frame, event, arg): 45 | if event == 'line': 46 | lineno = frame.f_lineno 47 | if '__file__' in frame.f_globals: 48 | filename = frame.f_globals['__file__'] 49 | if (filename.endswith('.pyc') or 50 | filename.endswith('.pyo')): 51 | filename = filename[:-1] 52 | name = frame.f_globals['__name__'] 53 | line = linecache.getline(filename, lineno) 54 | else: 55 | name = '[unknown]' 56 | try: 57 | src = inspect.getsourcelines(frame) 58 | line = src[lineno] 59 | except IOError: 60 | line = 'Unknown code named [%s]. VM instruction #%d' % ( 61 | frame.f_code.co_name, frame.f_lasti) 62 | if self.trace_names is None or name in self.trace_names: 63 | print('%s:%s: %s' % (name, lineno, line.rstrip())) 64 | if not self.show_values: 65 | return self 66 | details = [] 67 | tokens = _token_splitter.split(line) 68 | for tok in tokens: 69 | if tok in frame.f_globals: 70 | details.append('%s=%r' % (tok, frame.f_globals[tok])) 71 | if tok in frame.f_locals: 72 | details.append('%s=%r' % (tok, frame.f_locals[tok])) 73 | if details: 74 | print("\t%s" % ' '.join(details)) 75 | return self 76 | 77 | 78 | def spew(trace_names=None, show_values=False): 79 | """Install a trace hook which writes incredibly detailed logs 80 | about what code is being executed to stdout. 81 | """ 82 | sys.settrace(Spew(trace_names, show_values)) 83 | 84 | 85 | def unspew(): 86 | """Remove the trace hook installed by spew. 87 | """ 88 | sys.settrace(None) 89 | 90 | 91 | def format_hub_listeners(): 92 | """ Returns a formatted string of the current listeners on the current 93 | hub. This can be useful in determining what's going on in the event system, 94 | especially when used in conjunction with :func:`hub_listener_stacks`. 95 | """ 96 | from guv import hubs 97 | 98 | hub = hubs.get_hub() 99 | result = ['READERS:'] 100 | for l in hub.get_readers(): 101 | result.append(repr(l)) 102 | result.append('WRITERS:') 103 | for l in hub.get_writers(): 104 | result.append(repr(l)) 105 | return os.linesep.join(result) 106 | 107 | 108 | def format_hub_timers(): 109 | """ Returns a formatted string of the current timers on the current 110 | hub. This can be useful in determining what's going on in the event system, 111 | especially when used in conjunction with :func:`hub_timer_stacks`. 112 | """ 113 | from guv import hubs 114 | 115 | hub = hubs.get_hub() 116 | result = ['TIMERS:'] 117 | for l in hub.timers: 118 | result.append(repr(l)) 119 | return os.linesep.join(result) 120 | 121 | 122 | def hub_listener_stacks(state=False): 123 | """Toggles whether or not the hub records the stack when clients register 124 | listeners on file descriptors. This can be useful when trying to figure 125 | out what the hub is up to at any given moment. To inspect the stacks 126 | of the current listeners, call :func:`format_hub_listeners` at critical 127 | junctures in the application logic. 128 | """ 129 | from guv import hubs 130 | 131 | hubs.get_hub().set_debug_listeners(state) 132 | 133 | 134 | def hub_timer_stacks(state=False): 135 | """Toggles whether or not the hub records the stack when timers are set. 136 | To inspect the stacks of the current timers, call :func:`format_hub_timers` 137 | at critical junctures in the application logic. 138 | """ 139 | from guv.hubs import timer 140 | 141 | timer._g_debug = state 142 | 143 | 144 | def hub_prevent_multiple_readers(state=True): 145 | """Toggle prevention of multiple greenlets reading from a socket 146 | 147 | When multiple greenlets read from the same socket it is often hard 148 | to predict which greenlet will receive what data. To achieve 149 | resource sharing consider using ``guv.pools.Pool`` instead. 150 | 151 | But if you really know what you are doing you can change the state 152 | to ``False`` to stop the hub from protecting against this mistake. 153 | """ 154 | from guv.hubs import hub 155 | 156 | hub.g_prevent_multiple_readers = state 157 | 158 | 159 | def hub_exceptions(state=True): 160 | """Toggles whether the hub prints exceptions that are raised from its 161 | timers. This can be useful to see how greenthreads are terminating. 162 | """ 163 | from guv import hubs 164 | 165 | hubs.get_hub().set_timer_exceptions(state) 166 | from guv import greenpool 167 | 168 | greenpool.DEBUG = state 169 | 170 | 171 | def hub_blocking_detection(state=False, resolution=1): 172 | """Toggles whether Eventlet makes an effort to detect blocking 173 | behavior in an application. 174 | 175 | It does this by telling the kernel to raise a SIGALARM after a 176 | short timeout, and clearing the timeout every time the hub 177 | greenlet is resumed. Therefore, any code that runs for a long 178 | time without yielding to the hub will get interrupted by the 179 | blocking detector (don't use it in production!). 180 | 181 | The *resolution* argument governs how long the SIGALARM timeout 182 | waits in seconds. The implementation uses :func:`signal.setitimer` 183 | and can be specified as a floating-point value. 184 | The shorter the resolution, the greater the chance of false 185 | positives. 186 | """ 187 | from guv import hubs 188 | 189 | assert resolution > 0 190 | hubs.get_hub().debug_blocking = state 191 | hubs.get_hub().debug_blocking_resolution = resolution 192 | if not state: 193 | hubs.get_hub().block_detect_post() 194 | -------------------------------------------------------------------------------- /guv/util/decorators.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from functools import wraps 3 | import inspect 4 | import re 5 | 6 | log = logging.getLogger('guv') 7 | 8 | use_newlines = False 9 | indent = ' ' # indent character(s) 10 | max_param_len = 20 11 | log_function_start = True # log when the function is called 12 | log_function_exit = True # log when the function exits 13 | 14 | # colour constants 15 | RESET = '\x1B[0m' # INFO 16 | RED = '\x1B[31m' # ERROR, CRITICAL, FATAL 17 | GREEN = '\x1B[32m' # INFO 18 | YELLOW = '\x1B[33m' # WARNING 19 | BLUE = '\x1B[34m' # INFO 20 | MAGENTA = '\x1B[35m' # INFO 21 | CYAN = '\x1B[36m' # INFO 22 | WHITE = '\x1B[37m' # INFO 23 | BRGREEN = '\x1B[01;32m' # DEBUG (grey in solarized for terminals) 24 | 25 | # regex for extracting from > 26 | r_of = re.compile('<.*(?= of <)') 27 | 28 | # regex for extracting from <__main__.A object at 0x7fc54e28be80> 29 | r_at = re.compile('<.*(?= at )') 30 | 31 | 32 | def parse_repr(obj): 33 | if inspect.ismethod(obj): 34 | pat = r_of 35 | else: 36 | pat = r_at 37 | 38 | s = repr(obj) 39 | m = re.search(pat, s) 40 | if m: 41 | return '{}>'.format(m.group()) 42 | else: 43 | return s 44 | 45 | 46 | def format_arg(arg): 47 | """Convert `arg` to a string 48 | 49 | If arg is a simple object, 50 | """ 51 | s = str(arg) 52 | if type(arg) is type: 53 | # convert to string directly; the string isn't very long 54 | return s 55 | elif isinstance(arg, object) and len(s) > max_param_len: 56 | # format into a shorter representation 57 | return parse_repr(arg) 58 | else: 59 | # the string representation of `arg` is short enough to display directly 60 | return s 61 | 62 | 63 | def func_name(f): 64 | """Get qualified name of function 65 | 66 | :param f: function 67 | """ 68 | if hasattr(f, '__qualname__'): 69 | # for Python >= 3.3 70 | qualname = RESET + f.__qualname__ + BRGREEN 71 | else: 72 | # for Python < 3.3 73 | qualname = RESET + f.__name__ + BRGREEN 74 | 75 | return qualname 76 | 77 | 78 | def log_start(f, args, kwargs): 79 | argspec = inspect.getargspec(f) 80 | 81 | # check if this is a function or a method 82 | method = False 83 | if argspec.args and argspec.args[0] == 'self': 84 | method = True 85 | 86 | qualname = func_name(f) 87 | 88 | f_name = '.'.join([f.__module__, qualname]) 89 | 90 | if method: 91 | args_list = ['(self=){}'.format(format_arg(args[0]))] 92 | else: 93 | args_list = [] 94 | 95 | # function args 96 | if method: 97 | args_list += list(map(format_arg, args[1:])) 98 | else: 99 | args_list += list(map(format_arg, args)) 100 | 101 | # function kwargs 102 | args_list += list(map(lambda key: '{}={}'.format(key, format_arg(kwargs[key])), kwargs)) 103 | 104 | if use_newlines: 105 | f_args = ',\n{i}{i}'.format(i=indent).join(args_list) 106 | if f_args: 107 | log.debug('\n{i}{f_name}(\n{i}{i}{f_args}\n{i})' 108 | .format(i=indent, f_name=f_name, f_args=f_args)) 109 | else: 110 | log.debug('\n{i}{f_name}()'.format(i=indent, f_name=f_name)) 111 | else: 112 | f_args = ', '.join(args_list) 113 | log.debug('{f_name}({f_args})'.format(f_name=f_name, f_args=f_args)) 114 | 115 | 116 | def log_exit(f): 117 | f_name = '.'.join([f.__module__, func_name(f)]) 118 | 119 | log.debug('..done: {}'.format(f_name)) 120 | 121 | 122 | def logged(f): 123 | """Decorator which logs the name of the function called""" 124 | 125 | @wraps(f) 126 | def wrapper(*args, **kwargs): 127 | if log_function_start: 128 | log_start(f, args, kwargs) 129 | 130 | ret = f(*args, **kwargs) 131 | 132 | if log_function_exit: 133 | log_exit(f) 134 | 135 | return ret 136 | 137 | return wrapper 138 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | norecursedirs = .* venv docs *.egg-info examples build dist 3 | -------------------------------------------------------------------------------- /pyuv_cffi/pyuv_cffi.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom C functions for using libuv with CFFI 3 | */ 4 | #include 5 | #include 6 | #include 7 | 8 | #if UV_VERSION_MAJOR < 1 9 | #error "libuv >= 1.0.0 is required" 10 | #endif 11 | 12 | /** 13 | * Create a `uv_handle_t *` from a `uv_?_t` specific handle type 14 | * 15 | * The purpose of this is to instantiate the `Handle` class and use base 16 | * `uv_handle_t` functions on any specific `uv_?_t` handle types. I couldn't 17 | * figure out how to use `ffi.cast` to get what I wanted, so that is the purpose 18 | * of this function. 19 | */ 20 | uv_handle_t *cast_handle(void *handle) { 21 | return (uv_handle_t *)handle; 22 | } 23 | -------------------------------------------------------------------------------- /pyuv_cffi/pyuv_cffi_cdef.c: -------------------------------------------------------------------------------- 1 | typedef enum { 2 | UV_RUN_DEFAULT = 0, 3 | UV_RUN_ONCE, 4 | UV_RUN_NOWAIT 5 | } uv_run_mode; 6 | 7 | enum uv_poll_event { 8 | UV_READABLE = 1, 9 | UV_WRITABLE = 2 10 | }; 11 | 12 | // handle structs and types 13 | struct uv_loop_s {...;}; 14 | struct uv_handle_s {void *data; ...;}; 15 | struct uv_idle_s {...;}; 16 | struct uv_prepare_s {...;}; 17 | struct uv_timer_s {...;}; 18 | struct uv_signal_s {...;}; 19 | struct uv_poll_s {...;}; 20 | struct uv_check_s {...;}; 21 | 22 | typedef struct uv_loop_s uv_loop_t; 23 | typedef struct uv_handle_s uv_handle_t; 24 | typedef struct uv_idle_s uv_idle_t; 25 | typedef struct uv_prepare_s uv_prepare_t; 26 | typedef struct uv_timer_s uv_timer_t; 27 | typedef struct uv_signal_s uv_signal_t; 28 | typedef struct uv_poll_s uv_poll_t; 29 | typedef struct uv_check_s uv_check_t; 30 | 31 | typedef void (*uv_walk_cb)(uv_handle_t *handle, void *arg); 32 | typedef void (*uv_close_cb)(uv_handle_t *handle); 33 | typedef void (*uv_idle_cb)(uv_idle_t *handle); 34 | typedef void (*uv_prepare_cb)(uv_prepare_t *handle); 35 | typedef void (*uv_poll_cb)(uv_poll_t *handle, int status, int events); 36 | typedef void (*uv_timer_cb)(uv_timer_t *handle); 37 | typedef void (*uv_signal_cb)(uv_signal_t *handle, int signum); 38 | typedef void (*uv_check_cb)(uv_check_t* handle); 39 | 40 | // loop functions 41 | uv_loop_t *uv_default_loop(); 42 | int uv_loop_init(uv_loop_t* loop); 43 | int uv_loop_alive(const uv_loop_t *loop); 44 | int uv_run(uv_loop_t *, uv_run_mode mode); 45 | void uv_stop(uv_loop_t *); 46 | void uv_walk(uv_loop_t *loop, uv_walk_cb walk_cb, void *arg); 47 | 48 | // handle functions 49 | // uv_handle_t is the base type for all libuv handle types. 50 | uv_handle_t *cast_handle(void *handle); 51 | void uv_ref(uv_handle_t *); 52 | void uv_unref(uv_handle_t *); 53 | int uv_has_ref(const uv_handle_t *); 54 | void uv_close(uv_handle_t *handle, uv_close_cb close_cb); 55 | int uv_is_active(const uv_handle_t *handle); 56 | int uv_is_closing(const uv_handle_t *handle); 57 | 58 | // idle functions 59 | // Idle handles will run the given callback once per loop iteration, right 60 | // before the uv_prepare_t handles. Note: The notable difference with prepare 61 | // handles is that when there are active idle handles, the loop will perform a 62 | // zero timeout poll instead of blocking for i/o. Warning: Despite the name, 63 | // idle handles will get their callbacks called on every loop iteration, not 64 | // when the loop is actually "idle". 65 | int uv_idle_init(uv_loop_t *, uv_idle_t *idle); 66 | int uv_idle_start(uv_idle_t *idle, uv_idle_cb cb); 67 | int uv_idle_stop(uv_idle_t *idle); 68 | 69 | // prepare functions 70 | // Prepare handles will run the given callback once per loop iteration, right 71 | // before polling for i/o. 72 | int uv_prepare_init(uv_loop_t *, uv_prepare_t *prepare); 73 | int uv_prepare_start(uv_prepare_t *prepare, uv_prepare_cb cb); 74 | int uv_prepare_stop(uv_prepare_t *prepare); 75 | 76 | // check functions 77 | // Check handles will run the given callback once per loop iteration, right 78 | int uv_check_init(uv_loop_t *, uv_check_t *check); 79 | int uv_check_start(uv_check_t *check, uv_check_cb cb); 80 | int uv_check_stop(uv_check_t *check); 81 | 82 | // timer functions 83 | // Timer handles are used to schedule callbacks to be called in the future. 84 | int uv_timer_init(uv_loop_t *, uv_timer_t *handle); 85 | int uv_timer_start(uv_timer_t *handle, uv_timer_cb cb, uint64_t timeout, uint64_t repeat); 86 | int uv_timer_stop(uv_timer_t *handle); 87 | int uv_timer_again(uv_timer_t *handle); 88 | void uv_timer_set_repeat(uv_timer_t *handle, uint64_t repeat); 89 | uint64_t uv_timer_get_repeat(const uv_timer_t *handle); 90 | 91 | // signal functions 92 | // Signal handles implement Unix style signal handling on a per-event loop 93 | // bases. 94 | int uv_signal_init(uv_loop_t *loop, uv_signal_t *handle); 95 | int uv_signal_start(uv_signal_t *handle, uv_signal_cb signal_cb, int signum); 96 | int uv_signal_stop(uv_signal_t *handle); 97 | 98 | // poll functions 99 | // Poll handles are used to watch file descriptors for readability and 100 | // writability, similar to the purpose of poll(2). It is not okay to have 101 | // multiple active poll handles for the same socket, this can cause libuv to 102 | // busyloop or otherwise malfunction. 103 | int uv_poll_init(uv_loop_t *loop, uv_poll_t *handle, int fd); 104 | int uv_poll_start(uv_poll_t *handle, int events, uv_poll_cb cb); 105 | int uv_poll_stop(uv_poll_t *handle); 106 | -------------------------------------------------------------------------------- /requirements.in: -------------------------------------------------------------------------------- 1 | cffi 2 | dnspython3 3 | greenlet 4 | http-parser 5 | pytest 6 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # Make changes in requirements.in, then run this to update: 4 | # 5 | # pip-compile requirements.in 6 | # 7 | cffi==1.3.0 8 | dnspython3==1.12.0 9 | greenlet==0.4.9 10 | http-parser==0.8.3 11 | py==1.4.30 # via pytest 12 | pycparser==2.14 # via cffi 13 | pytest==2.8.2 14 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | import sys 3 | 4 | from setuptools import setup, find_packages 5 | 6 | from guv import __version__ 7 | 8 | if sys.version_info < (3, 2): 9 | raise Exception('guv requires Python 3.2 or higher') 10 | 11 | classifiers = [ 12 | "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", 13 | "Operating System :: MacOS :: MacOS X", 14 | "Operating System :: POSIX", 15 | # "Operating System :: Microsoft :: Windows", 16 | "Programming Language :: Python", 17 | "Programming Language :: Python :: Implementation :: CPython", 18 | "Programming Language :: Python :: Implementation :: PyPy", 19 | "Programming Language :: Python :: 3 :: Only", 20 | "Programming Language :: Python :: 3.2", 21 | "Programming Language :: Python :: 3.3", 22 | "Programming Language :: Python :: 3.4", 23 | "Topic :: Internet", 24 | "Topic :: Software Development :: Libraries :: Python Modules", 25 | "Topic :: System :: Networking", 26 | "Intended Audience :: Developers", 27 | "Development Status :: 3 - Alpha", 28 | ] 29 | 30 | setup( 31 | name='guv', 32 | version=__version__, 33 | description='Python 3 networking library based on greenlets and libuv', 34 | author='V G', 35 | author_email='veegee@veegee.org', 36 | url='http://guv.readthedocs.org', 37 | install_requires=['greenlet>=0.4.0', 'cffi>=0.8.0', 'dnspython3>=1.12.0'], 38 | zip_safe=False, 39 | long_description=open(path.join(path.dirname(__file__), 'README.rst')).read(), 40 | tests_require=['pytest>=2.6'], 41 | classifiers=classifiers, 42 | packages=find_packages(exclude=['ez_setup']), 43 | package_data={'pyuv_cffi': ['*.c']} 44 | ) 45 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veegee/guv/d7bac2ca6a73cc2059969af08223b82f3e187922/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from guv.greenio import socket 4 | from guv import listen 5 | 6 | 7 | @pytest.fixture(scope='session') 8 | def pub_addr(): 9 | """A working public address that is considered always available 10 | """ 11 | return 'gnu.org', 80 12 | 13 | 14 | @pytest.fixture(scope='session') 15 | def fail_addr(): 16 | """An address that nothing is listening on 17 | """ 18 | return '192.0.0.0', 1000 19 | 20 | 21 | @pytest.fixture(scope='function') 22 | def gsock(): 23 | return socket() 24 | 25 | 26 | @pytest.fixture(scope='function') 27 | def server_sock(): 28 | sock = listen(('', 0)) 29 | return sock 30 | 31 | -------------------------------------------------------------------------------- /tests/test_greenio.py: -------------------------------------------------------------------------------- 1 | import errno 2 | import gc 3 | import socket 4 | import sys 5 | 6 | import pytest 7 | 8 | from guv import spawn 9 | from guv.event import Event 10 | from guv.greenio import socket as green_socket 11 | from guv.green import socket as socket_patched 12 | from guv.support import get_errno 13 | 14 | pyversion = sys.version_info[:2] 15 | 16 | TIMEOUT_SMALL = 0.01 17 | BACKLOG = 10 18 | 19 | 20 | def resize_buffer(sock, size): 21 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, size) 22 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, size) 23 | 24 | 25 | class TestGreenSocket: 26 | def test_socket_init(self): 27 | sock = socket_patched.socket() 28 | assert isinstance(sock, green_socket) 29 | 30 | def test_socket_close(self, gsock): 31 | gsock.close() 32 | 33 | def test_connect(self, gsock, pub_addr): 34 | gsock.connect(pub_addr) 35 | print(gsock.getpeername()) 36 | assert gsock.getpeername() 37 | 38 | def test_connect_timeout(self, gsock, fail_addr): 39 | gsock.settimeout(TIMEOUT_SMALL) 40 | 41 | with pytest.raises(socket.timeout): 42 | gsock.connect(fail_addr) 43 | 44 | def test_connect_ex_timeout(self, gsock, fail_addr): 45 | gsock.settimeout(TIMEOUT_SMALL) 46 | 47 | e = gsock.connect_ex(fail_addr) 48 | 49 | if e not in {errno.EHOSTUNREACH, errno.ENETUNREACH}: 50 | assert e == errno.EAGAIN 51 | 52 | def test_accept_timeout(self, gsock): 53 | gsock.settimeout(TIMEOUT_SMALL) 54 | gsock.bind(('', 0)) 55 | gsock.listen(BACKLOG) 56 | 57 | with pytest.raises(socket.timeout): 58 | gsock.accept() 59 | 60 | def test_recv_timeout(self, gsock, pub_addr): 61 | gsock.connect(pub_addr) 62 | gsock.settimeout(TIMEOUT_SMALL) 63 | 64 | with pytest.raises(socket.timeout) as exc_info: 65 | gsock.recv(8192) 66 | 67 | assert exc_info.value.args[0] == 'timed out' 68 | 69 | def test_send_timeout(self, gsock, server_sock): 70 | resize_buffer(server_sock, 1) 71 | evt = Event() 72 | 73 | def server(): 74 | client_sock, addr = server_sock.accept() 75 | resize_buffer(client_sock, 1) 76 | evt.wait() 77 | 78 | g = spawn(server) 79 | 80 | server_addr = server_sock.getsockname() 81 | resize_buffer(gsock, 1) 82 | gsock.connect(server_addr) 83 | gsock.settimeout(TIMEOUT_SMALL) 84 | 85 | with pytest.raises(socket.timeout): 86 | # large enough data to overwhelm most buffers 87 | msg_len = 10 ** 6 88 | sent = 0 89 | 90 | while sent < msg_len: 91 | sent += gsock.send(bytes(msg_len)) 92 | 93 | evt.send() 94 | g.wait() 95 | 96 | def test_send_to_closed_sock_raises(self, gsock): 97 | try: 98 | gsock.send(b'hello') 99 | except socket.error as e: 100 | assert get_errno(e) == errno.EPIPE 101 | 102 | if pyversion >= (3, 3): 103 | # on python 3.3+, the exception can be caught like this as well 104 | with pytest.raises(BrokenPipeError): 105 | gsock.send(b'hello') 106 | 107 | def test_del_closes_socket(self, gsock, server_sock): 108 | def accept_once(sock): 109 | # delete/overwrite the original conn object, only keeping the file object around 110 | # closing the file object should close everything 111 | try: 112 | client_sock, addr = sock.accept() 113 | file = client_sock.makefile('wb') 114 | del client_sock 115 | file.write(b'hello\n') 116 | file.close() 117 | gc.collect() 118 | with pytest.raises(ValueError): 119 | file.write(b'a') 120 | finally: 121 | sock.close() 122 | 123 | killer = spawn(accept_once, server_sock) 124 | gsock.connect(('127.0.0.1', server_sock.getsockname()[1])) 125 | f = gsock.makefile('rb') 126 | gsock.close() 127 | assert f.read() == b'hello\n' 128 | assert f.read() == b'' 129 | killer.wait() 130 | 131 | 132 | class TestGreenSocketModule: 133 | def test_create_connection(self, pub_addr): 134 | sock = socket_patched.create_connection(pub_addr) 135 | assert sock 136 | 137 | def test_create_connection_timeout_error(self, fail_addr): 138 | # Inspired by eventlet Greenio_test 139 | try: 140 | socket_patched.create_connection(fail_addr, timeout=0.01) 141 | pytest.fail('Timeout not raised') 142 | except socket.timeout as e: 143 | assert str(e) == 'timed out' 144 | except socket.error as e: 145 | # unreachable is also a valid outcome 146 | if not get_errno(e) in (errno.EHOSTUNREACH, errno.ENETUNREACH): 147 | raise 148 | 149 | -------------------------------------------------------------------------------- /tests/test_threads.py: -------------------------------------------------------------------------------- 1 | from guv import gyield, spawn 2 | from guv.green import threading, time 3 | 4 | 5 | def f1(): 6 | """A simple function 7 | """ 8 | return 'Hello, world!' 9 | 10 | 11 | def f2(): 12 | """A simple function that sleeps for a short period of time 13 | """ 14 | time.sleep(0.1) 15 | 16 | 17 | class TestThread: 18 | def test_thread_create(self): 19 | t = threading.Thread(target=f1) 20 | assert 'green' in repr(t) 21 | 22 | def test_thread_start(self): 23 | t = threading.Thread(target=f1) 24 | assert 'green' in repr(t) 25 | t.start() 26 | 27 | def test_thread_join(self): 28 | t = threading.Thread(target=f1) 29 | assert 'green' in repr(t) 30 | t.start() 31 | t.join() 32 | 33 | def test_thread_active(self): 34 | initial_count = threading.active_count() 35 | t = threading.Thread(target=f2) 36 | assert 'green' in repr(t) 37 | t.start() 38 | assert threading.active_count() > initial_count 39 | t.join() 40 | assert threading.active_count() == initial_count 41 | 42 | 43 | class TestCondition: 44 | """ 45 | :class:`threading.Condition` is not explicitly patched, but since its dependencies are patched, 46 | it shall behave in a cooperative manner. The Cassandra driver relies on a ThreadPoolExecutor, 47 | which itself relies on threading.Condition. This class must be working for the driver to 48 | operate in a cooperative manner. 49 | """ 50 | 51 | def test_condition_init(self): 52 | cv = threading.Condition() 53 | assert cv 54 | 55 | def test_condition(self): 56 | print() 57 | cv = threading.Condition() 58 | 59 | items = [] 60 | 61 | def produce(): 62 | print('start produce()') 63 | with cv: 64 | for i in range(10): 65 | items.append(i) 66 | print('yield from produce()') 67 | gyield() 68 | 69 | print('notify') 70 | cv.notify() 71 | print('done produce()') 72 | 73 | def consume(): 74 | print('start consume()') 75 | with cv: 76 | while not len(items) == 10: 77 | print('wait ({}/{})'.format(len(items), 10)) 78 | cv.wait() 79 | 80 | print('items: {}'.format(len(items))) 81 | print('done consume()') 82 | 83 | spawn(produce) 84 | spawn(consume) 85 | 86 | print('switch to hub') 87 | gyield(False) 88 | print('done test') 89 | 90 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py34,pypy3,py33,py32 3 | 4 | [testenv] 5 | deps = pytest 6 | commands = py.test 7 | --------------------------------------------------------------------------------