Skip to content

Commit b0a5aa5

Browse files
authored
Callback support for data transfer from child to parent process during parallel execution (#441)
Signed-off-by: Henry Cox <[email protected]>
1 parent ceae745 commit b0a5aa5

File tree

10 files changed

+400
-19
lines changed

10 files changed

+400
-19
lines changed

README

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
-------------------------------------------------
22
- README file for the LTP GCOV extension (LCOV) -
3-
- Last changes: 2024-12-25
3+
- Last changes: 2025-10-01
44
-------------------------------------------------
55

66
Description
@@ -405,12 +405,15 @@ LCOV features and capabilities fall into 7 major categories:
405405

406406
The user can supply callbacks which are used to:
407407

408-
i) interface with the revision control system
408+
i) interface with the revision control system - to determine source
409+
code differences between versions and to find the author
410+
and date of the most recent edits.
409411
Sample scripts:
410412
- Perforce: see 'p4diff', 'p4annotate.pm', 'p4annotate'
411413
- Git: see 'gitdiff', 'gitblame.pm', 'gitblame'
412-
ii) verify that source code versions are compatible, and
413-
Sample scripts: see 'get_signature', 'P4version.pm', 'getp4version',
414+
ii) verify that source code versions are compatible - e.g.,
415+
before aggregating coverage data or displaying source code.
416+
Sample scripts: 'get_signature', 'P4version.pm', 'getp4version',
414417
'gitversion', 'gitversion.pm', and 'batchGitVersion.pm'
415418
iii) enforce a desired code coverage criteria
416419
Sample script: criteria.pm/criteria
@@ -423,11 +426,15 @@ LCOV features and capabilities fall into 7 major categories:
423426
Sample script: select.pm
424427
vi) keep track of environment and other settings - to aid
425428
infrastructure debugging in more complicated use cases.
429+
Sample script: context.pm
426430
vii) compress the 'function detail' table to improve readability
427431
by shortening long C++ template and function names.
432+
Sample script: simplify.pm
428433

429434
The callback may be any desired script or executable - but there
430435
may be performance advantages if it is written as a Perl module.
436+
See the "Additional considerations" and "Callbacks and parallel
437+
execution" discussion in the genhtml man page.
431438

432439
See the genhtml/lcov/geninfo man pages for details.
433440

lib/lcovutil.pm

Lines changed: 90 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ our @EXPORT_OK = qw($tool_name $tool_dir $lcov_version $lcov_url $VERSION
6262
@exclude_file_patterns @include_file_patterns %excluded_files
6363
@omit_line_patterns @exclude_function_patterns $case_insensitive
6464
munge_file_patterns warn_file_patterns transform_pattern
65+
warn_pattern_list
6566
parse_cov_filters summarize_cov_filters
6667
disable_cov_filters reenable_cov_filters is_filter_enabled
6768
filterStringsAndComments simplifyCode balancedParens
@@ -219,6 +220,15 @@ our $opt_no_external;
219220
our @build_directory;
220221

221222
our @configured_callbacks;
223+
# list of callbacks which support save/restore
224+
our @callback_save_restore;
225+
# list of callbacks which support 'finalize'
226+
our @callback_finalize;
227+
# list of callbacks which implement 'start' - which gets called when
228+
# child process starts
229+
our @callback_start_list;
230+
# the callback data which is saved from child process/restored from child process
231+
our @callback_state;
222232

223233
# optional callback to keep track of whatever user decides is important
224234
our @contextCallback;
@@ -988,6 +998,26 @@ sub configure_callback
988998
#$package->import(qw(new));
989999
# the first value in @_ is the script name
9901000
$$cb = $class->new(@args);
1001+
if (exists($ENV{LCOV_FORCE_PARALLEL}) ||
1002+
(defined($lcovutil::maxParallelism) &&
1003+
1 != $lcovutil::maxParallelism)
1004+
) {
1005+
# don't set up for parallel processing if we aren't going to fork
1006+
if ($$cb->can('save')) {
1007+
if ($$cb->can('restore')) {
1008+
push(@callback_save_restore, [$class, $$cb]);
1009+
push(@callback_start_list, [$class, $$cb])
1010+
if ($$cb->can('start'));
1011+
} else {
1012+
lcovutil::ignorable_error($lcovutil::ERROR_PACKAGE,
1013+
"$class implements 'save' but not 'restore'.");
1014+
return;
1015+
}
1016+
}
1017+
}
1018+
# implement 'finalize', regardless of parallel/not parallel
1019+
push(@callback_finalize, [$class, $$cb])
1020+
if ($$cb->can('finalize'));
9911021
};
9921022
if ($@ ||
9931023
!defined($$cb)) {
@@ -1745,23 +1775,40 @@ sub munge_file_patterns
17451775
@suppress_function_patterns = map({ $_->[0] } @exclude_function_patterns);
17461776
}
17471777

