summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Bugzilla/Template.pm23
-rw-r--r--Bugzilla/Template/PreloadProvider.pm110
-rw-r--r--mod_perl.pl3
3 files changed, 130 insertions, 6 deletions
diff --git a/Bugzilla/Template.pm b/Bugzilla/Template.pm
index 65396b96d..cb10544f1 100644
--- a/Bugzilla/Template.pm
+++ b/Bugzilla/Template.pm
@@ -12,6 +12,7 @@ use 5.10.1;
use strict;
use warnings;
+use Bugzilla::Template::PreloadProvider;
use Bugzilla::Bug;
use Bugzilla::Constants;
use Bugzilla::Hook;
@@ -47,6 +48,8 @@ use constant FORMAT_3_SIZE => [19,28,28];
use constant FORMAT_DOUBLE => '%19s %-55s';
use constant FORMAT_2_SIZE => [19,55];
+my %SHARED_PROVIDERS;
+
# Pseudo-constant.
sub SAFE_URL_REGEXP {
my $safe_protocols = join('|', SAFE_PROTOCOLS);
@@ -973,10 +976,12 @@ sub create {
PLUGIN_BASE => 'Bugzilla::Template::Plugin',
- CONSTANTS => _load_constants(),
-
# Default variables for all templates
VARIABLES => {
+ # Some of these are not really constants, and doing this messes up preloading.
+ # they are now fake constants.
+ constants => _load_constants(),
+
# Function for retrieving global parameters.
'Param' => sub { return Bugzilla->params->{$_[0]}; },
@@ -1115,12 +1120,18 @@ sub create {
'is_mobile_browser' => sub { return Bugzilla->cgi->user_agent =~ /Mobi/ },
},
};
+
+ # under mod_perl, use a provider (template loader) that preloads all templates into memory
+ my $provider_class
+ = $ENV{MOD_PERL}
+ ? 'Bugzilla::Template::PreloadProvider'
+ : 'Template::Provider';
+
# Use a per-process provider to cache compiled templates in memory across
# requests.
my $provider_key = join(':', @{ $config->{INCLUDE_PATH} });
- my $shared_providers = Bugzilla->process_cache->{shared_providers} ||= {};
- $shared_providers->{$provider_key} ||= Template::Provider->new($config);
- $config->{LOAD_TEMPLATES} = [ $shared_providers->{$provider_key} ];
+ $SHARED_PROVIDERS{$provider_key} ||= $provider_class->new($config);
+ $config->{LOAD_TEMPLATES} = [ $SHARED_PROVIDERS{$provider_key} ];
# BMO - use metrics subclass
local $Template::Config::CONTEXT = Bugzilla->metrics_enabled()
@@ -1209,7 +1220,7 @@ sub precompile_templates {
delete Bugzilla->request_cache->{template};
# Clear out the cached Provider object
- Bugzilla->process_cache->{shared_providers} = undef;
+ %SHARED_PROVIDERS = ();
print install_string('done') . "\n" if $output;
}
diff --git a/Bugzilla/Template/PreloadProvider.pm b/Bugzilla/Template/PreloadProvider.pm
new file mode 100644
index 000000000..2588b1a79
--- /dev/null
+++ b/Bugzilla/Template/PreloadProvider.pm
@@ -0,0 +1,110 @@
+# 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;
diff --git a/mod_perl.pl b/mod_perl.pl
index 0a0a0df6a..f3beb88db 100644
--- a/mod_perl.pl
+++ b/mod_perl.pl
@@ -79,6 +79,9 @@ $Bugzilla::extension_packages = Bugzilla::Extension->load_all();
Bugzilla->preload_features();
+# Force instantiation of template so Bugzilla::Template::PreloadProvider can do its magic.
+Bugzilla->template;
+
# Have ModPerl::RegistryLoader pre-compile all CGI scripts.
my $rl = new ModPerl::RegistryLoader();
# If we try to do this in "new" it fails because it looks for a