【问题标题】:Jetty takes 100% CPU when uploading multipart file and connection breaks上传多部分文件和连接中断时,Jetty 占用 100% CPU
【发布时间】:2016-05-03 14:00:54
【问题描述】:

Jetty 在上传分段文件和连接中断时占用 100% CPU

我们目前正在使用嵌入式 Jetty Server + Spring MVC 上传文件。 当我使用 curl 手动上传文件并故意使用 Ctrl^C 将其中断时,服务器上的 CPU 达到 100%,只有重新启动应用程序才能解决此问题。

curl -i -X POST -H "Content-Type: multipart/form-data" -F "param1=bla" -F "file=@a_file" http://myserver/bla/submit

我查看了其他解决此问题的帖子,但通常可以通过更新 Jetty API 版本和 Java 8 来解决。我已经这样做了,但同样的问题正在发生。可能和春天有关?

API:

  • 码头:9.3.6.v20151106
  • JRE:1.8_65(我没有使用JDK来运行应用程序)
  • Spring MVC:3.2.15

这是该方法的代码sn-p:

@RequestMapping(
            value = MY_PATH,
            method = RequestMethod.POST)
    public
    @ResponseBody
    void submitFile(
            @NotNull @RequestParam("param1") String param1,
            @NotNull @RequestParam("file") MultipartFile file) {
            //No code gets executed
            }

我已经使用 Java Visual VM 来监视并找到导致问题的线程转储:

"qtp529864074-68" - Thread t@68
   java.lang.Thread.State: RUNNABLE
    at sun.nio.ch.FileDispatcherImpl.writev0(Native Method)
    at sun.nio.ch.SocketDispatcher.writev(SocketDispatcher.java:51)
    at sun.nio.ch.IOUtil.write(IOUtil.java:148)
    at sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:504)
    - locked <6ddac168> (a java.lang.Object)
    at org.eclipse.jetty.io.ChannelEndPoint.flush(ChannelEndPoint.java:177)
    at org.eclipse.jetty.io.WriteFlusher.flush(WriteFlusher.java:419)
    at org.eclipse.jetty.io.WriteFlusher.write(WriteFlusher.java:313)
    at org.eclipse.jetty.io.AbstractEndPoint.write(AbstractEndPoint.java:141)
    at org.eclipse.jetty.server.HttpConnection$SendCallback.process(HttpConnection.java:735)
    at org.eclipse.jetty.util.IteratingCallback.processing(IteratingCallback.java:241)
    at org.eclipse.jetty.util.IteratingCallback.iterate(IteratingCallback.java:224)
    at org.eclipse.jetty.server.HttpConnection.send(HttpConnection.java:509)
    at org.eclipse.jetty.server.HttpChannel.sendResponse(HttpChannel.java:668)
    at org.eclipse.jetty.server.HttpChannel.write(HttpChannel.java:722)
    at org.eclipse.jetty.server.HttpOutput.write(HttpOutput.java:177)
    at org.eclipse.jetty.server.HttpOutput.write(HttpOutput.java:163)
    at org.eclipse.jetty.server.HttpOutput.write(HttpOutput.java:441)
    at org.eclipse.jetty.util.ByteArrayISO8859Writer.writeTo(ByteArrayISO8859Writer.java:109)
    at org.eclipse.jetty.server.Response.sendError(Response.java:606)
    at org.eclipse.jetty.server.Response.sendError(Response.java:512)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:650)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
    at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:548)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1160)
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:511)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1090)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
    at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:213)
    at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:109)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:119)
    at org.eclipse.jetty.server.Server.handle(Server.java:517)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:308)
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:242)
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:261)
    at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:95)
    at org.eclipse.jetty.io.SelectChannelEndPoint$2.run(SelectChannelEndPoint.java:75)
    at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.produceAndRun(ExecuteProduceConsume.java:213)
    at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.run(ExecuteProduceConsume.java:147)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:654)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:572)
    at java.lang.Thread.run(Thread.java:745)

