Re: is there a way to ensure some code is the last thing that is run?
by kcott (Archbishop) on Feb 03, 2017 at 02:23 UTC
|
G'day morgon,
END blocks are the last thing to be run.
They are run in reverse order.
From "perlmod: BEGIN, UNITCHECK, CHECK, INIT and END:
"An END code block is executed as late as possible, that is, after perl has finished running the program and just before the interpreter is being exited, even if it is exiting as a result of a die() function. ... You may have multiple END blocks within a file--they will execute in reverse order of definition; ..."
Would writing your code like the following, achieve what you want?
#!/usr/bin/env perl
use strict;
use warnings;
END {
# "the very last thing a script does"
}
END {
# Call destructors and other cleanup here
}
# ... rest of script here ...
If not, can you supply example code showing whatever problem you're experiencing.
It may be that you need to trap signals or reorganise your code in some way:
but I'm very much groping around in guesswork-land here.
| [reply] [d/l] [select] |
Re: is there a way to ensure some code is the last thing that is run?
by LanX (Saint) on Feb 03, 2017 at 01:57 UTC
|
Might be easier if you provide an example which runs after END {} ?
Anyway the trivial answer is, destroy
all data before by yourself. ;)
| [reply] [d/l] |
|
|
package Blubb;
+
sub new {
my $this = bless {};
$this->{circular} = $this;
}
sub DESTROY {
print "DESTROY\n";
}
package MAIN;
my $b = Blubb->new;
END {
print "END\n";
}
produces:
END
DESTROY
Now assume you have some code that someone else has written and where destructors run as above during gobal destruction and one of them (you are too lazy to figure out which) is doing something nasty.
Would it be (as a hack of course) then possible to graft something on to ensure that the nasty thing is undone as the last thing that happens?
My understanding is that during global destruction all bets are off and so I guess it is not possible, but then what do I know... | [reply] [d/l] [select] |
|
|
package Blubb;
sub new {
my $this = bless {};
$this->{circular} = $this;
}
sub DESTROY {
print "DESTROY\n";
}
package MAIN;
use POSIX ();
my $b = Blubb->new;
POSIX::_exit(0);
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.
In the absence of evidence, opinion is indistinguishable from prejudice.
| [reply] [d/l] |
|
|
|
|
Hi morgon,
Now assume you have some code that someone else has written and where destructors run as above during gobal destruction and one of them (you are too lazy to figure out which) is doing something nasty.
Would it be (as a hack of course) then possible to graft something on to ensure that the nasty thing is undone as the last thing that happens?
From my somewhat limited experience with it, fiddling with stuff during destruction is fraught with peril. I would argue that if someone else's destructor is doing something so complex/nasty in its DESTROY method that you have a need to undo what is being done there, it's that code that is to blame and that needs to be addressed.
What is this nasty thing you're trying to undo?
Regards, -- Hauke D
| [reply] [d/l] |
|
|
Destruction happens at the end of scope not file.
Try to put curlies around the code excluding the END.
In other words make sure the variables are in another scope than the END block.
UPDATE
Tested, doesn't work with your code!
BUT the problem is your circular reference.
It means that a normal destruction can't happen because the ref-count keeps up.
We are talking here about the garbage collection at the very end of a Perl script..
UPDATE
(on exit Perl 5 does a mark and sweep to reclaim circular references.)
| [reply] |
|
|
|
|
|
|
|
Or something that wouldn't be considered buggy:
package Logger;
our $default_logger = __PACKAGE__->new();
sub new {
my $this = bless {};
}
sub DESTROY {
print "DESTROY\n";
}
package MAIN;
END {
print "END\n";
}
or
package Logger;
my $default_logger = __PACKAGE__->new();
sub new {
my $this = bless {};
}
sub log {
$default_logger->foo();
}
sub DESTROY {
print "DESTROY\n";
}
package MAIN;
END {
print "END\n";
}
In both cases, a package variable ($Logger::default_logger in the first snippet, &Logger::log in the second snippet) keep the object alive until global destruction (where package variables are destroyed).
| [reply] [d/l] [select] |
|
|
use strict;
use warnings;
package Blubb;
sub new {
my $this = bless {a=>42};
$this->{circular} = $this;
}
sub DESTROY {
warn "DESTROY";
}
package MAIN;
my $b = Blubb->new;
{
no warnings 'redefine';
my $orig= \&Blubb::DESTROY;
*Blubb::DESTROY = sub {
$orig->(@_);
warn "END";
};
}
DESTROY at c:/tmp/pm/destruct_end.pl line 12 during global destruction
+.
END at c:/tmp/pm/destruct_end.pl line 27 during global destruction.
| [reply] [d/l] [select] |
|
|
|
|
|
|
|
|
Re: is there a way to ensure some code is the last thing that is run?
by haukex (Archbishop) on Feb 03, 2017 at 10:50 UTC
|
Hi morgon,
Based on your example code, would it be correct to assume that the problem is happening because of circular references? If so, then tracking those down might be a good start, it should lead you to the culprit code.
I looked into tracking circular references, unfortunately it seems that Devel::LeakTrace and Devel::LeakTrace::Fast don't compile on modern versions of Perl/glib, Devel::Leak seems a bit too low-level, and Devel::Cycle requires you to specify which variables to check for cycles, which can be difficult if you have lots of objects being created. So far, the "best" thing I have found seems to be Devel::Leak::Object, which can override bless.
Also, I fiddled around with Devel::Gladiator and has_circular_ref from Data::Structure::Util, which seems to support more types of data than Devel::Cycle. Note that it appears to be important to limit the inspection to those things that we're actually interested in, in this case objects - just blindly using it on everything that walk_arena() returns leads to errors, and I even got some segfaults at one point. But the following seems to work, at least on your example code:
use Devel::Gladiator qw/walk_arena/;
use Data::Structure::Util qw/has_circular_ref/;
use Scalar::Util qw/blessed/;
sub find_all_cycles {
my $all = walk_arena();
for my $sv (@$all) {
# limit search to objects
next unless blessed($sv);
warn "Has circular references: $sv\n"
if has_circular_ref($sv);
}
@$all=();
}
END { find_all_cycles() }
Hope this helps, -- Hauke D | [reply] [d/l] [select] |
|
|
Interesting stuff you bring up.
But just in order not to be misunderstood:
I provided the example just to illustrate how destructors can run after END-blocks.
In this particular case the problem is of couse the circular reference (that I put there simply to force this behaviour).
My question is not how to fix this particular case but if there was a way to ensure something is run at the very end, in particular when using some unknown code that you do not want to fiddle with.
| [reply] |
|
|
Hi morgon,
My question is not how to fix this particular case but if there was a way to ensure something is run at the very end, in particular when using some unknown code that you do not want to fiddle with.
Ah, I see - in that case it was still an interesting exercise. If you don't have circular references, then I believe END blocks are the answer, whereas if you do have circular references, that's what I might attack first. (Update: See Re^6: is there a way to ensure some code is the last thing that is run?)
So, do I understand correctly you don't have a specific case where you're having problems, and this was a theoretical exercise?
Regards, -- Hauke D
| [reply] [d/l] |
|
|
Morgon, interesting discussion you started.
I think the best you can do is to focus on "seen first, run last" and "end of scope".
Using those 2 points, I think the following is the closest to what you are asking for.
#!perl
# Make this END block the first thing compiled to insure it
# will be the last END block to run.
END {
# any special clean up goes here
warn 'END - Bypassing final global Perl clean up';
# $? is the value passed to exit() unless modified
# in another END block.
POSIX::_exit($?);
# WARNING - Any DESTRUCTions that could not be
# completed before _exit() is called will NOT
# be completed
}
# Create an extra layer of scope
{
use strict;
use warnings;
use Some::Module;
my $s = Some::Module->new();
} # Close extra scope to trigger most of the DESTRUCTion
exit(0);
# Here, the various END blocks will run in reverse order
# of being compiled, concluding with the END block at the
# top of this file.
Disclaimer: Not tested. YMMV. | [reply] [d/l] |