1778+
sub warn_pattern_list
1779+
{
1780+
my ($type, $patterns) = @_;
1781+
foreach my $pat (@$patterns) {
1782+
my $count = $pat->[-1];
1783+
if (0 == $count) {
1784+
my $str = $pat->[-2];
1785+
lcovutil::ignorable_error($ERROR_UNUSED,
1786+
"'$type' pattern '$str' is unused.");
1787+
}
1788+
}
1789+
}
1790+
17481791
sub warn_file_patterns
17491792
{
1793+
# a bit of a hack...we need a place to call the 'finalize' methods
1794+
# (if any are registered) - and this method is called very late in
1795+
# the game, by lcov/genhtml/geninfo - so is a workable location
1796+
for (my $i = 0; $i <= $#lcovutil::callback_finalize; ++$i) {
1797+
my ($class, $cb) = @{$lcovutil::callback_finalize[$i]};
1798+
eval { $cb->finalize(); };
1799+
if ($@) {
1800+
lcovutil::ignorable_error($lcovutil::ERROR_CALLBACK,
1801+
"\"$class->finalize()\" failed: $@");
1802+
}
1803+
}
1804+
17501805
foreach my $p (['include', \@include_file_patterns],
17511806
['exclude', \@exclude_file_patterns],
17521807
['substitute', \@file_subst_patterns],
17531808
['omit-lines', \@omit_line_patterns],
17541809
['exclude-functions', \@exclude_function_patterns],
17551810
) {
1756-
my ($type, $patterns) = @$p;
1757-
foreach my $pat (@$patterns) {
1758-
my $count = $pat->[scalar(@$pat) - 1];
1759-
if (0 == $count) {
1760-
my $str = $pat->[scalar(@$pat) - 2];
1761-
lcovutil::ignorable_error($ERROR_UNUSED,
1762-
"'$type' pattern '$str' is unused.");
1763-
}
1764-
}
1811+
warn_pattern_list(@$p);
17651812
}
17661813
}
17671814

@@ -2081,14 +2128,24 @@ sub initial_state
20812128
}
20822129
}
20832130

2131+
for (my $i = 0; $i <= $#lcovutil::callback_start_list; ++$i) {
2132+
my ($class, $cb) = @{$lcovutil::callback_start_list[$i]};
2133+
eval { $cb->start(); };
2134+
if ($@) {
2135+
lcovutil::ignorable_error($lcovutil::ERROR_CALLBACK,
2136+
"\"$class->start()\" failed: $@");
2137+
}
2138+
}
2139+
20842140
return Storable::dclone([\@message_count, \%versionCache, \%resolveCache]);
20852141
}
20862142

