changeset f49e2e7fc2e6 in trytond:default
details: https://hg.tryton.org/trytond?cmd=changeset;node=f49e2e7fc2e6
description:
        Add coroutine concurrency option

        issue8010
        review53501002
diffstat:

 CHANGELOG                              |   1 +
 bin/trytond                            |  38 ++++++++++++++++++++++++---------
 doc/topics/install.rst                 |   1 +
 doc/topics/start_server.rst            |   9 ++++++++
 setup.py                               |   1 +
 trytond/application.py                 |   4 +++
 trytond/backend/postgresql/database.py |   6 +++++
 trytond/commandline.py                 |   2 +
 trytond/tools/gevent.py                |  11 +++++++++
 9 files changed, 62 insertions(+), 11 deletions(-)

diffs (180 lines):

diff -r 21c4acd056a7 -r f49e2e7fc2e6 CHANGELOG
--- a/CHANGELOG Mon Feb 04 22:46:32 2019 +0100
+++ b/CHANGELOG Tue Feb 05 19:09:53 2019 +0100
@@ -1,3 +1,4 @@
+* Add coroutine concurrency option
 * Add increasing delay on database operational error retry
 * Allows to lock records for update
 * Remove _nocache on Transaction
diff -r 21c4acd056a7 -r f49e2e7fc2e6 bin/trytond
--- a/bin/trytond       Mon Feb 04 22:46:32 2019 +0100
+++ b/bin/trytond       Tue Feb 05 19:09:53 2019 +0100
@@ -1,11 +1,10 @@
 #!/usr/bin/env python3
 # This file is part of Tryton.  The COPYRIGHT file at the top level of
 # this repository contains the full copyright notices and license terms.
-import sys
+import glob
+import logging
 import os
-import glob
-
-from werkzeug.serving import run_simple
+import sys
 
 DIR = os.path.abspath(os.path.normpath(os.path.join(__file__,
     '..', '..', 'trytond')))
@@ -20,6 +19,11 @@
 commandline.config_log(options)
 extra_files = config.update_etc(options.configfile)
 
+if options.coroutine:
+    # Monkey patching must be done before importing
+    from gevent import monkey
+    monkey.patch_all()
+
 # Import trytond things after it is configured
 from trytond.wsgi import app
 from trytond.pool import Pool
@@ -33,15 +37,27 @@
     certificate = config.get('ssl', 'certificate')
     privatekey = config.get('ssl', 'privatekey')
     if certificate or privatekey:
-        ssl_context = (certificate, privatekey)
+        from werkzeug.serving import load_ssl_context
+        ssl_args = dict(
+            ssl_context=load_ssl_context(certificate, privatekey))
     else:
-        ssl_context = None
+        ssl_args = {}
     for module in get_module_list():
         info = get_module_info(module)
         path = os.path.join(info['directory'], 'view', '*.xml')
         extra_files.extend(glob.glob(path))
