2011-01-29 10 views
5

mam problem analogiczny do opisanego tutaj: Prevent fork() from copying socketsos.execute bez dziedziczenie FDS rodzica

Zasadniczo, w moim skrypcie Lua mam tarła inny skrypt, który:

  • nie wymagają komunikowania się z mojego skryptu albo sposób
  • kontynuuje pracę po mój skrypt skończył
  • jest 3rd Program partii, kod którego nie mam kontroli nad

Problemem jest to, że mój skrypt Lua otwiera gniazdo TCP nasłuchuje na określonym porcie i po to rzucić i mimo wyraźny server:close() dziecko (a dokładniej jego dzieci) posiada na gniazdka i utrzymuje port otwórz (w stanie LISTEN), zapobiegając ponownemu uruchomieniu skryptu.

Oto przykład kodu, który pokazuje problem:

require('socket') 

print('listening') 
s = socket.bind("*", 9999) 
s:settimeout(1) 

while true do 
    print('accepting connection') 
    local c = s:accept() 
    if c then 
      c:settimeout(1) 
      local rec = c:receive() 
      print('received ' .. rec) 
      c:close() 
      if rec == "quit" then break end 
      if rec == "exec" then 
        print('running ping in background') 
        os.execute('sleep 10s &') 
        break 
      end  
    end 
end 
print('closing server') 
s:close() 

Jeśli uruchomić powyższy skrypt i echo quit | nc localhost 9999 wszystko działa dobrze - Program zostanie zamknięty i port jest zamknięty.

Jeśli jednak wykonam echo exec | nc localhost 9999, program zostanie zamknięty, ale port zostanie zablokowany przez zarodnikowany sleep (potwierdzony przez netstat -lpn) do momentu jego wyjścia.

Jak rozwiązać ten problem w najprostszy możliwy sposób, najlepiej bez dodawania żadnych dodatkowych zależności.

Odpowiedz

3

znalazłem wiele prostsze rozwiązanie, które wykorzystuje fakt, że os.execute(cmd) biegnie cmd w shell, które, jak się okazuje, jest zdolny do zamykania deskryptorów plików, jak widać tutaj:


Na przykład (testowane w ash):

exec 3<&-          # closes fd3 
    exec 3<&- 4<&-         # closes fd3 and fd4 
    eval exec `seq 1 255 | sed -e 's/.*/&<\&-/'` # closes all file descriptors 

Więc w moim luasocket przykład opartej to wystarczy wymienić:

os.execute('sleep 10s &') 

z:

os.execute("eval exec `seq 1 255 | sed -e 's/.*/&<\\&-/'`; sleep 10s &") 

Powoduje zamknięcie wszystkich deskryptorów plików, w tym także gniazda mojego serwera, przed wykonaniem faktycznego polecenia (tutaj sleep 10s), tak aby nie powodował przejęcia portu po zakończeniu pracy mojego skryptu. Ma również premię dbania o przekierowanie stdout i stderr.

Jest to bardziej kompaktowy i nieskomplikowany sposób niż ograniczenia związane z Lua i nie wymaga żadnych dodatkowych zależności. Dziękuję #uclibc, w której dostałem świetną pomoc przy końcowej składni powłoki z wbudowanej obsady linuxowej.

+0

Dobra robota! Gdzie jest wola, jest sposób :-). Cieszę się, że wróciłeś z aktualizacją. –

2

Nie jestem pewien, czy będziesz w stanie to zrobić, jeśli chcesz zachować s:close tylko na końcu całego programu. Możesz odnieść sukces, przenosząc go przed os.execute, ponieważ i tak jesteś w stanie (ale prawdopodobnie nie robisz tego w swoim prawdziwym programie). Edytuj dla jasności: Rzeczywistym problemem jest to, że jedynym miejscem, w którym spawnujesz podproces w tym przypadku, jest użycie os.execute() i nie masz żadnej kontroli nad dziecięcym środowiskiem snu, w którym wszystko jest dziedziczone z głównego programu , w tym deskryptory gniazd i plików.

Więc kanoniczny sposób to zrobić na POSIX jest użycie fork(); close(s); exec(); zamiast system() (aka, os.execute) jako system()/os.execute zawiśnie na aktualnym stanie procesu w trakcie realizacji, a nie będą mogli zamknij go, gdy jest zablokowany w pod-procesie.

więc sugestia byłoby złapać luaposix i używać jej posix.fork() i posix.exec() funkcjonalność i nazywając s:close() w procesie potomnym fork ed. Nie powinno być tak źle, ponieważ korzystasz już z zewnętrznego pakietu, polegając na luasocket.


