summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'Bugzilla/Template/PreloadProvider.pm')
-rw-r--r--Bugzilla/Template/PreloadProvider.pm140
1 files changed, 140 insertions, 0 deletions
diff --git a/Bugzilla/Template/PreloadProvider.pm b/Bugzilla/Template/PreloadProvider.pm
new file mode 100644
index 000000000..d62bed07c
--- /dev/null
+++ b/Bugzilla/Template/PreloadProvider.pm
@@ -0,0 +1,140 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+# This exists to implement the template-before_process hook.
+package Bugzilla::Template::PreloadProvider;
+
+use 5.10.1;
+use strict;
+use warnings;
+
+use base qw(Template::Provider);
+
+use File::Find ();
+use Cwd ();
+use File::Spec;
+use Template::Constants qw( STATUS_ERROR );
+use Template::Document;
+use Template::Config;
+
+use Bugzilla::Util qw(trick_taint);
+
+sub _init {
+ my $self = shift;
+ $self->SUPER::_init(@_);
+
+ my $path = $self->{INCLUDE_PATH};
+ my $cache = $self->{_BZ_CACHE} = {};
+ my $search = $self->{_BZ_SEARCH} = {};
+
+ foreach my $template_dir (@$path) {
+ $template_dir = Cwd::realpath($template_dir);
+ my $wanted = sub {
+ my ($name, $dir) = ($File::Find::name, $File::Find::dir);
+ if ($name =~ /\.tmpl$/) {
+ my $key = $name;
+ $key =~ s/^\Q$template_dir\///;
+ unless ($search->{$key}) {
+ $search->{$key} = $name;
+ }
+ trick_taint($name);
+ my $data = {
+ name => $key,
+ text => do {
+ open my $fh, '<:utf8', $name or die "cannot open $name";
+ local $/ = undef;
+ scalar <$fh>; # $fh is closed it goes out of scope
+ },
+ time => (stat($name))[9],
+ };
+ trick_taint($data->{text}) if $data->{text};
+ $cache->{$name} = $self->_bz_compile($data) or die "compile error: $name";
+ }
+ };
+ File::Find::find({wanted => $wanted, no_chdir => 1}, $template_dir);
+ }
+
+ return $self;
+}
+
+sub fetch {
+ my ($self, $name, $prefix) = @_;
+ my $file;
+ if (File::Spec->file_name_is_absolute($name)) {
+ $file = $name;
+ }
+ elsif ($name =~ m#^\./#) {
+ $file = File::Spec->rel2abs($name);
+ }
+ else {
+ $file = $self->{_BZ_SEARCH}{$name};
+ }
+
+ if (not $file) {
+ return ("cannot find file - $name ($file)", STATUS_ERROR);
+ }
+
+ if ($self->{_BZ_CACHE}{$file}) {
+ return ($self->{_BZ_CACHE}{$file}, undef);
+ }
+ else {
+ return ("unknown file - $file", STATUS_ERROR);
+ }
+}
+
+sub _bz_compile {
+ my ($self, $data) = @_;
+
+ my $parser = $self->{PARSER} ||= Template::Config->parser($self->{PARAMS})
+ || return (Template::Config->error(), STATUS_ERROR);
+
+ # discard the template text - we don't need it any more
+ my $text = delete $data->{text};
+
+ # call parser to compile template into Perl code
+ if (my $parsedoc = $parser->parse($text, $data)) {
+ $parsedoc->{METADATA} = {
+ 'name' => $data->{name},
+ 'modtime' => $data->{time},
+ %{$parsedoc->{METADATA}},
+ };
+
+ return Template::Document->new($parsedoc);
+ }
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Template::PreloadProvider - Better performance under mod_perl.
+
+=head1 SYNOPSIS
+
+Bugzilla::Template::PreloadProvider->new($config);
+
+=head1 DESCRIPTION
+
+The template provider is loaded in each worker, so each worker ends up stat'ing the same files. Eventually each worker has loaded every template, only to repeat this again. There are really not that many templates.
+
+With this and the .htaccess thing, a running bugzilla only runs stat() about once per request.
+
+https://bugzilla.mozilla.org/show_bug.cgi?id=1352264
+
+=head1 SUBROUTINES
+
+=over
+
+=item C<fetch>
+
+Load template file from filesystem.
+
+=back
+
+=cut