/
FINALIZER.pm6
170 lines (132 loc) · 5.5 KB
/
FINALIZER.pm6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
use v6.c;
class FINALIZER:ver<0.0.5>:auth<cpan:ELIZABETH> {
# The blocks that this finalizer needs to finalize
has @.blocks;
has $!lock;
# Make sure we have a lock for adding / removing from blocks
submethod TWEAK() { $!lock = Lock.new }
# The actual method calling the registered blocks for this finalizer
method FINALIZE(FINALIZER:D:) {
$!lock.protect: {
my @exceptions;
for @!blocks -> &code {
code();
CATCH { default { @exceptions.push($_) } }
}
dd @exceptions if @exceptions;
}
}
# Run code with a lock protecting changes to blocks
method !protect(FINALIZER:D: &code) { $!lock.protect: &code }
# Register a block for finalizing if there is a dynamic variable with
# a FINALIZER object in it.
method register(FINALIZER:U: &code --> Callable:D) {
with $*FINALIZER -> $finalizer {
$finalizer!protect: { $finalizer.blocks.push(&code) }
-> { $finalizer!unregister(&code) }
}
else {
-> --> Nil { }
}
}
# Unregister a finalizing block: done as a private object method to
# make access to blocks easier. Assumes we're already in protected
# mode wrt making changes to blocks.
method !unregister(FINALIZER:D: &code --> Nil) {
my $WHICH := &code.WHICH;
@!blocks.splice($_,1) with @!blocks.first( $WHICH eq *.WHICH, :k );
}
}
# Exporting for a client environment
multi sub EXPORT() {
# The magic incantation to export a LEAVE phaser to the scope where
# the -use- statement is placed, Zoffix++ for producing this hack!
$*W.add_phaser: $*LANG, 'LEAVE', { $*FINALIZER.FINALIZE }
# Make sure we export a dynamic variable as well, to serve as the
# check point for the finalizations that need to happen in this scope.
my %export;
%export.BIND-KEY('$*FINALIZER',my $*FINALIZER = FINALIZER.new);
%export
}
# Exporting for a module environment
multi sub EXPORT('class-only') { {} }
=begin pod
=head1 NAME
FINALIZER - dynamic finalizing for objects that need finalizing
=head1 SYNOPSIS
{
use FINALIZER; # enable finalizing for this scope
my $foo = Foo.new(...);
# do stuff with $foo
}
# $foo has been finalized by exiting the above scope
# different file / module
use FINALIZER <class-only>; # only get the FINALIZER class
class Foo {
has &!unregister;
submethod TWEAK() {
&!unregister = FINALIZER.register: { .finalize with self }
}
method finalize() {
&!unregister(); # make sure there's no registration anymore
# do whatever we need to finalize, e.g. close db connection
}
}
=head1 DESCRIPTION
FINALIZER allows one to register finalization of objects in the scope that
you want, rather than in the scope where objects were created (like one
would otherwise do with C<LEAVE> blocks or the C<is leave> trait).
=head1 AS A MODULE DEVELOPER
If you are a module developer, you need to use the FINALIZE module in your
code. In any logic that returns an object (typically the C<new> method) that
you want finalized at the moment the client decides, you register a code
block to be executed when the object should be finalized. Typically that
looks something like:
use FINALIZER <class-only>; # only get the FINALIZER class
class Foo {
has &!unregister;
submethod TWEAK() {
&!unregister = FINALIZER.register: { .finalize with self }
}
method finalize() {
&!unregister(); # make sure there's no registration anymore
# do whatever we need to finalize, e.g. close db connection
}
}
=head1 AS A PROGRAM DEVELOPER
Just use the module in the scope you want to have objects finalized for
when that scope is left. If you don't use the module at all, all objects
that have been registered for finalization, will be finalized when the
program exits. If you want to have finalization happen for some scope,
just add C<use FINALIZER> in that scope. This could e.g. be used inside
C<start> blocks, to make sure all registered resources of a job run in
another thread, are finalized:
await start {
use FINALIZE;
# open database handles, shared memory, whatever
my $foo = Foo.new(...);
} # all finalized after the job is finished
=head1 RELATION TO DESTROY METHOD
This module has B<no> direct connection with the C<.DESTROY> method
functionality in Perl 6. However, if you, as a module developer, use
this module, you do not need to supply a C<DESTROY> method as well, as
the finalization will have been done by the C<FINALIZER> module. And as
the finalizer code that you have registered, will keep the object otherwise
alive until the program exits.
It therefore makes sense to reset the variable in the code doing the
finalization. For instance, in the above class Foo:
method finalize(\SELF: --> Nil) {
# do stuff with SELF
SELF = Nil
}
The C<\SELF:> is a way to get the invocant without it being decontainerized.
This allows resetting the variable containing the object (by assigning C<Nil>
to it).
=head1 AUTHOR
Elizabeth Mattijsen <liz@wenzperl.nl>
Source can be located at: https://github.com/lizmat/FINALIZER . Comments and
Pull Requests are welcome.
=head1 COPYRIGHT AND LICENSE
Copyright 2018 Elizabeth Mattijsen
This library is free software; you can redistribute it and/or modify it under the Artistic License 2.0.
=end pod