Django 服务器崩溃,退出代码为 139、77

Django server crashes with exit codes 139, 77

前言

好的,我有一个非常复杂的性能问题。我正在构建一个内容管理系统,其中一个功能应该是使用不同的模板生成大量 .docx 文件。我从 Webodt + Abiword. But then templates got too complex, so I had to swith my backend to Templated-docs + LibreOffice 开始。所以这就是我的问题开始的地方。

我使用:

实际问题

我有一个 API 可以处理 .docx 渲染。我将展示其中一个视图,作为示例,它们非常相似:

@permission_classes((permissions.IsAdminUser,))
class BookDocxViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
    def retrieve(self, request, *args, **kwargs):
        queryset = Pupils.objects.get(id=kwargs['pk'])
        serializer = StudentSerializer(queryset)
        context = dict(serializer.data)
        doc = fill_template('crm/docs/book.ott', context, output_format='docx')
        p = u'docs/books/%s/%s_%s_%s.doc' % (datetime.now().date(), context[u'surname'], context[u'name'], datetime.now().date())
        with open(doc, 'rb') as f:
            content = f.read()
            path = default_storage.save(p, ContentFile(content))
            f.close()
        return response.Response(u'/media/' + path)

当我第一次调用它时,它会创建一个 .docx 文件,将其保存到我的 default_storage,然后 returns 我下载 link。但是当我尝试再次执行此操作时,使用另一种方法(使用另一个模板和上下文)执行此操作时,我的服务器只是崩溃而没有任何日志。我最后看到的是

  1. Process finished with exit code 77 如果我稍微延迟调用它(超过一秒)
  2. Process finished with exit code 139 (interrupted by signal 11: SIGSEGV) 如果立即第二次调用我的方法(不到一秒)

我尝试使用调试器 -- 它说我的服务器在这一行崩溃:

doc = fill_template('crm/docs/book.ott', context, output_format='docx')

我敢打赌会发生什么:

  1. 当我第一次调用我的方法时 templated_docs 启动 LibreOffice 后端,然后 没有停止它
  2. 当我第二次调用我的方法时 templated_docs 尝试再次启动 LibreOffice 后端,但它已经很忙了。

问题

  1. 我如何调试 LibreOffice 来证明/反驳我的理论? (我想我需要调试 templated_docs
  2. 为什么我会根据延迟获得不同的退出代码?
  3. GitHub 上 issue 的基础是否足够?
  4. 我该如何解决?

UPD

这不是 REST Framework 或未使用 FileResponce() 的问题。 我已经尝试用常规视图对其进行测试。

def get_document(request, *args, **kwargs):
    context = Pupils.objects.get(id=kwargs['pk']).__dict__
    doc = fill_template('crm/docs/book.ott', context, output_format='docx')
    p = u'%s_%s_%s' % (context[u'surname'], context[u'name'], datetime.now().date())
    return FileResponse(doc, p)

同样的问题


更新 2

好的。此行正在刷新我的服务器:

# pylokit/lokit.py
self.lokit = lo.libreofficekit_hook(six.b(lo_path))

好的,这是 templated_docs 中的一个错误。我是对的,这是因为 templated_docs 试图启动 LibreOffice 两次。正如pylokitdocumentation中所说:

The use of _exit() instead of default exit() is required because in some circumstances LibreOffice segfaults on process exit.

这意味着使用pylockt的进程应该在之后被杀死。但是我们不能杀死 Django 服务器。所以我决定使用多处理:

# templated_docs/__init__.py

if source_extension[1:] != output_format:
    lo_path = getattr(
        settings,
        'TEMPLATED_DOCS_LIBREOFFICE_PATH',
        '/usr/lib/libreoffice/program/')

    def f(conn):
        with Office(lo_path) as lo:
            conv_file = NamedTemporaryFile(delete=False,
                                           suffix='.%s' % output_format)
            with lo.documentLoad(str(dest_file.name)) as doc:
                doc.saveAs(conv_file.name)
            os.unlink(dest_file.name)
            conn.send(conv_file.name)
            conn.close()

    parent_conn, child_conn = Pipe()
    p = Process(target=f, args=(child_conn,))
    p.start()
    conv_file_name = parent_conn.recv()
    p.join()

    return conv_file_name
else:
    return dest_file.name

我打开了 issue and made a pull request