由于 Mac OS X 上的 'java.net.SocketException Invalid argument',Tomcat 启动失败

标签 tomcat java-native-interface java

我们有一个在 Tomcat 6(准确地说是 6.0.35.0)上运行的应用程序,由于 Catalina.await 方法中的 socketAccept 调用抛出了 SocketException,我们在 Mac OS 上的大多数工程师在启动 Tomcat 时都遇到了问题:

SEVERE: StandardServer.await: accept:
java.net.SocketException: Invalid argument
      at java.net.PlainSocketImpl.socketAccept(Native Method)
      at java.net.PlainSocketImpl.socketAccept(PlainSocketImpl.java)
      at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:398)
      at java.net.ServerSocket.implAccept(ServerSocket.java:522)
      at java.net.ServerSocket.accept(ServerSocket.java:490)
      at org.apache.catalina.core.StandardServer.await(StandardServer.java:431)
      at org.apache.catalina.startup.Catalina.await(Catalina.java:676)
      at org.apache.catalina.startup.Catalina.start(Catalina.java:628)
      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      at java.lang.reflect.Method.invoke(Method.java:601)
      at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:289)
      at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:414)
      at mycompany.tomcat.startup.ThreadDumpWrapper.main(ThreadDumpWrapper.java:260)
      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      at java.lang.reflect.Method.invoke(Method.java:601)
      at org.tanukisoftware.wrapper.WrapperStartStopApp.run(WrapperStartStopApp.java:238)
      at java.lang.Thread.run(Thread.java:722)

这导致Tomcat在启动后立即关闭(并且不小的愤怒)。
我们认为这在 Mac OS w/Java 1.7 上一直伴随着我们,在过去的几个月里,我们很多人都转向了 Macbook Pro。到目前为止,唯一的症状是偶尔来自 Tomcat 的零字节响应,因为这个异常也会在 socketRead 上抛出。错误不会出现在日志中,我们将其作为一个孤立的问题单独耸了耸肩,只有在启动问题开始时才找到原因,我设置了 SocketException 断点:
Daemon Thread [http-8080-1] (Suspended (breakpoint at line 47 in SocketException))  
  SocketException.<init>(String) line: 47 
  SocketInputStream.socketRead0(FileDescriptor, byte[], int, int, int) line: not available [native method] 
  SocketInputStream.socketRead0(FileDescriptor, byte[], int, int, int) line: not available  
  SocketInputStream.read(byte[], int, int, int) line: 150 
  SocketInputStream.read(byte[], int, int) line: 121  
  InternalInputBuffer.fill() line: 735  
  InternalInputBuffer.parseRequestLine() line: 366  
  Http11Processor.process(Socket) line: 814 
  Http11Protocol$Http11ConnectionHandler.process(Socket) line: 602  
  JIoEndpoint$Worker.run() line: 489  
  Thread.run() line: 722  

对于参数:
arg0  FileDescriptor  (id=499)  
  fd  1097  
  useCount  AtomicInteger  (id=503) 
    value 2 
arg1  (id=502)
arg2  0 
arg3  8192  
arg4  20000 

问题是时间敏感的。由于应用程序更改而增加启动时间(更多 Spring 内省(introspection)/单例开销)似乎是导致这影响 Tomcat 启动的因素;临界点大约是 160 秒。我们可以通过禁用一些我们在开发过程中不需要的非强制上下文来减少启动时间来缓解这个问题,但我更愿意找到根本原因。

应用配置

应用程序的细节太复杂,无法详细介绍,但我有一种预感,这可能与较早的绑定(bind)有关,因此我至少会列出我机器上的监听端口:
localhost:32000 - Java service wrapper port
*:10001         - RMI registry
*:2322          - Java debug
*:56566         - RMI
*:8180          - Tomcat HTTP connector
*:8543          - Tomcat HTTPS connector
*:2223          - Tomcat Internal HTTP connector (used for cross-server requests)
*:14131         - 'Locking' port to determine if an internal service is running
*:56571         - EhCache RMI
*:56573         - RMI
*:62616         - ActiveMQ broker
*:5001          - SOAPMonitorService
*:8109          - Tomcat shutdown port

