带有 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()
调用以某种方式解决了问题。问题的根源仍然未知。
我已经使用中间件设置了一个请求范围缓存,并尝试使用 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()
调用以某种方式解决了问题。问题的根源仍然未知。