Wykrywanie, gdy proces potomny oczekuje na dane wejściowe

Piszę program w języku Python, aby uruchamiać przesłane przez użytkownika dowolne (a więc, w najgorszym przypadku, niebezpieczne, błędne i zawieszające się) kody na serwerze Linux. Pomijając pytania dotyczące bezpieczeństwa, moim celem jest określenie, czy kod (który może być w jakimkolwiek języku, skompilowany lub zinterpretowany) zapisuje poprawne rzeczy dostdout, stderr oraz inne pliki na danym wejściu wprowadzone do programustdin. Następnie muszę wyświetlić wyniki użytkownikowi.

Aktualne rozwiązanie

Obecnie moim rozwiązaniem jest zrobienie procesu potomnego za pomocąsubprocess.Popen(...) z uchwytami do plików dlastdout, stderr istdin. Plik zastdin uchwyt zawiera dane wejściowe, które program odczytuje podczas działania i po zakończeniu programustdout istderr pliki są odczytywane i sprawdzane pod kątem poprawności.

Problem

Podejście to działa doskonale w inny sposób, ale gdy wyświetlam wyniki, nie mogę połączyć danych wejściowych i wyjściowych, aby wejścia pojawiły się w tych samych miejscach, w jakich byłyby podczas uruchamiania programu z terminala. To znaczy. dla takiego programu

print "Hello."
name = raw_input("Type your name: ")
print "Nice to meet you, %s!" % (name)

zawartość pliku zawierającego programstdout po biegu byłby:

Hello.
Type your name: 
Nice to meet you, Anonymous!

biorąc pod uwagę, że zawartość pliku zawierastdin byliAnonymous<LF>. Krótko mówiąc, dla danego przykładowego kodu (i, równoważnie, dlakażdy inny kod) Chcę osiągnąć wynik taki jak:

Hello.
Type your name: Anonymous
Nice to meet you, Anonymous!

Problem polega na wykryciu, kiedy program oczekuje na dane wejściowe.

Wypróbowane metody

Wypróbowałem następujące metody rozwiązywania problemu:

Popen.communicate (...)

Pozwala to procesowi nadrzędnemu na oddzielne wysyłanie danych wzdłużrura, ale można go wywołać tylko raz, a zatem nie nadaje się do programów z wieloma wyjściami i wejściami - tak jak można wywnioskować z dokumentacji.

Bezpośredni odczyt zPopen.stdout iPopen.stderr i piszę doPopen.stdin

Dokumentacja ostrzega przed tym iPopen.stdouts .read() i.readline() wywołania wydają się blokować w nieskończoność, gdy programy zaczynają czekać na wejście.

Za pomocąselect.select(...) aby sprawdzić, czy uchwyty plików są gotowe do wejścia / wyjścia

To chyba nic nie poprawia. Najwyraźniej rury są zawsze gotowe do odczytu lub zapisu, więcselect.select(...) niewiele tu pomaga.

Używanie innego wątku do odczytu bez blokowania

Jak sugerowano wta odpowiedź, Próbowałem stworzyć osobneWątek() który przechowuje wyniki z odczytu zstdout w aKolejka(). Linie wyjściowe przed linią wymagającą danych wejściowych użytkownika są wyświetlane ładnie, ale linia, na której program zaczyna czekać na dane wprowadzone przez użytkownika ("Type your name: " w powyższym przykładzie nigdy się nie czyta.

UżywaćPTY slave jako plik procesu potomnego obsługuje

Zgodnie z zaleceniamitutaj, Próbowałempty.openpty() aby utworzyć pseudo terminal z deskryptorami plików master i slave. Następnie podałem deskryptor pliku slave jako argument dlasubprocess.Popen(...) dzwonistdout, stderr istdin parametry. Odczytywanie deskryptora pliku głównego otwartego za pomocąos.fdopen(...) daje taki sam wynik, jak użycie innego wątku: wejście wymagające linii nie jest odczytywane.

Edytować: Korzystanie z przykładu @Antti Haapalapty.fork() do tworzenia procesu potomnego zamiastsubprocess.Popen(...) wydaje mi się, że pozwala mi także odczytać dane wyjściowe utworzone przezraw_input(...).

Za pomocąpexpect

Próbowałem takżeread(), read_nonblocking() ireadline() metody (udokumentowanetutaj) procesu zrodzonego z pexpect, ale najlepszy wynik, który otrzymałemread_nonblocking(), jest taki sam jak poprzednio: linia z wyjściami, zanim użytkownik chce wprowadzić coś, nie jest czytana. jest taki sam jak w przypadku PTY utworzonego za pomocąpty.fork(): wejście wymagające liniirobi czytać.

Edytować: Używającsys.stdout.write(...) isys.stdout.flush() zamiastprintw moimmistrz program, który tworzy dziecko, wydawał się naprawiać wiersz zachęty, który nie był wyświetlany - w rzeczywistości jednak został odczytany w obu przypadkach.

Inni

Próbowałem teżselect.poll(...), ale wydawało się, że deskryptory pliku głównego rury lub PTY są zawsze gotowe do pisania.

UwagiInne rozwiązaniaPomyślałem, że spróbuję zasilić wejście, gdy minie trochę czasu bez wygenerowania nowego wyjścia. Jest to jednak ryzykowne, ponieważ nie ma możliwości sprawdzenia, czy program jest w trakcie wykonywania ciężkich obliczeń.Jak w swojej odpowiedzi wspomniał @Antti Haapala,read() systemowy wrapper z glibc może zostać zastąpiony, aby przekazać dane wejściowe do programu głównego. Nie działa to jednak w przypadku programów połączonych statycznie lub programów montażowych. (Chociaż teraz, gdy o tym myślę, wszystkie takie połączenia mogłyby zostać przechwycone z kodu źródłowego i zastąpione poprawioną wersjąread() - może być żmudne w dalszym ciągu.)Modyfikowanie kodu jądra Linuksa w celu komunikowania sięread() wywołania systemowe do programu są prawdopodobnie szalone ...PTYs

Myślę, że PTY jest dobrym rozwiązaniem, ponieważ fałszuje terminal, a interaktywne programy są uruchamiane wszędzie na terminalach. Pytanie brzmi, jak?

questionAnswers(2)

yourAnswerToTheQuestion