3 ms·
Driving student programs
- kd5bjo 9y agoIf you're not filtering or monitoring stdout and stderr, there's no need to proxy them via pipes. Instead, you can let the child process inherit them and it will send its output directly to the shell without a round-trip through the parent's userspace.
- tekknolagi 9y agoNow that's something I hadn't considered. Can you point me to some documentation?
- kd5bjo 9y agoUnfortunately, I don't know of a good write-up of how the kernel actually deals with file descriptors, aside from the man pages for the various syscalls you're using. From the kernel's perspective, there's nothing special about file descriptors 0, 1, and 2. The shell did the same pipe/fork/dup2/exec procedure as your code when it launched your program, and hooked the other end of the pipes to the terminal. When you call fork(), both processes keep all of their open file descriptors, which is why you have to close the ones you're not going to use with either close() or dup2(). If stdout or stderr is already attached to the correct destination when fork() is called, it's perfectly acceptable to leave them alone. Read-mode file descriptors (like stdin) are more problematic if they're used concurrently by multiple processes. Only one of the processes will read any given piece of data that was written into the pipe, so you need to coordinate the reads between the processes somehow.
- kd5bjo 9y agoActually, after looking at it again, I believe your code is at risk of deadlocking. If the program under test produces enough output to fill the stdout or stderr pipe buffer, the next write will block until your program reads some data out of it. That can only happen once you've finished writing everything to the stdin pipe, which may be full because the program under test has stopped reading from it (due to blocking on its write call). To avoid this, you need to process all three file descriptors in one loop using something like select() or poll() to work with whichever descriptor can make progress. Or, let a completely separate process (like the shell) handle the downstream file descriptors for you.
- repsilat 9y agoSounds like a job for bash... timeout 1 program < in > student_out diff student_out real_out Or something? Edit: I don't know how standard `timeout` is, but I guess you could also do something like program < in | tee student_out | sleep 1
- tekknolagi 9y agoThe problem is that they can't handle normal piping well and we don't want the extraneous output in our logs & tests.
- repsilat 9y agoHmm, I don't quite understand, sorry. As far as I can tell, the code in TFA takes the pipe output up to one second and then kills the student code if it hasn't exited by itself. That's what my examples do too. (Happy to be corrected on either point.) Another solution: give students some simple test cases and the test environment.
- kd5bjo 9y agoThe article's code leaves the input stream open, which causes reads to block forever instead of exiting with an end-of-file marker. As the student projects are for an introductory course, making them handle that particular detail would only be a distraction from the desired learning outcomes.
- tekknolagi 9y agoPrecisely!
- orf 9y agoSeems simple enough in Python: p = subprocess.Popen(stdin=PIPE, stdout=PIPE) stdout, stderr = p.communicate(timeout=5) p.kill() A lot easier to read to boot.