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 :
- Feign crée un
HttpURLConnectionavec le timeout. HttpURLConnection.connect()appelleplainConnect()puisplainConnect0().- La connexion est créée via
getNewHttpClient()qui instancie unHttpClient. - Ce dernier configure le timeout et ouvre la connexion via
openServer(). - La méthode
doConnect()deHttpClientutilisejava.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 :
- Le socket est mis en mode non-bloquant.
- La fonction
connect()est appelée ; elle échoue généralement immédiatement car non-bloquante. - On boucle avec
poll()ouselect()pour attendre que la connexion soit établie. - À 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.