web-dev-qa-db-fra.com

Comment gérer un tuyau cassé (SIGPIPE) en python?

J'ai écrit un serveur de jeux multi-thread simple en python qui crée un nouveau thread pour chaque connexion client. Je constate que le serveur plante de temps en temps à cause d'une erreur SIGPIPE/canal cassé. Je suis presque sûr que cela se produit lorsque le programme tente d'envoyer une réponse à un client qui n'est plus présent.

Quel est un bon moyen de faire face à cela? Ma résolution préférée fermait simplement la connexion côté serveur au client et passait au lieu de quitter le programme entier.

PS: Cette question/réponse traite le problème de manière générique; comment spécifiquement dois-je le résoudre?

45
Adam Plumb

Lisez sur l'essai: déclaration.

try:
    # do something
except socket.error, e:
    # A socket error
except IOError, e:
    if e.errno == errno.EPIPE:
        # EPIPE error
    else:
        # Other error
37
S.Lott

En supposant que vous utilisiez le module de socket standard, vous devriez capturer l'exception socket.error: (32, 'Broken pipe') (et non pas IOError comme d'autres l'ont suggéré). Ceci sera soulevé dans le cas que vous avez décrit, c’est-à-dire que vous envoyez/écrivez sur un socket pour lequel le côté distant a été déconnecté.

import socket, errno, time

# setup socket to listen for incoming connections
s = socket.socket()
s.bind(('localhost', 1234))
s.listen(1)
remote, address = s.accept()

print "Got connection from: ", address

while 1:
    try:
        remote.send("message to peer\n")
        time.sleep(1)
    except socket.error, e:
        if isinstance(e.args, Tuple):
            print "errno is %d" % e[0]
            if e[0] == errno.EPIPE:
               # remote peer disconnected
               print "Detected remote disconnect"
            else:
               # determine and handle different error
               pass
        else:
            print "socket error ", e
        remote.close()
        break
    except IOError, e:
        # Hmmm, Can IOError actually be raised by the socket module?
        print "Got IOError: ", e
        break

Notez que cette exception ne sera pas toujours déclenchée lors de la première écriture dans un socket fermé - plus généralement lors de la seconde écriture (sauf si le nombre d'octets écrits dans la première écriture est supérieur à la taille de la mémoire tampon du socket). Gardez cela à l'esprit au cas où votre application pense que l'extrémité distante a reçu les données dès la première écriture alors qu'elle s'est peut-être déjà déconnectée.

Vous pouvez en réduire l'incidence (sans toutefois l'éliminer entièrement) en utilisant select.select() (ou poll). Vérifiez que les données sont prêtes à être lues par l'homologue avant de tenter une écriture. Si select indique qu'il existe des données pouvant être lues à partir du socket homologue, lisez-les à l'aide de socket.recv(). Si cela renvoie une chaîne vide, l'homologue distant a fermé la connexion. Dans la mesure où il existe toujours une condition de concurrence critique, vous devez toujours détecter et gérer l'exception.

Twisted est idéal pour ce genre de choses, cependant, on dirait que vous avez déjà écrit pas mal de code.

50
mhawke

SIGPIPE (même si je pense que vous voulez peut-être dire EPIPE?) se produit sur des sockets lorsque vous fermez un socket puis que vous lui envoyez des données. La solution simple est de ne pas éteindre le socket avant d’essayer de lui envoyer des données. Cela peut également se produire sur des tuyaux, mais cela ne ressemble pas à ce que vous vivez, car il s'agit d'un serveur de réseau.

Vous pouvez également simplement appliquer la solution consistant à intercepter l'exception dans un gestionnaire de niveau supérieur dans chaque thread.

Bien sûr, si vous utilisiez Twisted plutôt que de créer un nouveau thread pour chaque connexion client, vous n'auriez probablement pas ce problème. Il est très difficile (voire impossible, en fonction de votre application) d'obtenir un ordre correct des opérations d'écriture et de fermeture si plusieurs threads traitent avec le même canal d'E/S.

4
Glyph

Je suis confronté à la même question. Mais je soumets le même code la prochaine fois, ça marche ... .. La première fois, il a éclaté:

$ packet_write_wait: Connection to 10.. port 22: Broken pipe

La deuxième fois que cela fonctionne:

[1]   Done                    Nohup python -u add_asc_dec.py > add2.log 2>&1

J'imagine que la raison peut être liée à l'environnement serveur actuel.

0
yuan