From c82963a57cf97932870e11de8cf2a6205132b954 Mon Sep 17 00:00:00 2001 From: Dylan William Hardison Date: Wed, 26 Apr 2017 15:33:11 -0400 Subject: Bug 1352264 - Preload all templates --- Bugzilla/Template.pm | 23 ++++++-- Bugzilla/Template/PreloadProvider.pm | 110 +++++++++++++++++++++++++++++++++++ mod_perl.pl | 3 + 3 files changed, 130 insertions(+), 6 deletions(-) create mode 100644 Bugzilla/Template/PreloadProvider.pm 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 -- cgit v1.2.3-24-g4f1b