http://qs321.pair.com?node_id=932273

hennesse has asked for the wisdom of the Perl Monks concerning the following question:

Hi, I'm trying to port a complex application from FreeBSD to Win32, and am having all kinds of problems with starting and stopping processes. Before getting into specific questions, let me describe the program. Perhaps someone has some general guidelines or ideas for starting and stopping this monster.
SUPERVISOR.PL ------------- print PIDFILE "$$\r\n"; $SIG{QUIT} = \%SHUTDOWN; fork() if child, exec ctrlman.pl fork() if child, exec pidman.pl fork() if child, exec progman.pl fork() if child, exec webman.pl while (1) { do stuff } exit; sub SHUTDOWN { kill 3, CTRLMAN_pid # pid saved from the fork kill 3, PIDMAN_pid kill 3, PROGMAN_pid kill 3, WEBMAN_pid }

Supervisor and his four children then talk to each other through DGRAM sockets. One big happy family.

Each programX has its own SHUTDOWN - SIG{QUIT} handler - which does an orderly shutdown, closing sockets and things before exiting.

In freeBSD:
I start with csh> perl supervisor.pl
I stop with csh> kill -3 `cat PIDFILE`
The supervisor propagates the QUIT to the other processes, and everybody shuts down nicely.

19:28:39:982 SUPERVISOR: Shutdown started^M 19:28:39:983 SUPERVISOR: Killing Ctrlman Pidman Progman Webman^M 19:28:39:983 SUPERVISOR: Waiting for processes to die^M 19:28:39:984 CTRLMAN: Shutdown started^M 19:28:39:985 CTRLMAN: Shutdown complete^M 19:28:39:990 PIDMAN: Shutdown started^M 19:28:39:991 PIDMAN: Shutdown complete^M 19:28:39:994 PROGMAN: Shutdown started^M 19:28:39:995 PROGMAN: Shutdown complete^M 19:28:39:997 WEBMAN: Shutdown started^M 19:28:39:998 WEBMAN: Shutdown complete^M 19:28:40:001 supervisor: reaped 17752 exit 0 signal 0 dump 0^M 19:28:40:002 SUPERVISOR: Shutdown complete^M

Everything works so nicely in Unix...

NOT so in Win32.
Windows doesn't really have signals or signal handlers, so the whole signal idea pretty much goes out the window (pun intended), although PERL does implement some limited signal handling capabilities.

If anyone has some general suggestions for managing startup and shutdown of such a system, I'd be glad to hear them.

I've got a ton of questions, so point me in the right direction now, and save yourself a ton of questions later.

Thanks,
Dave

Replies are listed 'Best First'.
Re: Win32: Starting and stopping processes (part 1)
by BrowserUk (Patriarch) on Oct 19, 2011 at 02:15 UTC

    Ostensibly, this is effectively equivalent and should work fine -- though I'm less sure about "DGRAM sockets" -- on windows:

    #! perl -slw use strict; my @pids; $SIG{'INT'} = sub { kill 'INT', @pids; }; push @pids, system 1, $_ for qw[ ctrlman.pl pidman.pl progman.pl webman.pl ]; while( 1 ) { ## do stuff; }

    That said, I'd almost certainly use threads & queues rather than processes and sockets.


    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".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Win32: Starting and stopping processes (part 1)
by bojinlund (Monsignor) on Oct 19, 2011 at 08:08 UTC

    In Win32 you should avoid using kill on forked processes.

    From http://perl5.git.perl.org/perl.git/blob_plain/HEAD:/pod/perlfork.pod:

    perlfork - Perl's fork() emulation

    ...

    The fork() emulation is implemented at the level of the Perl interpreter. What this means in general is that running fork() will actually clone the running interpreter and all its state, and run the cloned interpreter in a separate thread, beginning execution in the new thread just after the point where the fork() was called in the parent. We will refer to the thread that implements this child "process" as the pseudo-process.

    ...

    =item kill()

    C<kill('KILL', ...)> can be used to terminate a pseudo-process by passing it the ID returned by fork(). The outcome of kill on a pseudo-process is unpredictable and it should not be used except under dire circumstances, because the operating system may not guarantee integrity of the process resources when a running thread is terminated. The process which implements the pseudo-processes can be blocked and the Perl interpreter hangs. Note that using C<kill('KILL', ...)> on a pseudo-process() may typically cause memory leaks, because the thread that implements the pseudo-process does not get a chance to clean up its resources.

    As the terminated thread runs inside the same process as the controlling thread, it is always possible that the controlling thread will get damaged by this action.

    See also:

Re: Win32: Starting and stopping processes (part 1)
by hennesse (Beadle) on Oct 21, 2011 at 01:15 UTC

    Thanks guys.

    I took BrowserUK's suggestion to convert to threads instead of separate processes. It works well with a minimum of effort. supervisor.pl reads from 4 different sockets (the worker bees), so the Queues wouldn't work nearly as efficiently as the select call on the UDP sockets does.

    I created a "system tray" application with Win32::GUI, and Win32::Daemon to create a windows service. The service presents a little icon in the system tray which, when clicked, opens a little window that has start and stop buttons. "Start" opens supervisor.pl and the four dwarves (now "require" subroutines) as new threads in the server process. "Stop" shuts them down in an orderly fashion.

    "Stop" was still a problem, but was easily solved. Since supervisor and the dwarves send datagram messages back and forth constantly, I just created a new "message type" of "goodbye". Supervisor tells each dwarf "goodbye", and the dwarf shuts itself down.

    To get the service "starter.pl" to tell supervisor to shut down, I had to create a new datagram socket on supervisor to listen for a "goodbye" message from starter. Easily done. This eliminates the Unix signals method.

    Here's the code I used for "starter.pl". It's still preliminary, but it works, and it's almost starting to look like a professional application! Wow, you know, this Windows stuff might catch on one of these days...

    #!/usr/bin/perl use threads; use strict; use Socket; use Fcntl; use Errno; use Time::HiRes qw ( time alarm sleep ); use Brewery; use PID; use SSR; my $thr1; my $thr2; my $thr3; my $thr4; my $thr5; use Win32::Daemon; # Tell the OS to start processing the service... Win32::Daemon::StartService(); # Wait until the service manager is ready for us to continue... while( SERVICE_START_PENDING != Win32::Daemon::State() ) { sleep( 1 ); } # Now let the service manager know that we are running... Win32::Daemon::State( SERVICE_RUNNING ); chdir("c:/Documents and Settings/Owner/Desktop/WWW/brewery/threads"); require "c:/Documents and Settings/Owner/Desktop/WWW/brewery/threads/s +upervisor.pl"; require "c:/Documents and Settings/Owner/Desktop/WWW/brewery/threads/c +trlman.pl"; require "c:/Documents and Settings/Owner/Desktop/WWW/brewery/threads/p +idman.pl"; require "c:/Documents and Settings/Owner/Desktop/WWW/brewery/threads/p +rogman.pl"; require "c:/Documents and Settings/Owner/Desktop/WWW/brewery/threads/w +ebman.pl"; use Win32::GUI(); # my $DOS = Win32::GUI::GetPerlWindow(); # Win32::GUI::Hide($DOS); my $main = Win32::GUI::Window->new( -name => 'Main', -text => 'Perl', -width => 220, -height => 100, -text => 'The Electronic Brewery' ); my $start_button = $main->AddButton( -name => 'Start', -text => 'Start', -pos => [ 60, 30 ] ); my $stop_button = $main->AddButton( -name => 'Stop', -text => 'Stop', -visible => 0, -pos => [ 100, 30 ] ); my $theLabel = $main->AddLabel( -text => 'The Brewery is: STOPPED', -pos => [40,0] ); my $icon = new Win32::GUI::Icon('c:/Documents and Settings/Owner/D +esktop/WWW/brewery/favicon.ico'); my $ni = $main->AddNotifyIcon( -name => "NI", -icon => $icon, -tip => "The Electronic Brewery" ); Win32::GUI::Dialog(); sub Main_Terminate { $main->Disable(); $main->Hide(); return 0; } sub Main_Minimize { $main->Disable(); $main->Hide(); return 1; } sub NI_Click { $main->Enable(); $main->Show(); return 1; } sub Start_Click { $theLabel->Text('The Brewery is RUNNING'); $start_button->Hide(); $stop_button->Show(); $thr1 = threads->create(\&supervisor); $thr2 = threads->create(\&ctrlman); $thr3 = threads->create(\&pidman); $thr4 = threads->create(\&progman); $thr5 = threads->create(\&webman); return 0; } sub Stop_Click { $theLabel->Text('The Brewery is STOPPED'); $start_button->Show(); $stop_button->Hide(); my $iaddr = inet_aton("127.0.0.1"); my $proto = getprotobyname("udp"); my $port = 0; my $paddr = sockaddr_in( $port, $iaddr ); socket( SUPERVISOR, PF_INET, SOCK_DGRAM, $proto ) || die "sock +et: $!"; bind( SUPERVISOR, $paddr ) || die "bind: $!"; $port = 5000; my $dest = sockaddr_in($port, $iaddr); send(SUPERVISOR, "goodbye", 0, $dest); return 0; }