how to kill a port's external process?

classic Classic list List threaded Threaded
8 messages Options
Reply | Threaded
Open this post in threaded view
|

how to kill a port's external process?

Garry Hodgson-3


i'm building a little gui wrapper around a command line audio player.
i've got an erlang process that calls open_port() to spawn the external
player, but it doesn't actually send/receive anything to it over stdio.

when i send the controlling process a stop message, it sends a { self(), close }
message to the port, then receives the {Port, closed} reply and exits.
but the external player doesn't stop playing.  if i kill it from another
window before sending the stop, the controlling process gets the proper
exit_status message, so i know they're connected.  i can even exit erlang
entirely, and it keeps playing until i kill it.

so, is there a clean way to kill this beast?  i can think of some kludgey
ways, like gathering the output of ps and constructing a unix command to
kill it.  but it seems like there ought to be a better way?  can someone
point me in the right direction?

thanks.


Garry Hodgson                   And when they offer
Senior Hacker                    golden apples
garry                are you sure you'll refuse?
Software Innovation Services    Heaven help the fool.
AT&T Labs


Reply | Threaded
Open this post in threaded view
|

how to kill a port's external process?

Chris Pressey
"Garrett G. Hodgson" wrote:
> i'm building a little gui wrapper around a command line audio player.
> i've got an erlang process that calls open_port() to spawn the external
> player, but it doesn't actually send/receive anything to it over stdio.

I don't think the port mechanism was ever intended to be used that way
:)

The Interoperability Tutorial says:

| 4.1 Erlang Program
|    First of al,l communication between Erlang and C must be
|    established by creating the port. The Erlang process which
|    creates a port is said to be the connected process of the port. All
|    communication to and from the port should go via the connected
|    process. If the connected process terminates, so will the port
|    (and the external program, if it is written correctly).
                                ^^^^^^^^^^^^^^^^^^^^^^^^^^

Further down on that page there is example C source:

/* port.c */

     typedef unsigned char byte;

     int main() {
       int fn, arg, res;
       byte buf[100];

       while (read_cmd(buf) > 0) {
         fn = buf[0];
         arg = buf[1];
         
         if (fn == 1) {
           res = foo(arg);
         } else if (buf[0] == 2) {
           res = bar(arg);
         }

         buf[0] = res;
         write_cmd(buf, 1);
       }
     }

So the port terminates when the read_cmd() function detects end-of-file
from the Erlang side of things (AFAICT.)

Do you have the source to the external program?  It might help to
rewrite the main body to listen to the Erlang ports mechanism, even if
there's no established interface.

> so, is there a clean way to kill this beast?  i can think of some kludgey
> ways, like gathering the output of ps and constructing a unix command to
> kill it.  but it seems like there ought to be a better way?  can someone
> point me in the right direction?

This goes beyond Erlang, but... killing a process based on the name
of a program is always dodgy at best, given that a program can change
what it shows up as in 'ps'.  A better way is to use DJB's daemontools,
which, IIRC, opens up one pipe per process, giving you a truly unique
handle to it.

> Garry Hodgson                   And when they offer
> Senior Hacker                    golden apples
> garry                are you sure you'll refuse?
> Software Innovation Services    Heaven help the fool.
> AT&T Labs

Kallisti :)

Chris

--
This sentence is false.


Reply | Threaded
Open this post in threaded view
|

how to kill a port's external process?

Per Hedeland-4
Chris Pressey <cpressey> wrote:
>
>"Garrett G. Hodgson" wrote:
>> i'm building a little gui wrapper around a command line audio player.
>> i've got an erlang process that calls open_port() to spawn the external
>> player, but it doesn't actually send/receive anything to it over stdio.
>
>I don't think the port mechanism was ever intended to be used that way
>:)

True.:-) Well, at least if you intended to control the external program
in *any* way - e.g. firing up an independent daemon or whatever with
os:cmd() (that uses the port mechanim of course) is a reasonable thing
to do.

>Do you have the source to the external program?  It might help to
>rewrite the main body to listen to the Erlang ports mechanism, even if
>there's no established interface.

Actually, it could be done with a "wrapper", perhaps even something as
simple as:

  #!/bin/sh
  program &
  pid=$!
  while read dummy; do :; done
  kill $pid
  exit 0

But of course one has to wonder what the Erlang-based GUI is for if it
can't communicate with the program...:-)

--Per Hedeland
per


Reply | Threaded
Open this post in threaded view
|

how to kill a port's external process?

Matthias Lang-2

 >"Garrett G. Hodgson" wrote:
 >> i'm building a little gui wrapper around a command line audio player.
 >> i've got an erlang process that calls open_port() to spawn the external
 >> player, but it doesn't actually send/receive anything to it over stdio.

The bit which surprised me was that sending a kill to a port started
with open_port({spawn, ...}, [...]) doesn't necessarily kill the unix
process. It merely closes the file file descriptors. This is a problem
when the external process gets stuck in a loop somewhere and thus
stops caring about stdin/stdout. It's surprising because the Erlang book
(9.4) says that the goal is for ports to look just like Erlang
processes from the erlang program's point of view.