排除的项目
  • 最明显的解决方案:-Djava.net.preferIPv4Stack=true .我总是配置该选项
  • 最近对我们的基本应用程序配置、库、JVM 选项的任何配置更改(没有任何更改)
  • JDK 回归。我已经测试了 JDK 1.7.0_09、11、15、17 和 21(在此期间我已经在我的机器上安装的 JDK)
  • Mac 操作系统更新。 Mac OS 10.7.x 和 10.8.0 到 1.8.3 受到影响
  • 文件描述符限制 - 从 5000 增加至 10000
  • 在主以太网接口(interface)上完全禁用 IPv6
  • 设置断点,并删除第一个受 SocketException 影响的上下文(它们是对 Web 服务的传出 HTTP 调用)。无变化
  • 配置 /etc/hosts所以机器主机名解析为 localhost,并配置 JVM 选项以首选 IPv4 而不是首选 IPv6 地址(此答案:https://stackoverflow.com/a/16318860/364206)

  • 对于那些对主机配置感兴趣的人,它与默认值相同。我可以在带有 10.8 全新安装的 Fusion VM 上重现此内容:
    ##
    # Host Database
    #
    # localhost is used to configure the loopback interface
    # when the system is booting.  Do not change this entry.
    ##
    127.0.0.1   localhost
    255.255.255.255 broadcasthost
    ::1             localhost
    fe80::1%lo0 localhost
    

    Java代码调查

    由于问题的明显时间敏感性,设置断点来解决问题会导致它不会发生。根据评论中的要求,我还捕获了 arg0SocksSocketImpl(PlainSocketImpl).socketAccept(SocketImpl) ,似乎没有什么异常。
    arg0  SocksSocketImpl  (id=460) 
      address InetAddress  (id=465) 
        canonicalHostName null  
        holder  InetAddress$InetAddressHolder  (id=475) 
          address 0 
          family  0 
          hostName  null  
      applicationSetProxy false 
      closePending  false 
      cmdIn null  
      cmdOut  null  
      cmdsock null  
      CONNECTION_NOT_RESET  0 
      CONNECTION_RESET  2 
      CONNECTION_RESET_PENDING  1 
      external_address  null  
      fd  FileDescriptor  (id=713)  
        fd  -1  
        useCount  AtomicInteger  (id=771) 
          value 0 
      fdLock  Object  (id=714)  
      fdUseCount  0 
      localport 0 
      port  0 
      resetLock Object  (id=716)  
      resetState  0 
      server  null  
      serverPort  1080  
      serverSocket  null  
      shut_rd false 
      shut_wr false 
      socket  Socket  (id=718)  
        bound false 
        closed  false 
        closeLock Object  (id=848)  
        connected false 
        created false 
        impl  null  
        oldImpl false 
        shutIn  false 
        shutOut false 
      socketInputStream null  
      stream  false 
      timeout 0 
      trafficClass  0 
      useV4 false 
    

    我认为抛出异常的所有线程都是早期调用的受害者,该调用不会导致 SocketException,因此我无法捕获它。能够通过减少启动时间来启动 Tomcat 使我相信触发器可能是一些执行基于套接字的操作的计划任务,然后会影响其他套接字操作。

    这并没有解释这如何以及为什么会影响多个线程,无论我们做什么来导致这种情况,神秘的 SocketExceptions 不应该从 native 代码中冒泡并在多个线程上同时导致这些异常 - 即两个线程进行传出 Web 服务调用、Tomcat 等待调用和多个 TP 处理器线程。

    JNI代码调查

    鉴于通用消息,我假设 EINVAL错误必须从 socketAccept JNI 代码中的系统调用之一返回,因此我跟踪了导致异常的系统调用;没有 EINVAL从任何系统调用返回。所以,我去 OpenJDK 源代码中寻找 socketAccept 代码中的条件,这些条件会设置然后抛出 EINVAL ,但我也找不到任何设置 errno 的代码至 EINVAL ,或调用 NET_ThrowByNameWithLastError , NET_ThrowCurrentNET_ThrowNew以某种方式抛出带有此默认错误消息的 SocketException。

    就系统调用而言,我们似乎没有达到 accept 系统调用的程度:
     PID/THRD        RELATIVE   ELAPSD    CPU SYSCALL(args)    = return
     6606/0x2c750d:  221538243       5      0 sigprocmask(0x1, 0x0, 0x14D8BE100)    = 0x0 0
     6606/0x2c750d:  221538244       3      0 sigaltstack(0x0, 0x14D8BE0F0, 0x0)     = 0 0
     6606/0x2c750d:  221538836      14     10 socket(0x2, 0x1, 0x0)    = 1170 0
     6606/0x2c750d:  221538837       3      0 fcntl(0x492, 0x3, 0x4)     = 2 0
     6606/0x2c750d:  221538839       3      1 fcntl(0x492, 0x4, 0x6)     = 0 0
     6606/0x2c750d:  221538842       5      2 setsockopt(0x492, 0xFFFF, 0x4)     = 0 0
     6606/0x2c750d:  221538852       7      4 bind(0x492, 0x14D8BE5D8, 0x10)     = 0 0
     6606/0x2c750d:  221538857       5      2 listen(0x492, 0x1, 0x4)    = 0 0
     6606/0x2c750d:  221539625       6      2 psynch_cvsignal(0x7FEFBFE00868, 0x10000000200, 0x100)    = 257 0
     6606/0x2c750d:  221539633       4      1 write(0x2, "Apr 18, 2013 11:05:35 AM org.apache.catalina.core.StandardServer await\nSEVERE: StandardServer.await: accept: \njava.net.SocketException: Invalid argument\n\tat java.net.PlainSocketImpl.socketAccept(Native Method)\n\tat java.net.PlainSocketImpl.socketAcce", 0x644)    = 1604 0
    

    所以,我认为问题发生在 socketAccept 中接受循环顶部的超时处理代码中。 ,但我找不到任何情况下 NET_Timeout将设置 errnoEINVAL ,并导致抛出此 SocketException。我指的是这段代码;我假设 jdk7u 分支大部分是 Oracle JDK 中的:
  • http://hg.openjdk.java.net/jdk7u/jdk7u/jdk/file/d4bf5c15837c/src/solaris/native/java/net/PlainSocketImpl.c
  • http://hg.openjdk.java.net/jdk7u/jdk7u/jdk/file/d4bf5c15837c/src/solaris/native/java/net/bsd_close.c
  • http://hg.openjdk.java.net/jdk7u/jdk7u/jdk/file/d4bf5c15837c/src/solaris/native/java/net/net_util_md.c

  • 帮助!

    我在 Mac OS 上找不到任何受此特定问题影响的外部世界,但几乎这里的每个人都受到影响。一定有一些应用程序配置有所贡献,但我已经用尽了我能想到的所有途径来找到根本原因。

    将不胜感激有关故障排除或洞察可能原因的指针。

    最佳答案

    你试过了吗turning on JNI debugging-Xcheck:jni ?有趣的是Oracle documentation使用 PlainSocketImpl.socketAccept错误作为何时使用它的示例。

    还要注意 Bug 7131399 的含义是 JNI 使用 poll()在大多数平台上,但 select()由于 poll() 的问题,在 Mac OS 上在 Mac 上。所以也许select()也坏了。再深入一点,如果“ndfs 大于 FD_SETSIZE 并且 _DARWIN_UNLIMITED_SELECT 未定义”,则 select() 将返回 EINVAL。 FD_SETSIZE 是 1024,听起来你有大量的应用程序正在加载,所以也许它会过滤到一次等待超过 1024 个 FD。

    如需额外积分,请查看 the related (supposedly fixed) Java bug实际上固定在您的机器上。错误报告包含指向测试用例的指针。

    感谢 Old Pro 的回答,我确认了 select() FD_SETSIZE 限制是原因。我找到了此限制的现有错误:

    https://bugs.openjdk.java.net/browse/JDK-8021820

    可以使用以下代码重现该问题:

    import java.io.*;
    import java.net.*;
    
    public class SelectTest {
      public static void main(String[] args) throws Exception {
        // Use 1024 file descriptors. There'll already be some in use, obviously, but this guarantees the problem will occur
        for(int i = 0; i < 1024; i++) {
          new FileInputStream("/dev/null");
        }
        ServerSocket socket = new ServerSocket(8080);
        socket.accept();
      }
    }
    

    将近一年后,Java 7u60 修复了这个问题:

    http://www.oracle.com/technetwork/java/javase/2col/7u60-bugfixes-2202029.html

    我还发现 Tomcat 的 WebappClassLoader 在 90 秒后关闭文件句柄,这解释了为什么设置断点阻止了该问题的发生。

    关于由于 Mac OS X 上的 'java.net.SocketException Invalid argument',Tomcat 启动失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16191236/

    相关文章:

    tomcat - 基于 Tomcat 配置的 Worklight WAS Liberty 配置文件配置

    java - 为什么tomcat改jsp后不需要重启

    android jni ndk-build------------> 未定义对 `std::allocator<char>::allocator()' 的引用

    java - 使用来自 Blender 2.62+ 的 .obj 文件,在 java 中与 Eclipse for Android openGL ES

    java - 在做 Android 教程时 R.java 没有生成?

    eclipse - Eclipse Java EE IDE 是否随 Tomcat 一起提供?

    java - 构建 impl.xml :1031: The module has not been deployed

    java - 64 位编译器中的 JNI 未解析的外部符号 __imp_JNI_CreateJavaVM

    android - native 库中的 OpenCV 创建错误

    java - 如何从speechMatics中获取转录文本