[solved] close on exec für sockets?

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
monty
User
Beiträge: 4
Registriert: Samstag 11. August 2007, 21:38

Hallo!

Ich komme gleich mal zu Sache. Folgendes Problem, dass mich jetzt seit zwei Tagen beschäftigt:

Über einen Webservice soll ein lokale Applikation auf dem Server gestartet werden können. Für den Service nutze ich z.Z. pylons (0.9.6rc2). Die zu startende Anwendung ist seinerseits eine Art Server, der also nur angestoßen werden soll und dann weiter läuft. Und da liegt jetzt mein Problem.

Mit os.fork() und os.exec*() (oder auch os.spwan*()) starten den Service, aber blockiert die WebSite, solange der Prozess läuft.

os.system (...) hat dieses Problem zwar nicht, aber scheinbar werden die geöffneten File- und Socket-Handler mit übergeben. Weil somit der Port belegt ist kann der Webservice nicht mehr starten. (Pylons startet den Server bei Veränderungen am Code neu, was beim Entwickeln recht häufig vor kommt.)

Oder gibt es eine andere Möglichkeit eine Prozess ohne offene FileHandler im Hintergrund zu starten?

Btw. hab mich erst gestern hier angemeldet und mich gewundert das mein Nick tatsächlich noch frei war. Ich hatte den Namen nach dem 3. Fehlversuch nur spaßeshalber mal eingegeben.:)
Zuletzt geändert von monty am Dienstag 21. August 2007, 22:37, insgesamt 1-mal geändert.
Benutzeravatar
veers
User
Beiträge: 1219
Registriert: Mittwoch 28. Februar 2007, 20:01
Wohnort: Zürich (CH)
Kontaktdaten:

So ganz verstehe ich dein Problem nicht. Welcher Port wird wann von wem belegt/gebraucht?

Bist du dir sicher das sich dein Problem nicht mit SO_REUSEADDR lösen lässt?

Und wie kommst du zum Schluss das os.system File- und Socket-Handles übergibt?
monty
User
Beiträge: 4
Registriert: Samstag 11. August 2007, 21:38

veers hat geschrieben:So ganz verstehe ich dein Problem nicht. Welcher Port wird wann von wem belegt/gebraucht?
Der Webservice läuft auf port 5000. Dieser startet einen neuen Prozess (per ork/exec, system, ...). Stoppe ich den Webserver kann ich diesen nicht wieder starten: socket.error: (98, 'Address already in use')

Erst nachdem ich den noch laufenden Prozess getötet habe ist dies wieder möglich. Dieser belegt also scheinbar diesen Port. Benötigen tut er diesen aber nicht.
veers hat geschrieben:Bist du dir sicher das sich dein Problem nicht mit SO_REUSEADDR lösen lässt?
Ich beschäftige mich erst seit einer Woche mit Python, daher sagt mir SO_REUSEADDR gerade nix. Ich vermute mal, dass es hiermit möglich ist die Adresse mehrmals zu belegen? Ich werde es mal versuchen. Da gibt's bestimmt sowas wie setenv(), oder wie kann ich das übergeben?

Eigentlich benötigt der 2. Prozess keine Socketverbindung. Ich habe versucht mit execvpe ein (fast) leeres env zu übergeben, aber das Problem bestand nach wie vor. Hab mich auch an das close-on-exec-flag in c erinnert aber nichts vergleichbares für python gefunden.
veers hat geschrieben:Und wie kommst du zum Schluss das os.system File- und Socket-Handles übergibt?
War eine Vermutung, da mit system() die selben Symptome wie bei fork/exec zu beobachten waren.
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

Erst nachdem ich den noch laufenden Prozess getötet habe ist dies wieder möglich. Dieser belegt also scheinbar diesen Port. Benötigen tut er diesen aber nicht.
Beim Forken werden die FDs des Elternprozesses dupliziert und da der (eigenständige) Kindprozess diese niemals schließt, ist der Port auch nach dem Beenden des Elternprozesses noch belegt. Der Gebrauch von SO_REUSEADDR wird eher nicht funktionieren, eben weil der Socket nicht geschlossen wird und deswegen auch nicht in den TIME_WAIT-Zustand übergeht.
Das close-on-exec-flag wäre

Code: Alles auswählen

import fcntl
fcntl.fcntl(foo.fileno(), fcntl.F_SETFL, fcntl.FD_CLOEXEC)
dann wird der fd bei einem exec-Aufruf geschlossen (wobei foo eben das Socketobject wäre). Alternativ kannst du die geöffneten FDs im Kindprozess schließen, z.B. mit

Code: Alles auswählen

import os
# Höchsten Wert für einen file descriptor ermitteln
try:
    import resource
    maxfds = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
    if maxfds > 1024:
        maxfds = 1024
except:
    maxfds = 256

# Wenn stdin/stdout/stderr auch noch geschlossen werden sollen, 0 anstatt 3
for fd in xrange(3, maxfds):
    try:
        os.close(fd)
    except OSError:
        pass
Zuletzt geändert von Trundle am Sonntag 12. August 2007, 19:50, insgesamt 1-mal geändert.
Benutzeravatar
veers
User
Beiträge: 1219
Registriert: Mittwoch 28. Februar 2007, 20:01
Wohnort: Zürich (CH)
Kontaktdaten:

monty hat geschrieben:Der Webservice läuft auf port 5000. Dieser startet einen neuen Prozess (per ork/exec, system, ...). Stoppe ich den Webserver kann ich diesen nicht wieder starten: socket.error: (98, 'Address already in use')
Ich verstehe es immer noch nicht. Habe wohl zu wenig geschlafen :?
Es tönt aber als wäre SO_REUSEADDR was du suchst.

Ich beschäftige mich erst seit einer Woche mit Python, daher sagt mir SO_REUSEADDR gerade nix. Ich vermute mal, dass es hiermit möglich ist die Adresse mehrmals zu belegen?[/quote]http://www.unixguide.net/network/socketfaq/4.5.shtml
monty
User
Beiträge: 4
Registriert: Samstag 11. August 2007, 21:38

Trundle hat geschrieben:Beim Forken werden die FDs des Elternprozesses dupliziert und da der (eigenständige) Kindprozess diese niemals schließt, ist der Port auch nach dem Beenden des Elternprozesses noch belegt.[...]
Genau das war meine Vermutung:)

@Trundle:
Ich habe es zwar gerade noch nicht zum laufen bringen können, aber der Anzatz sieht sehr vielversprechend aus. Ich werde es morgen nochmal genau unter die Lupe nehmen.

Soweit euch beiden erstmal vielen Danke für die Hilfe. Ich berichte, sobald es läuft:)
monty
User
Beiträge: 4
Registriert: Samstag 11. August 2007, 21:38

OK, hab jetzt endlich die Zeit gefunden die Sache zum Laufen zu bringen. Ich habe dabei den 2. Vorschlag umgesetzt, der die Deskriptoren erst im Kindprozess schließt. Einfach deshalb, weil ich mir das forken im Elternprozess sparen wollte.

Elternprozess:

Code: Alles auswählen

import os
os.system('child.py')
Kindprozess (child.py)

Code: Alles auswählen

import os
# Höchsten Wert für einen file descriptor ermitteln
try:
    import resource
    maxfds = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
    if maxfds > 1024:
        maxfds = 1024
except:
    maxfds = 256

# Wenn stdin/stdout/stderr auch noch geschlossen werden sollen, 0 anstatt 3
for fd in xrange(3, maxfds):
    try:
        os.close(fd)
    except OSError:
        pass

[...]
Nochmal danke für die Hilfe.
Antworten