I can think of three ways around this. No doubt there are more.

  1. A variant of Per's approach. Wrap the external program so that
     it reports its pid on startup. That way you can nuke it by
     using os:cmd("kill .."), or whatever the windows equivalent
     is. The wrapper could be something like

       #!
       echo "my pid is $$"
       exec $*

     You might then use it as
     
       1> erlang:open_port({spawn, "./wrap.sh sleep 5000"}, []).
       #Port<0.8>
       2> flush().
       Shell got {#Port<0.8>,{data,"my pid is 2097\n"}}
       ok
       3> os:cmd("kill -9 2097").

     It can get more complicated if the child forks.

  2. If you're using heart (or some variant), it may be better to
     report the process IDs to the heart process in some way, for
     instance via a socket.

  3. Hack erlang so that erlang:port_info/2 returns the port's
     unix process ID. I did this but abandoned it in favour of #2
     because it didn't solve the problem of processes left lying
     around if erlang was killed.

Matthias
     


Reply | Threaded
Open this post in threaded view
|

how to kill a port's external process?

Garry Hodgson-3
In reply to this post by Chris Pressey
Chris Pressey wrote:

> "Garrett G. Hodgson" wrote:
> > i'm building a little gui wrapper around a command line audio player.
> > i've got an erlang process that calls open_port() to spawn the external
> > player, but it doesn't actually send/receive anything to it over stdio.
>
> I don't think the port mechanism was ever intended to be used that way
> :)

yeah, well, i live to abuse language features.

...

> So the port terminates when the read_cmd() function detects end-of-file
> from the Erlang side of things (AFAICT.)
>
> Do you have the source to the external program?  It might help to
> rewrite the main body to listen to the Erlang ports mechanism, even if
> there's no established interface.

alas, i do not.

> This goes beyond Erlang, but... killing a process based on the name
> of a program is always dodgy at best, given that a program can change
> what it shows up as in 'ps'.

in this case, the external program does not.  i've gotten it working
using this hack, and it works just fine for my purposes.  i expect i'll
let it be.

thanks to all for the information and tips.


--
Garry Hodgson                   sometimes we ride on your horses
Senior Hacker                   sometimes we walk alone
Software Innovation Services    sometimes the songs that we hear
AT&T Labs                       are just songs of our own
garry


Reply | Threaded
Open this post in threaded view
|

how to kill a port's external process?

Luke Gorrie-3
Garry Hodgson <garry> writes:

> > This goes beyond Erlang, but... killing a process based on the name
> > of a program is always dodgy at best, given that a program can change
> > what it shows up as in 'ps'.
>
> in this case, the external program does not.  i've gotten it working
> using this hack, and it works just fine for my purposes.  i expect i'll
> let it be.

I think a really nice and manly way to kill external ports would be to
hack the "spawn driver" so that Erlang could make a driver ioctl to
send a signal to a spawned program.

I think that's actually pretty straight forward - with some help I've
added an ioctl function to the spawn driver which I use to close
Erlang's sending pipe to a spawned program without closing the reading
one. I've attached a patch which should be mostly reusable.

The file to hack is erts/emulator/sys/unix/sys.c

If you hack that then you can then use erlang:port_command/2 to send a
message to the driver (erlang sends an I/O list, driver gets it as a
char*) and it can arbitrarily interpret that, e.g as:

  <<?SIG_REQUEST:8/integer, Signal:32/integer>>

I for one would cheer if someone were to do this :-) with just a few
spawn driver ioctls we could make a unix command shell in erlang.
Then you could annoy both bash and tcsh people when they use your
computer ;-)

-------------- next part --------------
A non-text attachment was scrubbed...
Name: sys.patch
Type: text/x-patch
Size: 2693 bytes
Desc: spawn driver ioctl patch
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20010809/1fca7d33/attachment.bin>

Reply | Threaded
Open this post in threaded view
|

how to kill a port's external process?

Luke Gorrie-3
Luke Gorrie <luke> writes:

> The file to hack is erts/emulator/sys/unix/sys.c
>
> If you hack that then you can then use erlang:port_command/2 to send a
> message to the driver (erlang sends an I/O list, driver gets it as a
> char*) and it can arbitrarily interpret that, e.g as:

I lie: it's port_control.

-Luke



Reply | Threaded
Open this post in threaded view
|

how to kill a port's external process?

Luke Gorrie-3
In reply to this post by Luke Gorrie-3
Luke Gorrie <luke> writes:

> I think a really nice and manly way to kill external ports would be to
> hack the "spawn driver" so that Erlang could make a driver ioctl to
> send a signal to a spawned program.

I couldn't resist and hacked in support for doing kill(2) on port
programs. I don't know if the approach is unwise for some reason but
it seems to work nicely.

The interface is a "spawn_drv" module in the kernel application with
these functions:

%% close_output(Port) -> ok | {error, Reason}
%%
%% Close the pipe from Erlang to the port's stdin

%% send_signal(Port, Signal) -> ok | {error, Reason}
%% Signal = hup | kill | term
%%
%% Send a Unix signal to the port with kill(2)

I was lazy and just defined 3 signals, but it's easy to add more.

Example:

  1> P = erlang:open_port({spawn, "/bin/cat"}, [exit_status]).
  #Port<0.7>
  2> flush().
  ok
  3> spawn_drv:send_signal(P, kill).
  ok
  4> flush().
  Shell got {#Port<0.7>,{exit_status,137}}
  ok

Patch against R7B-3 attached, I hope I built it properly.

-------------- next part --------------
A non-text attachment was scrubbed...
Name: spawndrv.patch
Type: text/x-patch
Size: 5842 bytes
Desc: not available
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20010809/3dcda4aa/attachment.bin>