-    run_simple(hostname, port, app,
-        threaded=True,
-        extra_files=extra_files,
-        ssl_context=ssl_context,
-        use_reloader=options.dev)
+
+    if options.coroutine:
+        from gevent.pywsgi import WSGIServer
+        logger = logging.getLogger('gevent')
+        WSGIServer((hostname, port), app,
+            log=logger,
+            error_log=logger,
+            **ssl_args).serve_forever()
+    else:
+        from werkzeug.serving import run_simple
+        run_simple(hostname, port, app,
+            threaded=True,
+            extra_files=extra_files,
+            use_reloader=options.dev,
+            **ssl_args)
diff -r 21c4acd056a7 -r f49e2e7fc2e6 doc/topics/install.rst
--- a/doc/topics/install.rst    Mon Feb 04 22:46:32 2019 +0100
+++ b/doc/topics/install.rst    Tue Feb 05 19:09:53 2019 +0100
@@ -26,6 +26,7 @@
       (http://github.com/miohtama/python-Levenshtein)
     * Optional: bcrypt (https://github.com/pyca/bcrypt)
     * Optional: html2text (https://pypi.python.org/pypi/html2text)
+    * Optional: gevent (http://www.gevent.org/)
 
 Install Tryton
 ==============
diff -r 21c4acd056a7 -r f49e2e7fc2e6 doc/topics/start_server.rst
--- a/doc/topics/start_server.rst       Mon Feb 04 22:46:32 2019 +0100
+++ b/doc/topics/start_server.rst       Tue Feb 05 19:09:53 2019 +0100
@@ -26,11 +26,20 @@
 
  * `TRYTOND_CONFIG`: Point to :ref:`configuration <topics-configuration>` file.
  * `TRYTOND_LOGGING_CONFIG`: Point to :ref:`logging <topics-logs>` file.
+ * `TRYTOND_COROUTINE`: Use coroutine for concurrency.
  * `TRYTOND_DATABASE_NAMES`: A list of database names in CSV format, using
    python default dialect.
 
 .. warning:: You must manage to serve the static files from the web root.
 
+Coroutine server
+----------------
+
+The Werkzeug server uses thread for concurrency. This is not optimal for the
+long-polling request on the :ref:`bus <ref-bus>` as each client consumes
+permanently one thread.
+You can start the server with coroutine using the option `--coroutine`.
+
 Cron service
 ============
 
diff -r 21c4acd056a7 -r f49e2e7fc2e6 setup.py
--- a/setup.py  Mon Feb 04 22:46:32 2019 +0100
+++ b/setup.py  Tue Feb 05 19:09:53 2019 +0100
@@ -138,6 +138,7 @@
         'Levenshtein': ['python-Levenshtein'],
         'BCrypt': ['passlib[bcrypt]'],
         'html2text': ['html2text'],
+        'coroutine': ['gevent>=1.1'],
         },
     zip_safe=False,
     test_suite='trytond.tests',
diff -r 21c4acd056a7 -r f49e2e7fc2e6 trytond/application.py
--- a/trytond/application.py    Mon Feb 04 22:46:32 2019 +0100
+++ b/trytond/application.py    Tue Feb 05 19:09:53 2019 +0100
@@ -12,6 +12,10 @@
 if logging_config:
     logging.config.fileConfig(logging_config)
 
+if os.environ.get('TRYTOND_COROUTINE'):
+    from gevent import monkey
+    monkey.patch_all()
+
 from trytond.pool import Pool
 from trytond.wsgi import app
 
diff -r 21c4acd056a7 -r f49e2e7fc2e6 trytond/backend/postgresql/database.py
--- a/trytond/backend/postgresql/database.py    Mon Feb 04 22:46:32 2019 +0100
+++ b/trytond/backend/postgresql/database.py    Tue Feb 05 19:09:53 2019 +0100
@@ -36,6 +36,7 @@
 from trytond.backend.database import DatabaseInterface, SQLType
 from trytond.config import config, parse_uri
 from trytond.protocols.jsonrpc import JSONDecoder
+from trytond.tools.gevent import is_gevent_monkey_patched
 
 __all__ = ['Database', 'DatabaseIntegrityError', 'DatabaseOperationalError']
 
@@ -538,3 +539,8 @@
     return json.loads(value, object_hook=JSONDecoder())
 register_default_json(loads=convert_json)
 register_default_jsonb(loads=convert_json)
+
+if is_gevent_monkey_patched():
+    from psycopg2.extensions import set_wait_callback
+    from psycopg2.extras import wait_select
+    set_wait_callback(wait_select)
diff -r 21c4acd056a7 -r f49e2e7fc2e6 trytond/commandline.py
--- a/trytond/commandline.py    Mon Feb 04 22:46:32 2019 +0100
+++ b/trytond/commandline.py    Tue Feb 05 19:09:53 2019 +0100
@@ -37,6 +37,8 @@
     parser = get_parser()
     parser.add_argument("--pidfile", dest="pidfile", metavar='FILE',
         help="file where the server pid will be stored")
+    parser.add_argument("--coroutine", action="store_true", dest="coroutine",
+        help="use coroutine for concurrency")
     return parser
 
 
diff -r 21c4acd056a7 -r f49e2e7fc2e6 trytond/tools/gevent.py
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/trytond/tools/gevent.py   Tue Feb 05 19:09:53 2019 +0100
@@ -0,0 +1,11 @@
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+
+def is_gevent_monkey_patched():
+    try:
+        from gevent import monkey
+    except ImportError:
+        return False
+    else:
+        return monkey.is_module_patched('__builtin__')

Reply via email to