¿Forzar a Perl a llamar a las subrutinas END cuando termina con exec ()?

Cuando se utiliza exec () en Perl:

Tenga en cuenta que exec will not call your END blocks, nor will it invoke DESTROY métodos en sus objetos.

Como fuerzo perl llamar END blocks anyway? Can I do something like END(); exec($0) ¿o lo que sea?

I really am trying to make the program end its current instance and start a brand new instance of itself, and am too lazy to do this correctly (using cron or putting the entire program in an infinite loop). However, my END subroutines cleanup temp files and other important things, so I need them to run between executions.

Unhelpful links to code:

https://github.com/barrycarter/bcapps/blob/master/bc-metar-db.pl

https://github.com/barrycarter/bcapps/blob/master/bc-voronoi-temperature.pl

https://github.com/barrycarter/bcapps/blob/master/bc-delaunay-temperature.pl

preguntado el 31 de enero de 12 a las 16:01

4 Respuestas

So you're trying to execute a program within your script? exec probably isn't what you want then. exec behaves like the C exec: what gets called reemplaza your current process; to keep going, you'd have to do something like a fork to preserve your current process while executing another.

But good news! That all exists in the system incorporado.

Does exactly the same thing as exec LIST , except that a fork is done first and the parent process waits for the child process to exit.

Así es como se ve:

use 5.012; # or use 5.012 or newer
use warnings;

... # some part of my program

system($my_command, $arg1, $arg2); # forks, execs, returns.

END {
    # still gets called because you never left the script.
}

If you absolutely must use an exec, you must call your cleanup routine automatically. To understand more about END, Consulte perldoc perlmod for full details. The short of it: END is one of several types of blocks of code that gets execucted at a particular stage in the execution of the script. They are NOT subroutines. However, you puede execute any code you want in those subroutines. So you can do:

sub cleanup { ... } # your cleanup code

sub do_exec {
    cleanup();
    exec( ... );
}

END {
    cleanup();
}

and then you know your cleanup code will be executed at either script exit OR when you do your exec.

Respondido el 31 de enero de 12 a las 21:01

No, I really am trying to do "exec($0)" [call a new instance of the program from itself, replacing current instance], and it's working fine, except that it doesn't cleanup its /tmp files (I have a /tmp file cleaner in the END subroutines), so I have to purge these manually. - user354134

Si esto is why the END Block isn't being called: the END block doesn't exist any more. If you want to replace your current process with a new instance of the same script, that's fine; just know that there's no more code to execute. There is no way to get back to that END block. It's gone. Consider instead, then, to put your cleanup code somewhere else; a function, perhaps, that you call manually before...which it sounds like you're doing. - Robert P

That's what I'd LIKE to do, but can't figure out how. How do I tell Perl "run your END subroutines now"? The "END(); exec($0)" was just psuedo-code. - user354134

You can't direct Perl to run its END subroutines whenever you want...but you CAN put subroutine calls in your END blocks, and then call that subroutine. EG: sub cleanup { ... }; sub do_exec { cleanup(); exec(...); } END { cleanup() } - Robert P

You can direct Perl to run its END subroutines whenever you want: $_->object_2svref->() for B::end_av->ARRAY - turba

To answer the narrow question of how to invoke your END blocks at arbitrary times, you can use the B::end_av método con B::SV::object_2svref to get the code references to your END Bloques

sub invoke_end_blocks_before_exec {
    use B;
    my @ENDS = B::end_av->ARRAY;
    foreach my $END (@ENDS) {
        $END->object_2svref->();
    }
}

END { print "END BLOCK 1\n" }
END { print "END BLOCK 2\n" }

...

invoke_end_blocks_before_exec();
exec("echo leave this program and never come back");

Salida:

END BLOCK 2
END BLOCK 1
leave this program and never come back

I would usually prefer something less magical, though. Why not a structure like

sub cleanup { ... }
END { &cleanup }

if (need_to_exec()) {
    cleanup();        # same thing END was going to do anyway
    exec( ... );
}

?

Respondido el 31 de enero de 12 a las 21:01

I actually considered your second solution before posting this, but my END blocks are in a library. If I change the library and add more END blocks, I want them to execute too, without having to edit the original program. Will try your first suggestion, however! - user354134

@barrycarter, This will call all END blocks, including those in libraries. (It won't call destructors, though.) - Ikegami

OK, of all the answers, this one was the easiest to implement. Thanks, mob! - user354134

Fork and exec

It'll leave you with a new pid, but you could do a fork/exec:

my $pid = fork();
defined $pid or die "fork failed";
exit if $pid; # parent immediately exits, calling END blocks.
exec($0) or die "exec failed"; # child immediately execs, will not call END blocks (but parent did, so OK)

This strikes me as far less fragile than mucking with internals or trying to make sure your exec is in the final END bloquear.

Wrap your program

Además, es trivial to just wrap your Perl program in a shell (or Perl) script that looks something like this:

#!/bin/sh

while sleep 5m; do
    perl your-program.pl
done

or

#!/usr/bin/perl

while (1) {
    system("perl your-program.pl");
    sleep(5*60);
}

Respondido el 31 de enero de 12 a las 22:01

I hadn't considered the 2nd approach, that looks brilliant! It's sort of like cron, but avoids running 2 instances of the same program at the same time. Is there a version of 'cron' that does this directly? (run something once every n minutes, but not if that thing is already running-- I've tried using lockfiles and things got horribly messy). Also, is adding an extra process for every such Perl program a good idea? - user354134

@barrycarter: There are a lot of programs that will do it. Quick apt-cache search restart finds daemontools, launchtool, restartd, supervisor (maybe), duende. An extra process will consume some memory. Starting perl consumes some CPU and IO. For a few tens of "monitor" processes, it won't matter on any modern machine. For thousands, it will. You could also write your monitor process very lightweight in C. In addition, restarting exited processes is something init does, check your system's init (or upstart, etc.) docs. - derobert

Can you put your call to exec in at the end of the (final) END block? Where your current call to exec is, set a flag, then exit. Al final de END block, check the flag, and if it's true, call exec there. This way, you can exit your script without restarting, if necessary, and still have the END blocks execute.

That said, I'd recommend not implementing this type of process-level tail recursion.

Respondido el 31 de enero de 12 a las 21:01

Wouldn't this require the exec() END block to run last? Can I control the order in which END blocks are run? - user354134

It would have to be the last END block to execute. According to man perlmod, END blocks are executed in reverse order of definition, so the exec statement would have to appear in the primeras END block in your script. - chepner

And I should point out that I haven't considered when END blocks that appear in imported modules get executed: I'm afraid I simply don't know and haven't checked. Keep that in mind if you try this approach. - chepner

Con Manip::END you can add additional code to the END blocks at runtime and guarantee their execution order (... unless other libraries are also manipulating the END blocks at runtime). - turba

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.