EDIT: kod tutaj jest mocno skomentował to zrobić z luaposix:

require('socket') 
require('posix') 

print('listening') 
s = socket.bind("*", 9999) 
s:settimeout(1) 

while true do 
    print('accepting connection') 
    local c = s:accept() 
    if c then 
      c:settimeout(1) 
      local rec = c:receive() 
      print('received ' .. rec) 
      c:close() 
      if rec == "quit" then break end 
      if rec == "exec" then 
        local pid = posix.fork() 
        if pid == 0 then 
         print('child: running ping in background') 
         s:close() 
         -- exec() replaces current process, doesn't return. 
         -- execp has PATH resolution 
         rc = posix.execp('sleep','60s'); 
         -- exec has no PATH resolution, probably "more secure" 
         --rc = posix.exec('/usr/bin/sleep','60s'); 
         print('exec failed with rc: ' .. rc); 
        else 
         -- if you want to catch the SIGCHLD: 
         --print('parent: waiting for ping to return') 
         --posix.wait(pid) 
         print('parent: exiting loop') 
        end 
        break; 
      end 
    end 
end 
print('closing server') 
s:close() 

to zamknięcie gniazda w procesie potomnym przed wywołaniem exec, a wyjście netstat -nlp pokazuje system jest poprawnie nie dłuższe słuchanie na porcie 9999, gdy rodzic wyjdzie.

P.S. Linia print('exec failed with rc: ' .. rc); skarżyła się na problem typu raz, gdy wykonanie nie powiodło się. Właściwie nie znam Lua, więc musisz to naprawić. :) Ponadto, fork() może zawieść, zwracając -1. Prawdopodobnie powinieneś to sprawdzić w swoim głównym kodzie, aby uzyskać kompletność.

+0

OK, to postęp, w którym potajemnie miałem nadzieję, że nie dojdzie do 'fork() ing'. 'luaposix' jest w porządku jako dep, ponieważ jest wbudowany w interpreter na docelowej platformie - OpenWrt. Problem z naszym przykładem "uśpienia" przerywającego port zniknął, ale został zastąpiony przez 2 kolejne: - jeśli usuniesz 'break' po' fork' i wyśle ​​'exec' do portu' s: accept()) 'nie przekracza limitów czasu i ma być blokowany. - jeśli zamienisz 'sleep' na' ping' jego wynik zostanie wyświetlony na tym samym poziomie, co wynik skryptu, który jest niepożądany. Mogą one wykraczać poza zakres pierwotnego pytania, ale są powiązane. – koniu

+0

Przepraszam, myopenid trochę tam poszedł. Grałem więcej z kodem na twoje inne pytania i myślę, że to wykracza poza moją wiedzę Lua. Próbowałem 'io.output ('/ dev/null')' i 'io.stdout: close()', przed 'execp()', bez powodzenia. Oto kilka powiązanych informacji: [stackoverflow] (http://stackoverflow.com/questions/1466064) i [instrukcja lua] (http://www.lua.org/pil/21.1.html). Na 'wait()', sprawdziłem źródło dla 'luaposix', i to nie implementuje' WNOHANG', więc może to stanowić problem dla nieblokowania. Jeśli nikt inny nie odpowie na to pytanie, możesz opublikować kolejną wiadomość Q dotyczącą tych problemów. –

+0

Znalazłem rozwiązanie problemu stdout tutaj: http://lua-users.org/wiki/HiddenFeatures. Zamyka standardowy plik: 'local f = assert (io.open '/ dev/null'); debug.setfenv (io.stdout, debug.getfenv (f)); f: close(); assert (io.stdout: close()) ' – koniu

1

Podejście POSIX polega na ustawieniu deskryptorów plików za pomocą flagi FD_CLOEXEC, przy użyciu fcntl (2). Po ustawieniu wszystkie podprocesy nie odziedziczą deskryptorów plików oznaczonych tą flagą.

Zdjęcie Lua nie ma funkcji fcntl, ale można ją dodać z modułem lua posix, jak opisano w poprzednich odpowiedziach. Aby wziąć przykład, trzeba by zmienić rozpoczyna się w następujący sposób:

require('socket') 
require('posix') 
s = socket.bind("*", 9999) 
posix.setfl(s, posix.FD_CLOEXEC) 
s:settimeout(1) 

Zauważ, że nie mogę znaleźć stałej FD_CLOEXEC w źródle luaposix więc może trzeba dodać ją ręcznie, jak również.

Powiązane problemy