Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?
 
PerlMonks  

Re^2: Killing a child process

by Anonymous Monk
on Oct 03, 2017 at 08:13 UTC ( [id://1200590]=note: print w/replies, xml ) Need Help??


in reply to Re: Killing a child process
in thread Killing a child process

I checked the SoX documentation for stuff like that, but I need to stop the sound playback 'on demand'; I don't know in advance when the playback will be stopped.

Replies are listed 'Best First'.
Re^3: Killing a child process
by haukex (Archbishop) on Oct 03, 2017 at 08:30 UTC

    Ah, I see. I like offloading things like this to modules, in this case I think IPC::Run would be appropriate, the following works for me. If you think the child might misbehave and not terminate on SIGINT, see the IPC::Run doc section "Timeouts and Timers" on how to time out the kill operation.

    use IPC::Run qw/start/; my $h = start ['play','-q','/home/myname/soundfile.wav']; sleep 5; $h->signal('INT'); $h->finish;

      I wonder if there's any way to check that the sound file is still playing?

      I checked $h{STATE}, but its value only changes from 2 to 3 once we apply the $h->signal('INT')

      I also considered piping the output into a text file, and checking it periodically, since SoX displays a nice 'Done' when the sound file finishes playing. However, that won't help us if the end user specifies an audio package other than SoX.

        I wonder if there's any way to check that the sound file is still playing?

        I would guess that the sound not playing anymore would normally be closely associated with the process terminating. There are probably exceptions, e.g. if the audio data is buffered or something, but personally I would probably first go with the aforementioned assumption, and only investigate further if problems arise (or if this application needs to be ported to a lot of different machines). So as far as I can tell, ->finish calls waitpid under the hood, which means that it should return when the process ends, and hopefully when audio file is done playing. The following test seems to confirm it - at least on my machine the sound stops playing right when the process terminates:

        use IPC::Run qw/start/; use Time::HiRes qw/gettimeofday tv_interval/; my $t0 = [gettimeofday]; my $h = start ['play','-q','/home/myname/soundfile.wav', 'trim','0','5']; printf " start: %.03f s\n", tv_interval($t0); $h->finish; printf "finish: %.03f s\n", tv_interval($t0); __END__ start: 0.001 s finish: 5.143 s

        Update: Other than waitpid for children, a generic way to see if another process is reachable is kill 'ZERO', $pid:

        If SIGNAL is either the number 0 or the string ZERO (or SIGZERO), no signal is sent to the process, but kill checks whether it's possible to send a signal to it (that means, to be brief, that the process is owned by the same user, or we are the super-user). This is useful to check that a child process is still alive (even if only as a zombie) and hasn't changed its UID. See perlport for notes on the portability of this construct.

      (This is a reply to Re^5)

      Your suggestions have provided the right answer! Here are two scripts; the first hangs until the sound file stops playing, and then displays a confirmation message.

      #!/usr/bin/perl use strict; use diagnostics; use warnings; use IPC::Run qw/start/; use POSIX ":sys_wait_h"; my ($h, $pid, $result); # Play the sound file $h = start ['play','-q','/home/ra/Desktop/trek3.mp3']; # Get the process ID (there's only one) foreach my $kid (@{ $h->{KIDS} }) { $pid = $kid->{PID}; } # Wait for the sound file to stop playing, and then display a message # Option 1: scripts hangs, and then correctly displays a message # as soon as the sound file stops playing sleep 5; waitpid($pid, 0); print "finished!\n"; # Tidy up $h->signal('INT'); $h->finish;

      The second checks once a second, and prints a confirmation message within a second of the sound file stopping.

      #!/usr/bin/perl use strict; use diagnostics; use warnings; use IPC::Run qw/start/; use POSIX ":sys_wait_h"; my ($h, $pid, $result); # Play the sound file $h = start ['play','-q','/home/ra/Desktop/trek3.mp3']; # Get the process ID (there's only one) foreach my $kid (@{ $h->{KIDS} }) { $pid = $kid->{PID}; } # Wait for the sound file to stop playing, and then display a message # Option 2: correctly displays a message within a second of the sound # file stopping playing do { sleep 1; waitpid($pid, WNOHANG); print "waiting...\n"; } until (! (kill 0, $pid)); print "finished!\n"; # Tidy up $h->signal('INT'); $h->finish;
        Your suggestions have provided the right answer!

        I'm glad to help, but unfortunately I'm also confused by your post. Maybe it's because I haven't fully understood all of your requirements. So far, I get that you want to play a sound to the user, but also want to be able to stop the playback at any time, is that right? In that case, I'm not sure how the "Option 1" script helps, and how it's different from the code I showed? As for the "Option 2" script, I understand you want to monitor the state of the subprocess - that's an understandable requirement, although there are often better solutions than polling, but that depends on other things like if this is a GUI app or something else, and how playing a sound fits into the "bigger picture" of your application's purpose and design? If I've missed something, please explain. <update> In my previous node, I admit I did not consider that you may want to poll the state instead of just block until the subprocess is done, but again, that's just another good reason to explain all of the requirements and the context :-) </update>

        I'm sorry but I have to say I don't agree with the way you're going about using the module. First, you're reaching into the IPC::Run object's undocumented internals. They may change without notice across different versions of the module, breaking your code. Also, your assumptions, like that the "KIDS" array only has one element, might not always be true. Second, I don't see how the sleep 5; helps in the first script. Third, I said that ->finish calls waitpid under the hood, but now you're using both. The same goes for $h->signal('INT') - you're signaling a process that at that point should already be gone. Both are unnecessary, and the reason I mention this is because it makes me suspect you may have misunderstood something in their use, or even the control of subprocesses in general (perhaps a close read of perlipc is a good idea).

        Modules like IPC::Run wrap the underlying system calls (like waitpid) in a "nicer" interface so that you don't have to use the lower-level calls. Sure, sometimes (rarely!) the modules don't support everything you need, and hacking their internals becomes necessary. But I strongly recommend looking at the API the module provides before looking into its internals!

        For your "Option 1" (block until the subprocess is done), the "right" way to use the module is the code I showed (just remove the Time::HiRes stuff). For your "Option 2" (poll the subprocess state), study the IPC::Run documentation and you will find the pumpable method:

        use IPC::Run qw/start/; my $h = start ['play','-q','/tmp/sound.wav']; while ($h->pumpable) { print "waiting...\n"; sleep 1; } $h->finish; print "finished!\n";

        See how much nicer that is, without having to muck about in the object's internals? Here, I've put that together with a signal handler that will catch SIGINT (usually Ctrl-C) and cleanly signal and terminate the subprocess:

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://1200590]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others making s'mores by the fire in the courtyard of the Monastery: (7)
As of 2024-03-28 13:55 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found