Thanks. This looks like exactly what I'm looking for, though I've got to do some man page reading to fully understand this. I've never used any of the thread modules before. So this is a good opportunity to explore them.
| [reply] |
## Start the command connecting our output to its input
## (You might need to use open2/3() if you want to capture the output)
my $pid = open CMD, '|-', $cmd or die $!;
## A shared var that captures whether the print succeeds
## If it does, the process went into an input state
## if it doesn't it ended (or was terminated) without entering an inpu
+t state
my $inInputState :shared = 0;
## Attempt to write to the process
## in a thread so we can do other things while it blocks.
## No newline so the process doesn't see it
## a series of spaces followed by backspaces
## which should be "cancelled out" by the line edit API
## 4096 chars to ensure it gets through pipe buffering
async {
$inInputState = 1 if printf CMD " \b"x2048;
}->detach;
## A microsleep to ensure responsiveness whilst avoiding cpu burn
## until the process self terminates
## or we reach the timeout period
## or the print succeeds -- entered input state
Win32::Sleep 10 until !kill 0, $pid
or $timedOut = time() > $timeout
or $inInputState
The rest is just mechanics.
With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
| [reply] [d/l] |
#!/usr/bin/perl -slw
use strict;
our $N //= 12;
our $I //= 0;
my $cmd = qq[exec $^X -E'sleep $N; \$_=<STDIN> if $I; sleep 2; printf
+"Kid done (read %d bytes)\\n", length'];
my $inInputState = 1;
my $timedOut = 0;
$SIG{PIPE} = sub { $inInputState = 0; };
$SIG{ALRM} = sub { $inInputState = 0; $timedOut = 1; die };
$SIG{CHLD} = 'IGNORE';
my $pid = open CMD, '|-', $cmd or die $!;
alarm 10;
eval { syswrite CMD, " \b"x(2**15+1) };
alarm 0;
if( $timedOut ) {
print "Command timed out";
kill 15, $pid;
}
if( $inInputState ) {
print "Child waiting for input";
}
else {
print "Kid never entered input state";
}
print 'Parent done';
__END__
$ ./detectChildInputState -I=0 -N=2
Kid done (read 0 bytes)
Kid never entered input state
Parent done
$ ./detectChildInputState -I=1 -N=2
Child waiting for input
Parent done
$ Kid done (read 65538 bytes)
$ ./detectChildInputState -I=1 -N=12
Command timed out
Kid never entered input state
Parent done
$ ./detectChildInputState -I=1 -N=9
Child waiting for input
Parent done
$ Kid done (read 65538 bytes)
$ ./detectChildInputState -I=0 -N=9
Command timed out
Kid never entered input state
Parent done
The main difference revolves around the SIGPIPE signal which on Unix would
be delivered to a process if it attempts to write to a broken pipe
(this is the case when the child terminates before having read anything).
By default, this signal would terminate the writing process, so it
would have to be handled one way or another, anyway (e.g. $SIG{PIPE} = 'IGNORE').
OTOH, we can take advantage of this error notification, in which case
we don't need an extra thread (or process) doing the blocking write.
The logic is kind of reversed now: we assume things went ok, unless we
know otherwise, which is when
- the child terminated before it went into an input state
(in which case we get a SIGPIPE)
- a timeout occurred before the child went into an input state
in those cases, $inInputState is set to zero in the respective signal handler.
A couple of more notes:
- the timeout is implemented via the usual alarm mechanism
(instead of status polling in a loop)
- I'm using syswrite to circumvent Perl's own buffering without
having to fiddle with autoflush
- Unix pipes typically use a rather large buffer (64k in my case),
so the chunk written needs to be significantly larger than on Windows,
in order to get the write operation to block
- the backspace cancellation trick would only work under rare
circumstances (AFAICT) — simply reading from stdin would, for
example, not treat the backspaces in any special way.
- last but not least, as a consequnce of the above, the tested
program should be able to handle 64k of junk in case it puts up an
innocent prompt (whether this is in fact an issue, of course depends on
the type of program...)
| [reply] [d/l] [select] |
a "unixish" implementation of BrowserUk's idea
Nice!++
the backspace cancellation trick would only work under rare circumstances (AFAICT) — simply reading from stdin would, for example, not treat the backspaces in any special way.
I added a little extra diagnostics. a) When the parent detects that the child entered an input state, it sends it a string "hello\n"; b) when the child enters an input state, it prints out what it receives: #! perl -slw
use strict;
use threads;
use threads::shared;
our $N //= 12;
our $I //= 0;
my $cmd = qq[$^X -E"\$|++; sleep $N; print 'Kid got ', scalar <STDIN>
+if $I; sleep 2;say 'Kid done'"];
my $timeout = time() + 10;
my $inInputState :shared = 0;
my $pid = open CMD, '|-', $cmd or die $!;
my $old = select CMD; $|++; select $old;
my $timedOut = 0;
async {
$inInputState = 1 if printf CMD " \b"x2048;
}->detach;
Win32::Sleep 10 until !kill 0, $pid
or $timedOut = time() > $timeout
or $inInputState
;
if( $timedOut ) {
print "Command timed out";
kill 3, $pid;
}
if( $inInputState ) {
print "Child waiting for input";
print CMD 'hello';
}
else {
print "Kid never entered input state";
}
print 'Parent done';
The upshot shows that on Windows at least, even though the child (perl in this case) only uses a standard read from stdin scalar <STDIN>, the CRT provides line-editing that allows the backspace cancellation to work:
C:\test>detectChildInputState -I=0 -N=2
Kid done
Kid never entered input state
Parent done
C:\test>detectChildInputState -I=1 -N=2
Child waiting for input
Parent done
Kid got hello
Kid done
C:\test>detectChildInputState -I=0 -N=10
Command timed out
Kid never entered input state
Parent done
C:\test>detectChildInputState -I=1 -N=10
Child waiting for input
Parent done
Kid got hello
Kid done
C:\test>detectChildInputState -I=0 -N=12
Command timed out
Kid never entered input state
Parent done
C:\test>detectChildInputState -I=1 -N=12
Command timed out
Kid never entered input state
Parent done
But I guess what you are saying is that under *nix, the standard CRT input routines don't provided line editing facilities, unless the program in question uses a readline(3) library or similar.
Would nulls be an workable alternative? Or DC1/DC3 (XON/XOFF)?
With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
| [reply] [d/l] [select] |