2015-07-07 12 views
13

semplice esempio, che non funziona sulla mia piattaforma (Ruby 2.2, Cygwin):Ruby - fork, exec, staccare .... abbiamo una condizione di gara qui?

#!/usr/bin/ruby 
backtt = fork { exec('mintty','/usr/bin/zsh','-i') } 
Process.detach(backtt) 
exit 

Questo piccolo programma (quando è partito dalla shell) dovrebbe estendersi su una finestra di terminale (mintty) e poi prendermi torna al prompt della shell.

Tuttavia, sebbene crei la finestra di mintty, non ho un prompt di shell in seguito, e non posso digitare nulla nella shell di chiamata.

Ma quando introduco un piccolo ritardo prima che il distacco, sia utilizzando 'sonno', oppure stampando qualcosa su stdout, esso funziona come previsto:

#!/usr/bin/ruby 
backtt = fork { exec('mintty','/usr/bin/zsh','-i') } 
sleep 1 
Process.detach(backtt) 
exit 

Perché è necessario?

proposito, sono ben consapevole che ho potuto (dal guscio) fare una

mintty /usr/bin/zsh -i & 

direttamente, o potrebbe usare sistema (...... &) dall'interno rubino, ma questo non è il punto qui. Sono particolarmente interessato al comportamento di fork/exec/detach in Ruby. Qualche intuizione?

+2

Sebbene nessuno specialista per Ruby, e non conoscendo affatto Cygwin, questa situazione mi sembra molto familiare (proveniente da c (++)): Questo script è troppo breve :-) - cosa succede se metti il dormire dopo distaccarsi e prima di uscire? – halfbit

risposta

0

Secondo il documentation:

Process::detach impedisce questo creando un filo rubino separato cui unico compito è di raccogliere lo stato del processo pid quando termina.

NB: non posso riprodurre questo comportamento in qualsiasi a mia disposizione i sistemi operativi, e sto postando questo come una risposta solo per il gusto di formattazione.

Dal Process.detach(backtt) crea in modo trasparente un filo, vorrei suggerire di provare:

#!/usr/bin/ruby 
backtt = fork { exec('mintty','/usr/bin/zsh','-i') } 
#      ⇓⇓⇓⇓⇓ 
Process.detach(backtt).join 
exit 

Questo non è un trucco con qualsiasi mezzo (come opposto a sciocco sleep,) in quanto è probabile che consapevoli di che il comando sottostante dovrebbe tornare più o meno immediatamente. Non sono un guru in cygwin, ma potrebbe avere alcuni problemi specifici con i thread, quindi lascia che questo thread sia gestito.

+0

Non riesco a utilizzare 'join' qui, perché il comando sottostante ** non ** restituisce immediatamente, ma deve essere eseguito per un lungo periodo di tempo - ore o giorni - come si può vedere dall'opzione' -i' che viene passato a 'zsh' (che significa" apri una shell interattiva "). In realtà, non penso che abbia senso usare mintty per un programma che dovrebbe essere non interattivo, vero? – user1934428

+1

Infatti. Si prega di ignorare quanto sopra, io per ragioni sconosciute ho deciso che 'mintty' staccherà il processo sottostante stesso. – mudasobwa

6

distacco come una risposta, perché è troppo lungo per un commento

Anche se io non sono un esperto in Ruby, e non so Cygwin a tutti, questa situazione suona molto familiare per me, venendo da C/C++.

Questo script è troppo breve, quindi il genitore del genitore completa, mentre il nipote tenta di avviarsi.

Cosa succederebbe se metteste il sonno dopo la rimozione e prima dell'uscita?

Se la mia teoria è corretta, dovrebbe funzionare anche. Il tuo programma si chiude prima che avvenga un qualsiasi (o abbastanza) passaggio di thread.

Io chiamo tali problemi "scuotimento della mano interrotto". Sebbene questa sia la terminologia psicologica, descrive cosa succede.

sonno "dà la porzione di tempo", che porta a filo-switching,

uscita della console (qualsiasi file di I/O) si imbatte in semafori, anche portando a filo di commutazione.

Se la mia idea è corretta, dovrebbe funzionare anche, se non "sonno", basta contare fino a 1E9 (a seconda della velocità di calcolo), perché il multitasking poi preemptive rende ancora il filo-interruttore non si dà la CPU .

Quindi è un errore nella programmazione (IMHO: la condizione di razza è filosofica in quel caso), ma sarà difficile trovare "chi" è responsabile. Ci sono molte cose coinvolte.

+0

Infatti, mettere il sonno dopo il distacco funziona! Molte grazie! Naturalmente preferirei un meccanismo più affidabile per combattere una condizione di competizione, ma almeno questo sembra essere pratico. – user1934428

+0

Oops, mi spiace dirlo, ma la tua soluzione NON ha aiutato. Ho fatto un errore durante la modifica e non ho commentato il primo sonno. Se lo faccio esattamente come hai detto tu, e dormi invece ** dopo ** il distacco, non funziona. – user1934428

+4

quindi la mia ipotesi non è corretta, era solo un'idea, perché sembrava familiare, ecco perché volevo solo commentare, non rispondere, - non ho idea di come rimuovere il '50' – halfbit

0

Non sono né un Ruby né un ragazzo Cygwin, quindi quello che propongo qui potrebbe non funzionare affatto. Ad ogni modo: immagino, non stai nemmeno colpendo un bug specifico di Ruby o Cygwin qui. In un programma chiamato "start" che ho scritto in C molti anni fa, ho riscontrato lo stesso problema. Ecco un commento dall'inizio della daemonize_now void function():

/* 
* This is a little bit trickier than I expected: If we simply call 
* setsid(), it may fail! We have to fork() and exit(), and let our 
* child call setsid(). 
* 
* Now the problem: If we fork() and exit() immediatelly, our child 
* will be killed before it ever had been run. So we need to sleep a 
* little bit. Now the question: How long? I don't know an answer. So 
* let us being killed by our child :-) 
*/ 

Così, egli strategia è questa: Lasciate che il genitore di attendere sul suo bambino (che può essere fatto immediatamente prima che il bambino effettivamente avuto la possibilità di fare qualsiasi cosa) e poi lasciare che il bambino faccia la parte distaccante. Come? Lascia che crei un nuovo gruppo di processi (sarà riparato per il processo di init). Questa è la richiesta di setsid(), di cui sto parlando nel commento. Si lavorerà qualcosa di simile (C-sintassi, si dovrebbe essere in grado di ricercare l'uso corretto per Ruby e applicare le modifiche necessarie da soli):

parentspid = getpid(); 
Fork = fork(); 
if (Fork) { 
    if (Fork == -1) { // fork() failed 
     handle error 
    } else { // parent, Fork is the pid of the child 
     int tmp; waitpid(0, &tmp, 0); 
    } 
} else { // child 
    if (setsid() == -1) { 
     handle error - possibly by doing nothing 
     and just let the parent wait ... 
    } else { 
     kill(parentspid, SIGUSR1); 
    } 
    exec(...); 
} 

È possibile utilizzare qualsiasi segnale, che termina il processo (cioè SIGKILL). Ho usato SIGUSR1 e installato un gestore di segnale che ha terminato (0) il processo genitore, quindi il chiamante ottiene un messaggio di successo. Unica avvertenza: ottieni successo anche se l'exec fallisce. Tuttavia, questo è un problema che non può essere veramente aggirato, dal momento che dopo un exec di successo non puoi più segnalare il tuo genitore. E dal momento che non sai quando l'exec avrà fallito (se fallisce), tornerai alla parte relativa alle condizioni di gara.

Problemi correlati