简体   繁体   中英

Flask restrict concurrent requests for certain routes

I have a flask application and I want most requests to run concurrently. I set app.run(threaded=True) and this seems to work for the most part. However, there's this one endpoint that runs dredd where I want to restrict it so that requests for that endpoint do not run concurrently. It appears to cause a socket error when more than one is run at the same time. Is there a way to do this?

I also faced the same kind of issue when I was working on a flask server to update/insert collection in MongoDB. I wanted to implement autoincrement id's in the MongoDB collection. we created an API route for the same and wrote MongoDB queries to get the first doc in reverse sorted the document and then added 1 and inserted a new doc with the id just calculated.

result = db_pymongo.academy.aggregate(
        [
            {
                '$sort': {
                    'academy_id': -1
                }
            }, {
                '$limit': 1
            }, {
                '$project': {
                    'academy_id': 1
                }
            }
        ]
    )
    # predefining academy_id if no document initially
    academy_id = 1
    for doc in result:
        if doc:
            academy_id = doc['academy_id'] + 1

    new_academy = {
        "academy": academy,
        'academy_id': academy_id,
        "players": [],
        "tournaments": [],
        "created_at": datetime.utcnow(),
    }
    db_pymongo.academy.insert_one(new_academy)

everything worked fine this way but when we sent multiple simultaneous requests to the same API route, it started misbehaving and gave the same ids to multiple inserted objects (which will obviously happen), so we tried different ways to do the same thing. we created a variable in the app.py file like this

LOCK_STATUS = {
               "academy": False
              }

now I created a decorator to check and update status as per need

def db_lock(name):
    def wrapper(f):
        @wraps(f)
        def wrapped(*args, **kwargs):
            while LOCK_STATUS[name]:
                time.sleep(0.5)
            LOCK_STATUS[name] = True
            # call function and set back to false
            try:
                return f(*args, **kwargs)
            except Exception as e:
                LOCK_STATUS[name] = False
                if isinstance(e, ValueError):
                    return jsonify({'message': e.args[0], 'type': 'ValueError'}), 400
                elif isinstance(e, AttributeError):
                    return jsonify({'message': e.args[0], 'type': 'AttributeError'}), 400
                elif isinstance(e, KeyError):
                    return jsonify({'message': e.args[0], 'type': 'KeyError'}), 400
                elif isinstance(e, TypeError):
                    return jsonify({'message': e.args[0], 'type': 'TypeError'}), 400
                else:
                    return jsonify({'message': str(e), 'type': 'InternalServerError'}), 500
            finally:
                LOCK_STATUS[name] = False
        return wrapped
    return wrapper

this workes in a way that it will check for status, if the status is false then it will proceed normally otherwise it will keep waiting until previous request releases the lock, the then-current request will acquire the lock by changing status again as true, and then the rest code will execute normally, if anything happens worng then also it will release the lock

for using this decorator we just need to pass "name" as args in (in this case it is academy) here is the full code after using db_lock decorator with API


@academy_api.route('', methods=['POST'])
@db_lock('academy')
@roles_required('admin')
def addAcademy():
    data = request.get_json()
    academy = data["academy"]
    # increment academy id by 1
    result = db_pymongo.academy.aggregate(
        [
            {
                '$sort': {
                    'academy_id': -1
                }
            }, {
                '$limit': 1
            }, {
                '$project': {
                    'academy_id': 1
                }
            }
        ]
    )

    academy_id = 1
    for doc in result:
        if doc:
            academy_id = doc['academy_id'] + 1

    new_academy = {
        "academy": academy,
        'academy_id': academy_id,
        "players": [],
        "tournaments": [],
        "created_at": datetime.utcnow(),
    }
    db_pymongo.academy.insert_one(new_academy)
    returnableObject(new_academy)
    return jsonify(message="success", payload=new_academy), 200

it works perfectly fine and won't create problem to any other route, that means other API routes can operate simultaneously in threaded mode

I don't think it is possible to do per-route declaratively.

You can, though, use a threading.lock or, better, a multiprocessing.lock to serialize actual processing of something you need processed serially. Request handlers will wait until they can acquire the lock. Be sure to pay a lot of attention to releasing the lock in various error scenarios as well as during normal execution.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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