#python #django #multithreading #gunicorn
Вопрос:
Я настроил кэш области запросов с помощью промежуточного программного обеспечения и попытался настроить его так, чтобы он был доступен из любого места с помощью потоковой передачи.локальная() переменная. Однако иногда процессы с длинными запросами завершаются со следующей ошибкой:
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 выполняет исправление обезьяны внутри, и Django использует один и тот же поток на протяжении всего запроса, что означает, что локальное хранилище потока не должно изменяться в середине запроса ни в одном из этих случаев. Однако возможно, что одновременно обрабатывается несколько запросов, и каждый запрос может содержать входные данные размером около 200 МБ, а время, необходимое для обработки запроса, может составлять несколько часов — последний сбой произошел после 4 часов обработки.
В чем может быть причина потери этого кэша в процессе запроса? Если бы он вообще не был создан, запрос рухнул бы намного быстрее, и я не могу придумать причину, по которой Django мог бы изменить или потерять threading.local()
объект в середине запроса.
Ответ №1:
Добавление явного monkey.patch_all()
вызова каким-то образом устранило проблему. Корень проблемы так и остался неизвестным.