uWSGI 和 Django 分段错误

uWSGI and Django segmentation fault

我一直在处理一个间歇性且难以追踪的奇怪错误。我的 django 网站上的一个页面允许用户下载他们购买的音乐。

问题: 一些用户在下载时报告 502 错误。对该问题的进一步调查显示 uWSGI 中存在分段错误。我真的不知道如何调试它,并且正在寻找任何可能有助于找到解决方案的东西。

尝试解决问题:
我认为可能是我 运行 uWSGI 进程太多,所以我降低了它,但由于问题只是有时发生(有些用户没有问题,有些重试成功),我不能确保错误已解决。

对该问题的思考:
我认为这可能是由于我在这里处理文件的方式所致。我正在压缩的文件也在其他视图中发送给其他用户。当 uWSGI 进程都试图读取一个文件时,这会是一个问题吗?没有进程写入文件。

后续步骤:
我正在寻找这方面的一些指导,或者关于如何获得更多关于这里发生的事情的信息的任何想法。

uWSGI 配置:

[uwsgi]
plugin=/etc/uwsgi/python_plugin.so
wsgi-file           = /path/to/my/project/projectname/wsgi.py
home                = /path/to/my/project/
master              = true
socket              = /tmp/uwsgi.sock
chmod-socket        = 666
vacuum              = true
processes           = 3
workers             = 15
min-worker-lifetime = 45
max-requests        = 100
reload-mercy        = 5
harakiri            = 20
buffer-size         = 16384

相关观点:

def zipForDownload(album):

    bonus = BonusContent.objects.filter(album=album)
    bonus_files = [open(f.bonus_file.path, 'rb') for f in bonus]

    tracks = trackSort(list(Track.objects.filter(album=album)))
    track_files = [open(f.audio_file.path, 'rb') for f in tracks]


    zipped_file = StringIO.StringIO()
    with zipfile.ZipFile(zipped_file, 'w') as zip:
        for i, f in enumerate(track_files):
            f.seek(0)
            num = tracks[i].track_number
            name = tracks[i].name
            ext = os.path.basename(f.name).split('.')[-1]
            zip.writestr("{0} - {1}.{2}".format(num, name, ext), f.read())
        for i, f in enumerate(bonus_files):
            name = bonus[i].name
            ext = os.path.basename(f.name).split('.')[-1]
            zip.writestr("{0}.{1}".format(name, ext), f.read())

    zipped_file.seek(0)
    response = HttpResponse(zipped_file, content_type='application/octet-stream')
    response['Content-Disposition'] = 'attachment; filename=%s.zip' % (album.name)
    return response

def downloadPostPin(request, purchase):
    if request.POST.get('PIN').encode('utf-8') == purchase.download_pin.encode('utf-8'):
        # Get zip response with all tracks and bonus content
        resp = zipForDownload(purchase.album)
        return resp

    else:
        # Otherwise, return the same page again with an error
        error = "Invalid PIN. Please try again!"
        return downloadPrePin(request, purchase, error)

uWSGI 错误日志:

!!! uWSGI process 8094 got Segmentation Fault !!!
*** backtrace of 8094 ***
/home/web/.envs/music/bin/uwsgi(uwsgi_backtrace+0x2e) [0x46a1be]
/home/web/.envs/music/bin/uwsgi(uwsgi_segfault+0x21) [0x46a581]
/lib/x86_64-linux-gnu/libc.so.6(+0x36c30) [0x7fdc1ffd6c30]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyObject_Malloc+0x248) [0x7fdc206ef508]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyString_FromStringAndSize+0xa2) [0x7fdc206de232]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyEval_EvalFrameEx+0x3be7) [0x7fdc206fe0e7]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyEval_EvalCodeEx+0x80d) [0x7fdc2070117d]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyEval_EvalFrameEx+0x48d8) [0x7fdc206fedd8]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyEval_EvalCodeEx+0x80d) [0x7fdc2070117d]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(+0x162310) [0x7fdc20701310]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyObject_Call+0x43) [0x7fdc206c8e23]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(+0x7d30d) [0x7fdc2061c30d]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyObject_Call+0x43) [0x7fdc206c8e23]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyEval_CallObjectWithKeywords+0x47) [0x7fdc20687837]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(+0xff706) [0x7fdc2069e706]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyEval_EvalFrameEx+0x2920) [0x7fdc206fce20]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(+0x161883) [0x7fdc20700883]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(+0x1a7d02) [0x7fdc20746d02]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PySequence_List+0x2c) [0x7fdc207471bc]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PySequence_Fast+0x3d) [0x7fdc2074807d]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(+0x1aa995) [0x7fdc20749995]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyEval_EvalFrameEx+0x4abc) [0x7fdc206fefbc]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyEval_EvalCodeEx+0x80d) [0x7fdc2070117d]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(+0x162310) [0x7fdc20701310]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyObject_Call+0x43) [0x7fdc206c8e23]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyObject_CallFunction+0xbb) [0x7fdc20706efb]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(+0x16a15d) [0x7fdc2070915d]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(_PyObject_GenericSetAttrWithDict+0x107) [0x7fdc205f00b7]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyObject_SetAttr+0x8f) [0x7fdc206aeadf]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyEval_EvalFrameEx+0x1cda) [0x7fdc206fc1da]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyEval_EvalCodeEx+0x80d) [0x7fdc2070117d]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(+0x1623e5) [0x7fdc207013e5]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyObject_Call+0x43) [0x7fdc206c8e23]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(+0x7d30d) [0x7fdc2061c30d]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyObject_Call+0x43) [0x7fdc206c8e23]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(+0x12e48f) [0x7fdc206cd48f]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(+0x12c4df) [0x7fdc206cb4df]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyObject_Call+0x43) [0x7fdc206c8e23]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyEval_EvalFrameEx+0x2316) [0x7fdc206fc816]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyEval_EvalFrameEx+0x4b59) [0x7fdc206ff059]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyEval_EvalFrameEx+0x4b59) [0x7fdc206ff059]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyEval_EvalCodeEx+0x80d) [0x7fdc2070117d]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(+0x1623e5) [0x7fdc207013e5]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyObject_Call+0x43) [0x7fdc206c8e23]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyEval_EvalFrameEx+0xeb1) [0x7fdc206fb3b1]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyEval_EvalCodeEx+0x80d) [0x7fdc2070117d]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyEval_EvalFrameEx+0x48d8) [0x7fdc206fedd8]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyEval_EvalCodeEx+0x80d) [0x7fdc2070117d]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(+0x162310) [0x7fdc20701310]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyObject_Call+0x43) [0x7fdc206c8e23]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(+0x7d30d) [0x7fdc2061c30d]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyObject_Call+0x43) [0x7fdc206c8e23]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(+0x12e5f5) [0x7fdc206cd5f5]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyObject_Call+0x43) [0x7fdc206c8e23]
/usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0(PyEval_CallObjectWithKeywords+0x47) [0x7fdc20687837]
/home/web/.envs/music/bin/uwsgi(python_call+0x11) [0x4808a1]
/home/web/.envs/music/bin/uwsgi(uwsgi_request_wsgi+0x116) [0x482a96]
/home/web/.envs/music/bin/uwsgi(wsgi_req_recv+0xa2) [0x41f1f2]
/home/web/.envs/music/bin/uwsgi(simple_loop_run+0xc4) [0x466664]
/home/web/.envs/music/bin/uwsgi(uwsgi_ignition+0x194) [0x46a7d4]
/home/web/.envs/music/bin/uwsgi(uwsgi_worker_run+0x2dd) [0x46f02d]
/home/web/.envs/music/bin/uwsgi(uwsgi_run+0x3b4) [0x46f554]
/home/web/.envs/music/bin/uwsgi(_start+0) [0x41e8ae]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf5) [0x7fdc1ffc1ec5]
*** end of backtrace ***
DAMN ! worker 9 (pid: 8094) died :( trying respawn ...
Respawned uWSGI worker 9 (new pid: 8371)

出现问题是因为您的进程无法分配内存块。

当我第一次看您的代码时,引起我注意的第一件事是您将文件的全部内容加载到内存中,并在每次迭代时一次读取它们。

zip.writestr("{0} - {1}.{2}".format(num, name, ext), f.read())
这里的

f.read()会一次性读取文件的全部内容并放入内存,然后尝试写入内存的另一段,即StringIO对象,反过来最终在 RAM 的不同部分存储相同的数据两次

我建议您一次从文件一行读取或者至少合理数量的数据并且不要忘记完成后关闭文件:

for i, f in enumerate(track_files):
    f.seek(0)
    ...
    while True:
        data = f.read(2**16)
        if not data:
            break
        else:
            zip.write(data)
    ...
    f.close()

您可能想查看 this SO answer 关于逐块写入 zipfile.ZipFile 实例的内容。