У нас есть приложение, которое работает на Tomcat 6 (6.0.35.0, если быть точным), и большинство наших инженеров на Mac OS испытывают проблемы с запуском Tomcat из-за вызова socketAccept в методе Catalina.await, бросающего SocketException:
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 с Java 1.7, за последние несколько месяцев многие из нас перешли на Macbook Pros. До сих пор единственным симптомом были случайные ответы с нулевым байтом от 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/singleton overhead), по-видимому, является фактором, который заставляет это влиять на запуск Tomcat; точка опрокидывания составляет около 160 секунд. Мы можем смягчить эту проблему, отключив некоторые необязательные контексты, которые нам не нужны во время разработки, чтобы сократить время запуска, но я бы предпочел найти основную причину.
Конфигурация приложения
Специфика приложения слишком сложна, чтобы входить в слишком подробные сведения, но у меня есть догадка, что это может относиться к раннему связыванию, поэтому я, по крайней мере, буду перечислять прослушивающие порты на моей машине:
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
- Отключение IPv6 полностью на главном интерфейсе Ethernet
- Установка контрольных точек и удаление первых контекстов, на которые влияет SocketException (они являются исходящими HTTP-вызовами веб-служб). Без изменений
- Конфигурирование
/etc/hosts
, так что имя хоста компьютера разрешается на localhost и настраивает параметры JVM для предпочтения IPv4 и не предпочитает адреса IPv6 (этот ответ: qaru.site/info/187549/...)
Для тех, кто интересуется конфигурацией хостов, он аналогичен по умолчанию. Я могу воспроизвести это на Fusion VM с чистой установкой 10.8:
##
# 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-кода
Из-за очевидного чувствительного к времени характера проблемы установка контрольных точек для устранения неполадок не приводит к ее возникновению. В соответствии с просьбой в комментариях я также взял arg0
для SocksSocketImpl(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 не должны пузыриться из собственного кода и вызывать эти исключения одновременно на нескольких потоках - это, два потока, вызывающие исходящие вызовы веб-сервисов, ожидание вызова Tomcat и несколько потоков процессора TP.
Исследование кода JNI
Учитывая общее сообщение, я предположил, что ошибка из EINVAL
должна быть возвращена из одного из системных вызовов в коде socketSccept JNI, поэтому я проследил системные вызовы, ведущие к исключению; там нет EINVAL
, возвращаемого с любого системного вызова. Итак, я пошел в источники OpenJDK, ища условия в коде socketAccept, который бы установил, а затем выбросил EINVAL
, но я также не смог найти код, который устанавливает errno
в EINVAL
, или вызывает NET_ThrowByNameWithLastError
, NET_ThrowCurrent
или NET_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
Итак, я думаю, что проблема возникает в коде обработки тайм-аута в верхней части цикла accept в socketAccept
, но я не мог найти случай, когда NET_Timeout
установил errno
в EINVAL
, и в результате возникает это 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, но почти все здесь затронуты. Должна быть какая-то конфигурация приложения, которая способствует, но я исчерпал все возможности, которые я могу придумать, чтобы найти основную причину.
Указатели на устранение неполадок или понимание возможной причины были бы очень оценены.