TCP-соединение зависает на SYN_SENT

Рассмотрим следующие клиентские и серверные компоненты:

import java.io.InputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;

public class client {
    public static void main(String[] args) throws IOException {
        while (true) {
            URL url = new URL("http://localhost:8000");
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            int statusCode = connection.getResponseCode();
            System.out.println("Status Code: " + statusCode);
            connection.disconnect();
        }
    }
}
import java.io.OutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class server {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8000);
        while (true) {
            Socket clientSocket = serverSocket.accept();
            OutputStream outputStream = clientSocket.getOutputStream();
            outputStream.write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n".getBytes());
            outputStream.flush();
            clientSocket.close();
        }
    }
}

Запустив клиент во время работы сервера, вы вскоре увидите, что клиент начинает зависать SYN_SENT на уровне TCP (всего около 30 секунд):

$ watch -n 0.1 "ss -on state syn-sent '( dport = :8000 )'"
Every 0.1s: ss -on state syn-sent '( dport = :8000 )'               myhost: Tue Jul 16 04:08:52 2024

Netid Recv-Q Send-Q      Local Address:Port        Peer Address:Port           Process
tcp   0      1           [::ffff:127.0.0.1]:60418  [::ffff:127.0.0.1]:8000     timer:(on,3.731ms,2)
$ pkill -3 java
# Stack trace of client's main thread while hanging outputted in Java terminal...
"main" #1 prio=5 os_prio=0 cpu=2429.68ms elapsed=40.96s tid=0x000079e6c40266c0 nid=0x18a1c6 runnable  [0x000079e6cb9fd000]
   java.lang.Thread.State: RUNNABLE
    at sun.nio.ch.Net.connect0([email protected]/Native Method)
    at sun.nio.ch.Net.connect([email protected]/Net.java:579)
    at sun.nio.ch.Net.connect([email protected]/Net.java:568)
    at sun.nio.ch.NioSocketImpl.connect([email protected]/NioSocketImpl.java:593)
    at java.net.Socket.connect([email protected]/Socket.java:633)
    at java.net.Socket.connect([email protected]/Socket.java:583)
    at sun.net.NetworkClient.doConnect([email protected]/NetworkClient.java:183)
    at sun.net.www.http.HttpClient.openServer([email protected]/HttpClient.java:533)
    at sun.net.www.http.HttpClient.openServer([email protected]/HttpClient.java:638)
    at sun.net.www.http.HttpClient.<init>([email protected]/HttpClient.java:281)
    at sun.net.www.http.HttpClient.New([email protected]/HttpClient.java:386)
    at sun.net.www.http.HttpClient.New([email protected]/HttpClient.java:422)
    at sun.net.www.protocol.http.HttpURLConnection.setNewClient([email protected]/HttpURLConnection.java:831)
    at sun.net.www.protocol.http.HttpURLConnection.setNewClient([email protected]/HttpURLConnection.java:819)
    at sun.net.www.protocol.http.HttpURLConnection.writeRequests([email protected]/HttpURLConnection.java:759)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream0([email protected]/HttpURLConnection.java:1708)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream([email protected]/HttpURLConnection.java:1611)
    at java.net.HttpURLConnection.getResponseCode([email protected]/HttpURLConnection.java:529)
    at client.main(client.java:13)
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0([email protected]/Native Method)
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke([email protected]/NativeMethodAccessorImpl.java:77)
    at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke([email protected]/DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke([email protected]/Method.java:568)
    at com.sun.tools.javac.launcher.Main.execute([email protected]/Main.java:419)
    at com.sun.tools.javac.launcher.Main.run([email protected]/Main.java:192)
    at com.sun.tools.javac.launcher.Main.main([email protected]/Main.java:132)

Я создаю Java-приложение (когда я столкнулся с этой проблемой, я пытался использовать интервал опроса 10 мс - мне показалось, что 30 мс работает), где мне нужно быстро отправлять HTTP-запросы, подобные этому, в течение определенного периода времени (я не могу использовать веб-сокеты). Итак, мой вопрос: почему происходит это зависание и как это исправить?

На данный момент моей лучшей попыткой исправить это было увеличение количества доступных дескрипторов файлов с обеих сторон (uname -n unlimitied), но безрезультатно.

Сейчас тестирую больше... Я также могу воспроизвести то же самое с Python:

import requests

while True:
    response = requests.get("http://localhost:8000")
    print(f"Status Code: {response.status_code}")

Тогда python -m http.server на сервер и вы SYN_SENT повесите. Итак, похоже, что проблема может быть глубже, чем я изначально предполагал, но мне любопытно и я готов услышать любое потенциальное решение.

Я хочу, чтобы эта SYN_SENT проблема с зависанием не возникала. Я хочу иметь возможность настроить интервал опроса HTTP-запросов так, чтобы он был очень низким (даже 1 мс между запросами в локальной сети; пока нет утечки ресурсов и все запросы выполняются последовательно, я не понимаю, почему это не так. Это достижимо), при этом мое Java или другое приложение работает совершенно надежно. Однако я также хочу понять проблему. Вид из Wireshark меня сбивает с толку, поскольку он показывает, что сервер вернул ответ HTTP 200 OK, но по какой-то причине клиент зависает во время его чтения (я пытался включить все необходимое, чтобы воспроизвести то, что я вижу, поскольку я занимался отладкой это часами). Спасибо за ваше время.


53
1

Ответ:

Решено

Я рад сообщить, что нашел причину этой проблемы! Я заметил, что всякий раз, когда у меня возникали эти SYN_SENT зависания (как показано ss), в моем dmesg также регистрировалось следующее:

nf_conntrack: nf_conntrack: table full, dropping packet

Таблица отслеживания соединений заполнялась! Каждый запрос HTTP 1.1, который я делал, выполнялся в собственном потоке TCP. Теперь все это имеет смысл.

Я использую довольно уникальный дистрибутив Linux, поэтому не уверен, что моя таблица отслеживания соединений меньше, чем в среднем. В моем приложении также было несколько проблем с утечкой сокетов/ресурсов, которые усугубляли проблему даже между отдельными программами в моей системе. Я еще не рассматривал возможность увеличения размера этой таблицы. Если вы столкнулись с этой проблемой, помните, что проблема также может заключаться в небольшой таблице подключений другого устройства в сети, например маршрутизатора, коммутатора или брандмауэра.

Я оставил свое приложение работающим на ночь с очень коротким интервалом опроса, чтобы посмотреть, что произойдет, и когда я проснулся, компилятор Java давал сбой, потому что systemd, кажется, заполнил /tmp этими nf_conntrack журналами. Вот тогда я подумал проверить dmesg, и вуаля! Затем я перезагрузился, чтобы очиститься tmpfs.