2011-01-29 17 views
5

tengo un problema análogo al descrito aquí: Prevent fork() from copying socketsos.execute sin heredar de los padres FDS

Básicamente, dentro de mi script Lua estoy desove otro script que:

  • no lo hace requerirá la comunicación con mi guión de cualquier manera
  • sigue corriendo detrás de mi guión había terminado
  • es un programa de 3 ª parte, el código de las que no tengo control sobre

El problema es que mi script Lua abre un socket TCP para escuchar en un puerto específico y después de que dejar de fumar y pesar una explícita server:close() el niño (o más específicamente sus hijos) se mantiene en el zócalo y mantiene el puerto abrir (en estado ESCUCHAR) impidiendo que mi secuencia de comandos se ejecute nuevamente.

Aquí es código de ejemplo que muestra el problema:

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() 

Si ejecuto el script de arriba y echo quit | nc localhost 9999 todo funciona bien - programa se cierra y el puerto está cerrado.

Sin embargo, si hago echo exec | nc localhost 9999, el programa se cierra pero el puerto está bloqueado por el generado sleep (confirmado por netstat -lpn) hasta que se cierra.

Cómo abordar esto de la manera más simple posible, preferiblemente sin agregar dependencias adicionales.

Respuesta

3

he encontrado una solución mucho más simple que utiliza el hecho de que os.execute(cmd) carreras cmd en un shell, que, como resulta, es capaz de descriptores de archivo de cierre como se ve aquí:


Por ejemplo (probado en ash):

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

Así que en mi ejemplo basado luasocket es suficiente para reemplazar:

os.execute('sleep 10s &') 

con:

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

Esto cierra todos los descriptores de archivos, incluido el socket de mi servidor, antes de ejecutar el comando real (aquí sleep 10s) para que no acapare el puerto una vez que haya salido mi script. También tiene la ventaja de ocuparse de la redirección stdout y stderr.

Esto es mucho más compacto y sin complicaciones que trabajar con las limitaciones de Lua y no requiere ninguna dependencia adicional. Gracias a #uclibc donde obtuve una ayuda brillante con la sintaxis final de shell de la tripulación integrada de Linux.

+0

¡Bien hecho! Donde hay voluntad, hay una forma :-) Me alegro de que hayas vuelto con una actualización también. –

2

No estoy seguro si podrá hacerlo así si desea mantener el s:close solo al final de todo el programa. Puede tener éxito moviéndolo hacia arriba antes de os.execute, ya que está break ing de todos modos (pero probablemente no esté haciendo esto en su programa real). Editar para mayor claridad: El problema real es que el único lugar en el que está generando un subproceso en este caso es usando os.execute(), y no tiene control sobre el entorno secundario de suspensión, en el que todo se hereda del programa principal , incluidos los descriptores de socket y archivo.

Por lo tanto, la forma canónica de hacer esto en POSIX es utilizar fork(); close(s); exec();, en lugar de system() (aka, os.execute) como system()/os.execute colgará en el estado actual proceso durante la ejecución, y no podrán ciérrelo mientras está bloqueado en el subproceso.

Así, una sugerencia sería para agarrar luaposix, y utilizar su funcionalidad posix.fork() y posix.exec(), y llamando s:close() en el proceso hijo fork ed. No debería ser tan malo, ya que ya está usando un paquete externo al confiar en luasocket.


EDITAR: Código Aquí está fuertemente comentado hacerlo con 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() 

Esto cierra el socket en el proceso hijo antes de llamar exec, y la salida netstat -nlp muestra el sistema está correctamente sin escuchando más en el puerto 9999 cuando el padre sale.

P.S. La línea print('exec failed with rc: ' .. rc); se quejó de un problema de tipo una vez cuando falló la ejecución. Realmente no conozco a lua, así que tendrás que arreglar eso. :) Además, fork() puede fallar, devolviendo -1. Probablemente también debería verificar eso en su código principal, para estar completo.

+0

bien, es el progreso aunque yo estaba esperando en secreto que no iba a venir a 'tenedor() ing'. 'luaposix' está bien como dep, ya que está integrado en el intérprete en la plataforma de destino - OpenWrt. El problema con nuestro ejemplo 'sleep' acaparando el puerto se ha ido, pero ha sido sustituido por 2 más: - si le quitas el' 'break' después fork' y enviar' exec' al puerto 's: aceptar () 'ya no se agota y debe ser no bloqueante. - si reemplaza 'sleep' con' ping', su salida será igual a la salida de script que no es deseable. Estos pueden ir más allá del alcance de la pregunta original, pero están relacionados. – koniu

+0

Lo siento, myopenid bajó un poco allí. Jugué más con el código según tus otras preguntas, y creo que esto está más allá de mi conocimiento lua. Intenté 'io.output ('/ dev/null')' y 'io.stdout: close()', antes de 'execp()', sin suerte. Aquí hay información relacionada: [stackoverflow] (http://stackoverflow.com/questions/1466064) y el [manual de lua] (http://www.lua.org/pil/21.1.html). En 'wait()', verifiqué el origen de 'luaposix', y no implementa' WNOHANG', por lo que podría ser un problema para no bloquear. Si nadie más responde a esto, puede publicar otra Q sobre estos problemas. –

+0

Encontré una solución al problema de stdout aquí: http://lua-users.org/wiki/HiddenFeatures. Esto cierra el archivo estándar: 'local f = assert (io.open '/ dev/null'); debug.setfenv (io.stdout, debug.getfenv (f)); f: close(); afirman (io.stdout: close()) ' – koniu

1

El enfoque de POSIX es establecer los descriptores de archivo con el indicador FD_CLOEXEC, usando fcntl (2). Cuando se establece, todos los subprocesos no heredarán los descriptores de archivos marcados con esa bandera.

Stock Lua no tiene la función fcntl pero es posible agregarlo con el módulo lua posix como se presentó en las respuestas anteriores. Para tomar su ejemplo, usted tendría que cambiar las aperturas tales como:

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

Tenga en cuenta que yo no encontrar la constante FD_CLOEXEC en la fuente luaposix lo que es posible que tenga que añadir a mano también.