【问题讨论】:

    标签: java spring jetty embedded-jetty


    【解决方案1】:

    查看线程转储,您会看到来自Response.sendError() 调用的.flush() 调用。这可能意味着您的 POST 失败(无论出于何种原因)并且正在尝试发送错误响应,但尚未完成,或者客户端尚未完成读取错误。 它甚至可能是 sendError 循环的结果(错误页面被查找、使用/发送到,并且该错误页面本身有一个错误导致另一个 sendError 触发)

    至于 Multipart 方面,您的 SpringMVC 设置似乎存在问题,因为 Jetty 处理 Multipart 上传的内容没有问题。

    在 Github 上为这个问题创建了一个示例项目。

    https://github.com/jetty-project/multipartconfig-example

    在 Jetty 中使用 Servlet API 执行此操作的示例。

    @MultipartConfig(location="/tmp/upload", 
                     fileSizeThreshold=1024*1024, 
                     maxFileSize=1024*1024*50)
    @WebServlet(urlPatterns={"/upload"}, name="upload")
    public class UploadServlet extends HttpServlet
    {
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
        {
            resp.setContentType("text/plain");
            PrintWriter out = resp.getWriter();
    
            int i=0;
            for(Part part: req.getParts())
            {
                out.printf("Got part: name=%s, size=%d%n",part.getName(), part.getSize());
                part.write(String.format("part-%02d.dat",i++));
            }
        }
    }
    

    如果我们在部署在 Jetty 发行版中的 war 文件中运行它,然后使用 Curl 对其进行测试,您将看到以下内容...

    $ curl -i -X POST -H "Content-Type: multipart/form-data" \
      -F "param1=bla" -F "file=@big.tar.gz" \
      http://localhost:8080/upload
    HTTP/1.1 100 Continue
    
    HTTP/1.1 200 OK
    Content-Type: text/plain;charset=iso-8859-1
    Content-Length: 65
    Server: Jetty(9.3.6.v20151106)
    
    Got part: name=file, size=23597555
    Got part: name=param1, size=3
    
    $ ls -la /tmp/upload/
    total 23052
    drwxrwxr-x.  2 joakim joakim       80 Jan 26 14:19 .
    drwxrwxrwt. 20 root   root        500 Jan 26 14:19 ..
    -rw-rw-r--.  1 joakim joakim 23597555 Jan 26 14:19 part-00.dat
    -rw-rw-r--.  1 joakim joakim        3 Jan 26 14:19 part-01.dat
    
    $ sha1sum /tmp/upload/part-00.dat big.tar.gz 
    13be13872d0203bae2f8a83ad457f8a0d259a2db  /tmp/upload/part-00.dat
    13be13872d0203bae2f8a83ad457f8a0d259a2db  big.tar.gz
    
    $ file /tmp/upload/part-01.dat 
    /tmp/upload/part-01.dat: ASCII text, with no line terminators
    
    $ cat /tmp/upload/part-01.dat 
    bla
    

    【讨论】:

    • 非常感谢您的回复。我们调试了 Jetty servlet,发现在确定的场景下,当 EofException 发生时,仍然会向端点发送响应,从而导致套接字进入空闲写入和 100% 的 CPU。更改源代码以中止 HttpChannel 解决了该问题。已在 Jetty 中打开了一个新的开发人员线程,以确定它是否是错误。
    • 手头有错误编号吗?
    • 一直在尝试几种不同的方法来使用 Jetty 和 servlet api 来复制它(只是为了确认/否认错误在 Jetty 中)。到目前为止,让客户端提前终止不会产生错误(来自 curl 的 Ctrl+C 方法),也不会让 Servlet 在读取请求正文内容的过程中抛出 Throwable。你有可以分享的可重复测试用例吗?
    • 是的,bugs.eclipse.org/bugs/show_bug.cgi?id=486826 上有详细信息。我将创建一个可重复的案例来附加它。到目前为止,我们能够在我们的系统上重现它并在那里调试它,但一个简单的案例会有所帮助。谢谢。
    • 大家好。我们终于发现这不是 Jetty 错误。我们有一个 JNI 进程向 JVM 发送无穷无尽的 SIGPIPE 信号。删除它解决了这个问题。
    猜你喜欢
    • 2011-02-07
    • 2015-04-05
    • 2019-12-31
    • 2012-12-29
    • 1970-01-01
    • 1970-01-01
    • 2022-12-15
    • 1970-01-01
    • 2011-11-09
    相关资源
    最近更新 更多