Analyse du paramètre de configuration connectTimeout dans Feign

Dans mon projet actuel, nous utilisons Spring Cloud avec FeignClient comme client RPC. Nous rencontrons souvent des problèmes de timeout. Un collègue m'a conseillé la configuration suviante pour éviter les erreurs dues à un délai trop court :

feign:
  client:
    config:
      default:
        connectTimeout: 5000
        readTimeout: 10000

Le problème de timeout est résolu, tout fonctionne. Mais une question demeure : que signifie exactement connectTimeout ?

readTimeout est clair : on configure le socket en mode non-bloquant et on mesure le temps d'une opération de lecture. Si ce temps dépasse la valeur donnée, on lève une exception ou on retourne les données lues. Mais connectTimeout ? La signature de la fonction connect :

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

Aucun paramètre de timeout. Alors, d'où vient ce délai ?

Plongeons dans le code source de FeignClient pour comprendre. Grâce à un IDE puissant, nous localisons l'utilisation de ce paramètre :

// Dans FeignClientConfiguration
private int connectTimeout;

public int getConnectTimeout() {
    return connectTimeout;
}

Le paramètre est ensuite injecté dans l'objet Request :

// Dans Request
private int connectionTimeoutMillis;

public Request build(...) {
    connectionTimeoutMillis = configuration.getConnectTimeout();
}

Le paramètre est finalement utilisé dans HttpURLConnection lors de l'établissement de la connexion. Suivons le flux :

  1. Feign crée un HttpURLConnection avec le timeout.
  2. HttpURLConnection.connect() appelle plainConnect() puis plainConnect0().
  3. La connexion est créée via getNewHttpClient() qui instancie un HttpClient.
  4. Ce dernier configure le timeout et ouvre la connexion via openServer().
  5. La méthode doConnect() de HttpClient utilise java.net.Socket.connect(SocketAddress, int timeout).

Le code natif de Socket.connect est implémenté en C. Voici l'extrait pertinent (simplifié) :

// Dans PlainSocketImpl.c (JVM)
if (timeout > 0) {
    SET_NONBLOCKING(fd);
    connect_rv = connect(fd, (struct sockaddr *)&him, len);
    if (connect_rv != 0) {
        // Attente avec poll/select jusqu'à timeout
        while (1) {
            connect_rv = NET_Poll(&pfd, 1, timeout);
            if (connect_rv >= 0) break;
            if (errno != EINTR) break;
            timeout -= temps écoulé;
            if (timeout <= 0) {
                // Timeout : lève une exception
                JNU_ThrowByName(env, "java/net/SocketTimeoutException", "connect timed out");
                SET_BLOCKING(fd);
                JVM_SocketShutdown(fd, 2);
                return;
            }
        }
    }
}

Le foncitonnement est le suivant :

  1. Le socket est mis en mode non-bloquant.
  2. La fonction connect() est appelée ; elle échoue généralement immédiatement car non-bloquante.
  3. On boucle avec poll() ou select() pour attendre que la connexion soit établie.
  4. À chaque itération, on soustrait le temps écoulé du timeout. Si le timeout est épuisé, on lève SocketTimeoutException.

Ainsi, connectTimeout est implémenté au niveau natif en rendant le socket temporairement non-bloquant et en surveillant l'état de la connexion avec un mécanisme de polling. Cela permet de limiter le temps d'attente d'une connexion TCP.

Étiquettes: Feign Spring Cloud timeout HttpURLConnection Socket

Publié le 31 mai à 16h59