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

Hello Fellow Monks

I wrote a CLI programm running on windows 7 (64 Bit). I would like to call some cleanup code whenever the programm is terminated through closing the window (someone clicks the [x] in the upper right corner). Ideally the cleanup code should be run for any abnormal termination for which a handler can be registered.

Perl Versions used for testing:
strawberry-perl-5.18.1.1-32bit-portable strawberry-perl-5.28.0.1-64bit-portable
The actual programm is compiled using pp. For my tests i called the perl script using portableshell.bat, and then closed the window using the [x] in the upper right corner. Until now i failed to find a way to trigger one of my handlers. I tried a number of things, and my google and perlmonks search skills have failed me so far. As such i come here, in the hope that someone might have already solved this problem. Example code for some failed attempts:
use strict; use warnings; use FindBin; use File::Spec; my $log_fn = File::Spec->catfile($FindBin::Bin, "cleanup_handler_log.t +xt"); my $LOG; open $LOG, '>>', $log_fn or die "Failed to open log '$log_fn' for appe +nding ($!)\n"; binmode $LOG; $LOG->autoflush(1); # disable buffering # Intent: register a cleanup handler, which is called when the process + is terminated # because someone closed the window using [x] in Windows. # # Random examples of what does not work: sub END { print $LOG "END() called. Cleaning up...\n"; } sub DESTROY { print $LOG "DESTROY() called. Cleaning up...\n"; } foreach my $s (qw/INT TERM HUP QUIT ABRT KILL CHLD STOP/) { $SIG{$s} = sub { print $LOG "SIG $s received! Cleaning up ...\n"; exit 0; }; } my $sleep_time = 60 * 60 * 24; # 1d print "Sleeping $sleep_time seconds ...\n"; sleep $sleep_time; print "Print finishing normally (sleep $sleep_time completed)\n";
After the programm is terminated through closing the window, the log is still empty.

Replies are listed 'Best First'.
Re: Window Close / Process Termination Handler on Windows
by Marshall (Canon) on Aug 29, 2018 at 18:23 UTC
    This is sort of a weird idea and not a direct solution to your question and there are some issues to be dealt with however, in theory this kludge would work...

    Since the problem occurs when closing the console window by the "x", make it impossible for the user to do that!
    I am going partly from memory on this one, but if you start a Tk gui as a detached process (no console window), it is easy to intercept hitting the "x" on the gui window. There are a couple of ways: see Re: Catching WINDOW close event and take actions and from my memory any code right after Mainloop; executes after the "x" is clicked.

    I had one GUI app where I implemented some code after Mainloop; to make sure all of the files were saved if the user clicked "x" instead of using the gui to "save and exit" - it is possible to restart the Mainloop. You don't need any fancy buttons...a simple text display widget would be enough. It is possible to have a routine run right after the Mainloop starts (no user "click" required), but I don't remember the exact way to do that.

    On a different tack, why are the user's hitting the console "x" button in the first place?
    In my experience that is because they think that the program is hung and because of that, closing the window won't matter.

    See if you can just print a "dot", "." every so many work units. Do not have a separate timing routine that prints a dot every 3 seconds or whatever. The users will figure out that that is BS. Make each dot represent some unit of actual work performed. This typically will lead to a jerky timing of dots and the users trust that more than some kind of regularly timed dots. Maximum user patience is somewhat south of 90 seconds.

    It is also possible that your program shouldn't even have a constantly displayed UI! In Unix lingo this is a daemon, in Windows lingo this is service. If your command line program is just a monitor for this continuously running process, closing it would have no effect upon the underlying process.

      Hello Marshall,

      Replacing the console window with a gui window would indeed solve the problem. Another solution would be to convert it into a daemon and writing a gui client to display the status messages.

      The reason why i am not doing any of this is, that the program itself is an interim kludge for a closed source CTI (computer telephony interface) program which is used in a hotline. The hotline software fails to display basic things which one would expect of such a software, such as who is logged in, what their status is, what the call statistics are (SLA), and so on. The kludge which i quickly whipped up a few year ago, is a simple tcp proxy reading the communication between the client and the server presenting the information above through printing it to the console whenever there is any change. There is no user interaction, it just displays the infos, and it updates frequently. The users do not get the impression of it being hung.

      Then why did i want to add a handler for program termination? When a user is done, they will close the windows. So far so good. If they close however the tcp proxy before the client programm, then the logoff in the client programm will never reach the server, because the tcp proxy is down. With the given cti server software the hotline agent will still be active and receive call, thus causing failed call counts to go up. Reason being that an explicit logoff is required to be removed from the hotline rotation. My intent was to inject the logoff message into the tcp stream whenever the simple tcp proxy is closed, thus eliminating the problem. But for that i would have needed a working "close" or "destroy" handler.

      Since this software is supposed to be retired soon, there is no point in investing time into a major rewrite. I would have just added the logoff, if there would have been an easy way to call it upon window destruction. If i started converting that quick and dirty kludge to a gui application, then i could immediately write a new GUI client for the CTI software, since i know enough about the protocol to do so. This would also eliminate the need for a tcp proxy. Considering however that it is a soon to be retired application with a small user base, i can not justify putting that much time into it.
        Thanks for your reply.
        I see that you understood my suggestions and I see why they aren't appropriate for this specific situation.

        I saw a post suggesting using the Window "START" command in a .bat file. There might be some way to use that to solve your problem. I don't know at the moment.

        However now that the Monks know more about what you are doing, there is an increased likelihood of a simple solution, perhaps not a general solution, but a solution that will solve your specific problem.