20872143
sub compute_update
20882144
{
20892145
my $state = shift;
2090-
my @new_count;
20912146
my ($initialCount, $initialVersionCache, $initialResolveCache) = @$state;
2147+
2148+
my @new_count;
20922149
my $id = 0;
20932150
foreach my $count (@message_count) {
20942151
my $v = $count - $initialCount->[$id++];
@@ -2104,7 +2161,20 @@ sub compute_update
21042161
$resolveUpdate{$f} = $v
21052162
unless exists($initialResolveCache->{$f});
21062163
}
2107-
my @rtn = (\@new_count,
2164+
my @cbData;
2165+
for (my $i = 0; $i <= $#lcovutil::callback_save_restore; ++$i) {
2166+
my ($class, $cb) = @{$lcovutil::callback_save_restore[$i]};
2167+
eval {
2168+
my $data = $cb->save();
2169+
push(@cbData, $data);
2170+
};
2171+
if ($@) {
2172+
lcovutil::ignorable_error($lcovutil::ERROR_CALLBACK,
2173+
"\"$class->save(...)\" failed: $@");
2174+
}
2175+
}
2176+
my @rtn = (\@cbData,
2177+
\@new_count,
21082178
\%versionUpdate,
21092179
\%resolveUpdate,
21102180
\%message_types,
@@ -2131,6 +2201,15 @@ sub compute_update
21312201

21322202
sub update_state
21332203
{
2204+
my $callbackData = shift;
2205+
for (my $i = 0; $i <= $#$callbackData; ++$i) {
2206+
my ($class, $cb) = @{$lcovutil::callback_save_restore[$i]};
2207+
eval { $cb->restore($callbackData->[$i]); };
2208+
if ($@) {
2209+
lcovutil::ignorable_error($lcovutil::ERROR_CALLBACK,
2210+
"\"$class->restore(...)\" failed: $@");
2211+
}
2212+
}
21342213
my $updateCount = shift;
21352214
my $id = 0;
21362215
foreach my $count (@$updateCount) {

man/genhtml.1

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -631,7 +631,7 @@ and does not modify information in the coveage DB.
631631
.IP 2. 3
632632
The option may be specified as a single
633633
.I split_char
634-
separated string which is divied into words (see
634+
separated string which is divided into words (see
635635
.B man lcovrc(5)
636636
), or as a list of arguments.
637637
The resulting command line is passed
@@ -778,6 +778,75 @@ The callback will occur in the child process (possibly simultaneously with other
778778
As a result: if your callback needs to pass data back to the parent, you will need to arrange a communication mechanism to do so.
779779
.br
780780

781+
.SS Callbacks and parallel execution
782+
783+
Because callbacks may need to record data -
784+
.I e.g.,
785+
for error reporting or action summaries in the presence of parallel execution -
786+
.B genhtml (and
787+
.B lcov and
788+
.B geninfo
789+
) can call certain optional callback methods:
790+
791+
.IP \- 3
792+
.I $callback->start()
793+
.br
794+
is called when the child process begins execution. This method can be
795+
used to capture initial state -
796+
.I e.g.,
797+
to set the count of events in this child to zero.
798+
This method is optional.
799+
.PP
800+
801+
.IP \- 3
802+
.I my $data = $callback->save()
803+
.br
804+
is called when processing is complete, just before the child process exits.
805+
The scalar
806+
.I $data
807+
returned by your
808+
.I $callback->save()
809+
method
810+
.I $callback->restore()
811+
method when the child process is reaped.
812+
.PP
813+
814+
.IP \- 3
815+
.I $callback->restore($data)
816+
.br
817+
is called in the parent process when the child is reaped.
818+
.I $data
819+
is the data that was returned when your
820+
.I $callback->save()
821+
method was called in the child. (Serialization/deserialization has happened under the covers.)
822+
.PP
823+
824+
.IP \- 3
825+
.I $callback->finalize()
826+
.br
827+
is called in the parent process when all calculalations are complete
828+
and the parent setting up to report final results.
829+
This method is optional.
830+
.br
831+
Note that, unlike the other callback methods described in this section,
832+
.I finalize()
833+
is called in both parallel and serial execution contexts.
834+
.PP
835+
836+
Note that your callback must implement
837+
.I $callback->restore()
838+
if it implements
839+
.I $callback->save().
840+
.I $callback->start()
841+
and
842+
.I $callback->finalize()
843+
are optional: if they are implemented, then they will be called.
844+
845+
These methods are available only for callbacks implemented a perl modules.
846+
If you callback is implemented as an executable script (say) - then you
847+
are free to implement parent/child data passing however you prefer.
848+
849+
781850
.SS Additional considerations
782851

783852
If the

scripts/simplify.pm

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,23 +92,63 @@ EOF
9292

9393
# verify that the patterns are valid...
9494
lcovutil::verify_regexp_patterns($script, \@patterns);
95+
my @munged = map({ [$_, 0]; } @patterns);
9596

96-
return bless \@patterns, $class;
97+
return bless \@munged, $class;
9798
}
9899

99100
sub simplify
100101
{
101102
my ($self, $name) = @_;
102103

103104
foreach my $p (@$self) {
105+
my $orig = $name;
104106
# sadly, no support for pre-compiled patterns
105-
eval "\$name =~ $p ;"; # apply pattern that user provided...
107+
eval "\$name =~ $p->[0] ;"; # apply pattern that user provided...
106108
# $@ should never match: we already checked pattern validity during
107109
# initialization - above. Still: belt and braces.
108110
die("invalid 'simplify' regexp '$p->[0]': $@")
109111
if ($@);
112+
++$p->[1]
113+
if ($name ne $orig);
110114
}
111115
return $name;
112116
}
113117

118+
sub start
119+
{
120+
my $self = shift;
121+
foreach my $p (@$self) {
122+
$p->[1] = 0;
123+
}
124+
}
125+
126+
sub save
127+
{
128+
my $self = shift;
129+
my @data;
130+
foreach my $p (@$self) {
131+
push(@data, $p->[1]);
132+
}
133+
return \@data;
134+
}
135+
136+
sub restore
137+
{
138+
my ($self, $data) = @_;
139+
die("unexpected restore: (" .
140+
join(' ', @$self) . ") <- [" .
141+
join(' ', @$data) . "]\n")
142+
unless $#$self == $#$data;
143+
for (my $i = 0; $i <= $#$self; ++$i) {
144+
$self->[$i]->[-1] += $data->[$i];
145+
}
146+
}
147+
148+
sub finalize
149+
{
150+
my $self = shift;
151+
lcovutil::warn_pattern_list("simplify", $self);
152+
}
153+
114154
1;

tests/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ report:
2727
--version-script $(VERSION_SCRIPT) \
2828
--exclude genError.pm --exclude filter.pl \
2929
--exclude brokenCallback.pm --exclude MsgContext.pm \
30+
--exclude missingRestore.pm --exclude parallelFail.pm \
3031
--omit-lines 'ERROR_INTERNAL' --omit-lines '\bdie\b' \
3132
--ignore unsupported,unused,inconsistent \
3233
--filter region ; \

0 commit comments

Comments
 (0)