带有 Gunicorn 的 Django 在请求期间丢失线程本地存储

Django with Unicorn losing thread local storage during requst

我已经使用中间件设置了一个请求范围缓存,并尝试使用 threading.local() 变量将其设置为可从任何地方使用。但是,有时长请求进程会因以下错误而丢失:

  File "label.py", line 29, in get_from_request_cache
    label = cache.labels[url]
AttributeError: '_thread._local' object has no attribute 'labels'

但是,一些项目得到了正确处理,它们都取决于该缓存的存在。

缓存对象初始化一次,并在请求结束时使用以下中间件清除:

request_cache.py

import threading

_request_cache = threading.local()

def get_request_cache():
    return _request_cache

class RequestCacheMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        global _request_cache
        response = self.get_response(request)
        _request_cache.__dict__.clear()

        return response

    def process_exception(self, request, exception):
        _request_cache.__dict__.clear()
        return None

而直接访问缓存对象的唯一代码是这部分:

label.py

import django.db.models

from request_cache import get_request_cache
from base import Model


class Label(Model):
    **model fields**

    @staticmethod
    def create_for_request_cache(urls):
        cache = get_request_cache()
        urls = set(urls)
        if not hasattr(cache, 'labels'):
            cache.labels = {}
        new_urls = urls.symmetric_difference(set(cache.labels.keys()))
        entries = [Label(url=url) for url in new_urls]
        if entries:
            Label.objects.bulk_create(entries)
            for entry in entries:
                cache.labels[entry.url] = entry

    @staticmethod
    def get_from_request_cache(url):
        cache = get_request_cache()
        label = cache.labels[url]
        return label

崩溃的请求在代码中被分成批次,在每个批次之前,使用以下代码获取新的唯一 url 并将其添加到缓存中,这就是进程崩溃的地方:

fill_labels.py

class LabelView(django.views.generic.base.View):
    def _fill_labels_batch(items_batch):
        VersionLabel.create_for_request_cache([item.get('url', '') for item in items_batch])

        for item in items_batch:
            **process items** - CRASHES HERE

    @transaction.atomic
    def post(self, request, subcategory):
        item_batches = **split items into batches**
        for item_batch in item_batches:
            _fill_labels_batch(item_batch)

如果我正确理解 Django 和 Gunicorn 的工作方式,如果没有猴子补丁,线程本地对象应该是线程本地的,或者如果 Gunicorn 在内部进行猴子补丁,那么线程本地对象应该是 greenlet 的本地对象,并且 Django 使用在整个请求期间使用相同的线程,这意味着在这两种情况下,线程本地存储不应在请求中更改。然而,有可能同时处理多个请求,每个请求可以有大约 200MB 的输入数据,处理请求所需的时间可能是几个小时——最后一次崩溃发生在处理 4 小时后。

请求进程丢失此缓存的原因可能是什么?如果根本不创建它,请求会崩溃得更快,而且我想不出 Django 在请求中更改或丢失 threading.local() 对象的原因。

添加显式 monkey.patch_all() 调用以某种方式解决了问题。问题的根源仍然未知。