Re: Window Close / Process Termination Handler on Windows
by stevieb (Canon) on Aug 29, 2018 at 14:26 UTC

    I don't have time currently to deeply troubleshoot (especially why END isn't working for you), but one sure-fire way to ensure DESTROY is called is wrap it with a dummy package, and then generate an unused object in your code. DESTROY is called when an object goes out of scope (in our case, the program finishes). This is quick and dirty, but it does do what I think you're after. Better would be to possibly rewrite the whole shebang to be Object Oriented.

    use strict; use warnings; use FindBin; use File::Spec; my $log_fn = File::Spec->catfile($FindBin::Bin, "cleanup_handler_log.t +xt"); open my $LOG, '>>', $log_fn or die "Failed to open log '$log_fn' for a +ppending ($!)\n"; binmode $LOG; $LOG->autoflush(1); # disable buffering package Blah;{ sub new { return bless {}, shift; } sub DESTROY { print $LOG "DESTROY() called. Cleaning up...\r\n"; } } my $obj = Blah->new; my $sleep_time = 1; # 1d print "Sleeping $sleep_time seconds ...\n"; sleep $sleep_time; print "Print finishing normally (sleep $sleep_time completed)\n";

      I think I'm able to follow you:

      $ ./1.term.pl Sleeping 1 seconds ... Print finishing normally (sleep 1 completed) [1]+ Done gedit 1.term.pl $ ./1.term.pl Sleeping 1 seconds ... Print finishing normally (sleep 1 completed) $ ./1.term.pl Sleeping 3 seconds ... ^C $ cat cleanup_handler_log.txt DESTROY() called. Cleaning up... DESTROY() called. Cleaning up...

      Destroy gets called when the sleep amount expires. I raised the sleep number to 3 and hit it with control-c and did not get the destroy called. Why is that? Finally, I ran it in the terminal and x'ed out of it while it was running and did see the destroy message:

      $ cat cleanup_handler_log.txt DESTROY() called. Cleaning up... DESTROY() called. Cleaning up... DESTROY() called. Cleaning up... $

      Script I was running:

      $ cat 1.term.pl #!/usr/bin/perl -w use 5.011; use FindBin; use File::Spec; my $log_fn = File::Spec->catfile($FindBin::Bin, "cleanup_handler_log.t +xt"); open my $LOG, '>>', $log_fn or die "Failed to open log '$log_fn' for a +ppending ($!)\n"; binmode $LOG; $LOG->autoflush(1); # disable buffering package Blah;{ sub new { return bless {}, shift; } sub DESTROY { print $LOG "DESTROY() called. Cleaning up...\r\n"; } } my $obj = Blah->new; my $sleep_time = 3; print "Sleeping $sleep_time seconds ...\n"; sleep $sleep_time; print "Print finishing normally (sleep $sleep_time completed)\n"; $
      Unfortunately, when closing the window with the [x] in the upper right corner, the DESTROY handler from your example is not being called in the environment listed above.
Re: Window Close / Process Termination Handler on Windows
by TheloniusMonk (Sexton) on Aug 29, 2018 at 13:35 UTC
    As Murphy would have it, you missed BREAK from the list, which IIRC is what Windows sends on close window.

    Update: and apart from also INT, QUIT, and TERM, you can forget about windows sending the other types.

      Hello TheloniusMonk,

      Thanks for your feedback. Unfortunately adding BREAK to my list of signals still did not cause my code to trigger. The log is still empty after i close the window.

      Updated Code which still does not trigger:
      # partially based upon code by BrowserUK from https://www.perlmonks.or +g/?node_id=629210 use strict; use Config; use FindBin; use File::Spec; my $log_fn = File::Spec->catfile($FindBin::Bin, "cleanup_handler_log.t +xt"); my $LOG; open $LOG, '>>', $log_fn or die "Failed to open log '$log_fn' for appe +nding ($!)\n"; binmode $LOG; $LOG->autoflush(1); # disable buffering my @sigs = split ' ', $Config{ sig_name }; shift @sigs; print "SIGNALS: @sigs\n"; for ( @sigs ) { my $msg = "Signal $_ received\n"; $SIG{ $_ } = sub{ print $LOG $msg; warn $msg; }; } my $count; while( 1 ) { $count += 1 for 1.. 1e6; warn $count; sleep 3; }
      The List of signals for which a signal handler is registered:
      SIGNALS: HUP INT QUIT ILL NUM05 NUM06 NUM07 FPE KILL NUM10 SEGV NUM12 +PIPE ALRM TERM NUM16 NUM17 NUM18 NUM19 CHLD BREAK ABRT STOP NUM24 CONT CLD
        In that case, the parent process is not signalling your process and it dies when the parent dies. So you need to write a subroutine in C that sets a handler,
        BOOL WINAPI SetConsoleCtrlHandler( _In_opt_ PHANDLER_ROUTINE HandlerRoutine, _In_ BOOL Add );
        and another that is called back. The handler routine can then issue a signal of its own for your perl program to trap. It's a bit of a maze but these are the set of functions you can choose from to manage the windows console: https://docs.microsoft.com/en-us/windows/console/console-functions

        Update: If you can live with uninterruptable, you could also consider a DOS wrapper that does "START /B wrapper2.bat", where wrapper2.bat runs the perl program. This should then be detached from the console window.

Re: Window Close / Process Termination Handler on Windows
by beech (Parson) on Sep 03, 2018 at 03:44 UTC