简体   繁体   English

限制Tornado中特定视图的连接

[英]Limit connections for particular view in Tornado

I have view that takes a lot of memory and is asynchronous. 我认为占用大量内存并且是异步的。 Can I limit count of connections simultaneously working inside handler function (like critical section with N max workers inside). 我可以限制同时在处理函数内部工作的连接数(例如内部有N max个工作者的临界区)。

Is this possible in Tornado? 龙卷风有可能吗?

Like: 喜欢:

@tornado.web.asynchronous
def get(self):
    with critical_section(count=5):
    # some code

Thanks 谢谢

Toro provides synchronization primitives similar to those found in the threading module for Tornado coroutines. Toro提供的类似于Tornado协同程序的线程模块中的同步原语。 You could use its BoundedSemaphore to gate entry to the handler body: 您可以使用其BoundedSemaphore来输入处理程序主体:

# global semaphore
sem = toro.BoundedSemaphore(5)

@gen.coroutine
def get(self):
    with (yield sem.acquire()):
        # do work

Short answer: 简短回答:

As far as I understand Tornado and other frameworks that use Future/Deferred/generator based concurrency, this is not possible. 据我所知,Tornado和其他使用Future / Deferred / generator并发的框架,这是不可能的。 However, it should definitely be possible using higher-order functions, ie a critical_section() helper function that takes the body of the with -block as a parameter. 但是,绝对可以使用高阶函数,即使用with -block的主体作为参数的critical_section()辅助函数。

Long answer: 答案很长:

To my best knowledge, Tornado's concurrency works very much like that of Twisted; 据我所知,Tornado的并发性非常类似于Twisted; which means non-blocking calls are limited to using Future s and yield (based on Twisted's @inlineCallbacks or whatever is the equivalent in Tornado). 这意味着非阻塞调用仅限于使用Future s和yield (基于Twisted的@inlineCallbacks或Tornado中的等价物)。

In order to implement a critical_section context manager, it would have to cooperate with the reactor internally; 为了实现一个critical_section上下文管理器,它必须在内部与反应堆合作; this can only happen using callbacks or yield . 这只能通过回调或yield However, neither is composable with context managers. 但是,它们都不能与上下文管理器组合。

I'd actually already thrown up some code until I remembered this. 实际上我已经抛出了一些代码,直到我记起这一点。 This is the code I came up with: 这是我提出的代码:

import sys
from contextlib import contextmanager
from collections import defaultdict

from tornado.concurrent import Future


_critical_sections = defaultdict(lambda: (0, []))


@contextmanager
def critical_section(count):
    # get the code location of the critical section
    frame = sys._getframe()
    orig_caller = frame.f_back.f_back
    lineno = orig_caller.f_lineno
    filename = orig_caller.f_code.co_filename
    loc = (filename, lineno)

    count, waiters = _critical_sections[loc]
    if count > 5:
        future = Future()
        _critical_sections[loc] = (count + 1, waiters + [future])
        # XXX: not possible; either have to set a callback or use yield, but
        # then this context manager itself would not work as you'd expect:
        future.wait()  # <---- not possible in Tornado nor Twisted; only in Gevent/Eventlet
        fn(*args, **kwargs)
    else:
        _critical_sections[loc] = (count + 1, waiters)
        try:
            yield
        finally:
            count, waiters = _critical_sections[loc]
            _, w_future = waiters[0]
            _critical_sections[loc] = (count, waiters[1:])
            w_future.set_result(None)

(I have not tested it in anyway, and it's not runnable on Tornado anyway.) (无论如何我还没有测试过,而且无论如何它都不能在Tornado上运行。)

Now, if you're happy with the proposed approach, here's something to get you started (or maybe it even works out of the box): 现在,如果您对提议的方法感到满意,这里有一些东西可以帮助您入门(或者甚至可以开箱即用):

...

def _critical_section(count, fn, *args, **kwargs):
    ...
    if count > 5:
        future = Future()
        future.add_done_callback(lambda _: fn(*args, **kwargs))
        _critical_sections[loc] = (count + 1, waiters + [future])
        # XXX: not possible; either have to set a callback or use yield, but
        # then this context manager itself would not work as you'd expect:
        return future
    else:
        _critical_sections[loc] = (count + 1, waiters)
        try:
            return fn()
        finally:
            ... # same

then you could just turn it into a decorator: 然后你可以把它变成一个装饰者:

from functools import wraps

def critical_section(count):
    def decorate(fn):
        @wraps(fn)
        def ret(*args, **kwargs):
            return _critical_section(count, fn, *args, **kwargs)
        return ret
    return decorate

Usage: 用法:

@tornado.web.asynchronous
def get(self):
    @critical_section(count=5)
    def some_code():
        pass  # do stuff

Also, the code is using sys._getframe() , which has (at least) 2 implications: 此外,代码使用sys._getframe() ,它具有(至少)2个含义:

  • it will make the code slower when run on PyPy (until/unless PyPy has become able to JIT-compile functions that use sys._getframe ), but most of the time, it's an acceptable tradeoff when it comes to web code 它会使代码在PyPy上运行时变慢(直到/除非PyPy能够使用sys._getframe进行JIT编译功能),但大多数情况下,当涉及到Web代码时,这是一个可接受的权衡
  • I don't think the code will work if you compile it to .pyc and remove the .py —it shouldn't then be able to determine the filename and line number of the calling code, so it will not (probably) be possible to uniquely distinguish the location of the critical section, in which case you'd have to use lock objects. 我不认为如果你将代码编译为.pyc并删除.py -it代码将无法确定调用代码的文件名和行号,因此它不会(可能)成为可能唯一地区分关键部分的位置,在这种情况下,您必须使用锁定对象。

NOTE: The context manager version would be perfectly feasible on Gevent ( http://gevent.org ) or Eventlet. 注意:上下文管理器版本在Gevent( http://gevent.org )或Eventlet上完全可行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM