[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

[Un peu HS] Truc bizarre avec des sockets Linux



	Bonjour à tous,

	Je développe le logiciel suivant http://www.rpl2.fr et un utilisateur
du bout du monde vient de me remonter un bug bizarre sur la gestion des
sockets réseau TCP. Je viens de passer la journée dessus et je ne
comprends pas.

	Je précise que la fonction a été méchamment testée et qu'il me semble
qu'elle fonctionnait parfaitement lorsqu'elle a été publiée il y a de
cela plusieurs années.

	La séquence de commandes C effectuée est la suivante :
- création d'une socket avec socket() et bind() ;
- attente d'une connexion entrante avec accept() ;
- fork() et traitement du client dans le processus fils (y compris la
libération de la socket créée par accept()).

C'est ni plus ni moins que ceci (fonction serve_forever()):
https://gist.github.com/laobubu/d6d0e9beb934b60b2e552c2d03e1409e#file-httpd-c

	Dans le langage en question, ça se traduit comme ceci :

SERVEUR
<<
    // Création d'une socket TCP écoutant
    // sur le port 87 avec un maximum de 16
    // sockets clientes.

    {
        "local" "stream" "flow"
        { "protocol" "ipv4" }
        { "listen" 16 }
        { "port" 87 }
        { "option" "reuse address" }
    } open

    // Format binaire
    { "length*(*)" } swap format
    -> SOCKET
    <<
        do
           // On attend une connexion
            SOCKET wfsock

            // Si connexion, on lance un
            // traitement dans un processus
            // fils.
            'CIRCUIT_CONNECTE' detach
            // On efface le PID de la pile
            drop
            // On efface les deux sockets de la pile
            drop2
        until
            false
        end
     >>
>>

CIRCUIT_CONNECTE
<<
    "Socket connectée" disp
   'SOCKET' sto
   'FERMETURE_SOCKET' atexit

   do
      SOCKET "POLLIN" 2 ->list 1 ->list TIMEOUT_CONNEXION poll
      ...
   until
      ...
   end
>>

FERMETURE_SOCKET
<<
    SOCKET close
>>

	Si je remplace 'detach' (fork()) par 'spawn' (thread_create()), ça
fonctionne parfaitement bien. Ça peut tourner des heures (j'ai laissé le
processus fonctionner durant plusieurs heures, ce qui correspond à
plusieurs centaines de milliers de connexion sur la socket). Avec
'detach', le programme finit par planter faute de descripteur de socket
disponible (trop de fichiers ouverts).

	Je viens de passer le code dans valgrind et je ne comprends pas bien :

Root rayleigh:[~/exemple] > valgrind --track-fds=yes ./connecteur.rpl

+++RPL/2 (R) version 4.1.35 (Jeudi 23/11/2023, 16:42:36 CET)
+++Copyright (C) 1989 à 2022, 2023 BERTRAND Joël
...
socket: 7 <- renvoyée par accept() dans WFSOCK (la socket créée par
socket est la 6)
Socket connectée
close 7 <- fermeture de la socket 7 (par un shutdown() puis close())
close 7 OK
...
socket: 9
Socket connectée
close 9
close 9 OK
==20196== FILE DESCRIPTORS: 7 open (3 std) at exit.
==20196== Open AF_INET socket 7: 127.0.0.1:87 <-> unbound
==20196==    at 0x546950F: accept (accept.c:26)
==20196==    by 0x5C0661: librpl_instruction_wfsock
(instructions_w1-conv.c:3410)
==20196==    by 0x4CC36D: librpl_analyse (analyse-conv.c:1076)
==20196==    by 0x4D9C58: librpl_evaluation (evaluation-conv.c:764)
==20196==    by 0x5CF16D: librpl_sequenceur_optimise
(optimisation-conv.c:399)
==20196==    by 0x5D5A46: librpl_rplinit (rpl-conv.c:5198)
==20196==    by 0x466F9D: main (init-conv.c:29)
...

	Comment se fait-il que la socket soit toujours ouverte dans le père
(elle a été explicitement fermée dans le processus fils) ? Les
ressources système ne sont pas libérées. Pire, chaque socket cliente
reste ouverte pour le système et le processus parent.

	Je résous une partie du problème en rajoutant un close de la socket
cliente après un timeout dans le processus serveur (mais ce n'est pas
satisfaisant).

	Je constate aussi que si je ferme dans le processus fils la socket
initiale (celle créée par socket()), accept() râle. Le processus fils
peut donc fermer la socket en attente sur accept() mais s'il ferme la
socket() renvoyée par accept(), il ne la ferme que pour lui-même (et pas
pour le processus père).

	La question est donc : suis-je passé à côté de quelque chose ?

	J'en reviens donc à ce bout de code :
https://gist.github.com/laobubu/d6d0e9beb934b60b2e552c2d03e1409e#file-httpd-c

	Sauf erreur de ma part, dans la fonction serve_forever(), je trouve
bien un accept(), mais jamais de close() sur la socket créée par
accept() (plus exactement, je trouve le close dans le processus détaché
par fork()). Comment ce bout de code peut-il fonctionner sans qu'il ne
finisse par planter par un dépassement du nombre de fichiers ouverts ?

	Merci de votre attention,

	JB

PS: j'essaie de compiler sur un NetBSD, mais j'ai un problème de
symboles entre ncurses et readline :

ltiple definition of `UP';
../tools/readline-8.2/libreadline.a(terminal.o):(.bss+0xa8): first
defined here
/usr/bin/ld:
../tools/ncurses-6.4/lib/libncurses.a(lib_termcap.o):(.bss+0x0):
multiple definition of `BC';
../tools/readline-8.2/libreadline.a(terminal.o):(.bss+0xb0): first
defined here
/usr/bin/ld:
../tools/ncurses-6.4/lib/libncurses.a(lib_tputs.o):(.bss+0x6): multiple
definition of `PC';
../tools/readline-8.2/libreadline.a(terminal.o):(.bss+0xb8): first
defined here
collect2: erreur: ld a retourné le statut de sortie 1

À vue de nez, vu le méchant define dans readline, je vais avoir le même
avec FreeBSD.

Attachment: signature.asc
Description: OpenPGP digital signature


Reply to: