Skip to content

Commit

Permalink
Bug 992767: backport bug 987032 to bmo (allow memcached to cache bugz…
Browse files Browse the repository at this point in the history
…illa configuration information)
  • Loading branch information
globau committed May 12, 2014
1 parent 0295433 commit 3ff56a8
Show file tree
Hide file tree
Showing 13 changed files with 237 additions and 52 deletions.
3 changes: 3 additions & 0 deletions Bugzilla/Classification.pm
Expand Up @@ -31,6 +31,8 @@ use base qw(Bugzilla::Field::ChoiceInterface Bugzilla::Object);
#### Initialization ####
###############################

use constant IS_CONFIG => 1;

use constant DB_TABLE => 'classifications';
use constant LIST_ORDER => 'sortkey, name';

Expand Down Expand Up @@ -75,6 +77,7 @@ sub remove_from_db {
foreach my $id (@$product_ids) {
Bugzilla->memcached->clear({ table => 'products', id => $id });
}
Bugzilla->memcached->clear_config();
}

$self->SUPER::remove_from_db();
Expand Down
5 changes: 5 additions & 0 deletions Bugzilla/Field.pm
Expand Up @@ -87,6 +87,8 @@ use Scalar::Util qw(blessed);
#### Initialization ####
###############################

use constant IS_CONFIG => 1;

use constant DB_TABLE => 'fielddefs';
use constant LIST_ORDER => 'sortkey, name';

Expand Down Expand Up @@ -1056,6 +1058,7 @@ sub create {
$field->_update_visibility_values();

$dbh->bz_commit_transaction();
Bugzilla->memcached->clear_config();

if ($field->custom) {
my $name = $field->name;
Expand All @@ -1080,6 +1083,7 @@ sub create {
unless $is_obsolete;

Bugzilla->memcached->clear({ table => 'fielddefs', id => $field->id });
Bugzilla->memcached->clear_config();
}
};

Expand All @@ -1103,6 +1107,7 @@ sub update {
$dbh->do("UPDATE " . $self->name . " SET visibility_value_id = NULL");
}
$self->_update_visibility_values();
Bugzilla->memcached->clear_config();
return $changes;
}

Expand Down
2 changes: 2 additions & 0 deletions Bugzilla/Field/Choice.pm
Expand Up @@ -37,6 +37,8 @@ use Scalar::Util qw(blessed);
# Initialization #
##################

use constant IS_CONFIG => 1;

use constant DB_COLUMNS => qw(
id
value
Expand Down
4 changes: 4 additions & 0 deletions Bugzilla/Group.pm
Expand Up @@ -37,6 +37,8 @@ use Bugzilla::Config qw(:admin);
##### Module Initialization ###
###############################

use constant IS_CONFIG => 1;

use constant DB_COLUMNS => qw(
groups.id
groups.name
Expand Down Expand Up @@ -231,6 +233,7 @@ sub update {
Bugzilla::Hook::process('group_end_of_update',
{ group => $self, changes => $changes });
$dbh->bz_commit_transaction();
Bugzilla->memcached->clear_config();
return $changes;
}

Expand Down Expand Up @@ -420,6 +423,7 @@ sub create {

Bugzilla::Hook::process('group_end_of_create', { group => $group });
$dbh->bz_commit_transaction();
Bugzilla->memcached->clear_config();
return $group;
}

Expand Down
2 changes: 2 additions & 0 deletions Bugzilla/Keyword.pm
Expand Up @@ -27,6 +27,8 @@ use Bugzilla::Util;
#### Initialization ####
###############################

use constant IS_CONFIG => 1;

use constant DB_COLUMNS => qw(
keyworddefs.id
keyworddefs.name
Expand Down
132 changes: 114 additions & 18 deletions Bugzilla/Memcached.pm
Expand Up @@ -42,6 +42,10 @@ sub _new {
return bless($self, $class);
}

sub enabled {
return $_[0]->{memcached} ? 1 : 0;
}

sub set {
my ($self, $args) = @_;
return unless $self->{memcached};
Expand Down Expand Up @@ -97,6 +101,32 @@ sub get {
}
}

sub set_config {
my ($self, $args) = @_;
return unless $self->{memcached};

if (exists $args->{key}) {
return $self->_set($self->_config_prefix . ':' . $args->{key}, $args->{data});
}
else {
ThrowCodeError('params_required', { function => "Bugzilla::Memcached::set_config",
params => [ 'key' ] });
}
}

sub get_config {
my ($self, $args) = @_;
return unless $self->{memcached};

if (exists $args->{key}) {
return $self->_get($self->_config_prefix . ':' . $args->{key});
}
else {
ThrowCodeError('params_required', { function => "Bugzilla::Memcached::get_config",
params => [ 'key' ] });
}
}

sub clear {
my ($self, $args) = @_;
return unless $self->{memcached};
Expand Down Expand Up @@ -132,44 +162,66 @@ sub clear {

sub clear_all {
my ($self) = @_;
return unless my $memcached = $self->{memcached};
if (!$memcached->incr("prefix", 1)) {
$memcached->add("prefix", time());
}
return unless $self->{memcached};
$self->_inc_prefix("global");
}

# BMO - log that we've wiped the cache
openlog('apache', 'cons,pid', 'local4');
syslog('notice', encode_utf8('[memcached] cache cleared'));
closelog();
sub clear_config {
my ($self) = @_;
return unless $self->{memcached};
$self->_inc_prefix("config");
}

# in order to clear all our keys, we add a prefix to all our keys. when we
# need to "clear" all current keys, we increment the prefix.
sub _prefix {
my ($self) = @_;
my ($self, $name) = @_;
# we don't want to change prefixes in the middle of a request
my $request_cache = Bugzilla->request_cache;
if (!$request_cache->{memcached_prefix}) {
my $request_cache_key = "memcached_prefix_$name";
if (!$request_cache->{$request_cache_key}) {
my $memcached = $self->{memcached};
my $prefix = $memcached->get("prefix");
my $prefix = $memcached->get($name);
if (!$prefix) {
$prefix = time();
if (!$memcached->add("prefix", $prefix)) {
if (!$memcached->add($name, $prefix)) {
# if this failed, either another process set the prefix, or
# memcached is down. assume we lost the race, and get the new
# value. if that fails, memcached is down so use a dummy
# prefix for this request.
$prefix = $memcached->get("prefix") || 0;
$prefix = $memcached->get($name) || 0;
}
}
$request_cache->{memcached_prefix} = $prefix;
$request_cache->{$request_cache_key} = $prefix;
}
return $request_cache->{$request_cache_key};
}

sub _inc_prefix {
my ($self, $name) = @_;
my $memcached = $self->{memcached};
if (!$memcached->incr($name, 1)) {
$memcached->add($name, time());
}
return $request_cache->{memcached_prefix};
delete Bugzilla->request_cache->{"memcached_prefix_$name"};

# BMO - log that we've wiped the cache
openlog('apache', 'cons,pid', 'local4');
syslog('notice', encode_utf8("[memcached] $name cache cleared"));
closelog();
}

sub _global_prefix {
return $_[0]->_prefix("global");
}

sub _config_prefix {
return $_[0]->_prefix("config");
}

sub _encode_key {
my ($self, $key) = @_;
$key = $self->_prefix . ':' . uri_escape_utf8($key);
$key = $self->_global_prefix . ':' . uri_escape_utf8($key);
return length($self->{namespace} . $key) > MAX_KEY_LENGTH
? undef
: $key;
Expand All @@ -182,6 +234,7 @@ sub _set {
ThrowCodeError('param_invalid', { function => "Bugzilla::Memcached::set",
param => "value" });
}

$key = $self->_encode_key($key)
or return;
return $self->{memcached}->set($key, $value);
Expand All @@ -198,17 +251,35 @@ sub _get {
# detaint returned values
# hashes and arrays are detainted just one level deep
if (ref($value) eq 'HASH') {
map { defined($_) && trick_taint($_) } values %$value;
_detaint_hashref($value);
}
elsif (ref($value) eq 'ARRAY') {
trick_taint($_) foreach @$value;
foreach my $value (@$value) {
next unless defined $value;
# arrays of hashes are common
if (ref($value) eq 'HASH') {
_detaint_hashref($value);
}
elsif (!ref($value)) {
trick_taint($value);
}
}
}
elsif (!ref($value)) {
trick_taint($value);
}
return $value;
}

sub _detaint_hashref {
my ($hashref) = @_;
foreach my $value (values %$hashref) {
if (defined($value) && !ref($value)) {
trick_taint($value);
}
}
}

sub _delete {
my ($self, $key) = @_;
$key = $self->_encode_key($key)
Expand Down Expand Up @@ -273,6 +344,14 @@ L<Bugzilla-E<gt>memcached()|Bugzilla/memcached>.
=head1 METHODS
=over
=item C<enabled>
Returns true if Memcached support is available and enabled.
=back
=head2 Setting
Adds a value to Memcached.
Expand All @@ -292,6 +371,13 @@ to C<undef>.
This is a convenience method which allows cached data to be later retrieved by
specifying the C<table> and either the C<id> or C<name>.
=item C<set_config({ key =E<gt> $key, data =E<gt> $data })>
Adds the C<data> using the C<key> while identifying the data as part of
Bugzilla's configuration (such as fields, products, components, groups, etc).
Values set with C<set_config> are automatically cleared when changes are made
to Bugzilla's configuration.
=back
=head2 Getting
Expand All @@ -313,6 +399,11 @@ Return C<value> with the specified C<table> and C<id>.
Return C<value> with the specified C<table> and C<name>.
=item C<get_config({ key =E<gt> $key })>
Return C<value> with the specified C<key> from the configuration cache. See
C<set_config> for more information.
=back
=head2 Clearing
Expand All @@ -335,6 +426,11 @@ corresponding C<table> and C<name> entry.
Removes C<value> with the specified C<table> and C<name>, as well as the
corresponding C<table> and C<id> entry.
=item C<clear_config>
Removes all configuration related values from the cache. See C<set_config> for
more information.
=item C<clear_all>
Removes all values from the cache.
Expand Down
14 changes: 11 additions & 3 deletions Bugzilla/Milestone.pm
Expand Up @@ -108,10 +108,12 @@ sub run_create_validators {

sub update {
my $self = shift;
my $dbh = Bugzilla->dbh;

$dbh->bz_start_transaction();
my $changes = $self->SUPER::update(@_);

if (exists $changes->{value}) {
my $dbh = Bugzilla->dbh;
# The milestone value is stored in the bugs table instead of its ID.
$dbh->do('UPDATE bugs SET target_milestone = ?
WHERE target_milestone = ? AND product_id = ?',
Expand All @@ -121,15 +123,20 @@ sub update {
$dbh->do('UPDATE products SET defaultmilestone = ?
WHERE id = ? AND defaultmilestone = ?',
undef, ($self->name, $self->product_id, $changes->{value}->[0]));
Bugzilla->memcached->clear({ table => 'produles', id => $self->product_id });
Bugzilla->memcached->clear({ table => 'products', id => $self->product_id });
}
$dbh->bz_commit_transaction();
Bugzilla->memcached->clear_config();

return $changes;
}

sub remove_from_db {
my $self = shift;
my $dbh = Bugzilla->dbh;

$dbh->bz_start_transaction();

# The default milestone cannot be deleted.
if ($self->name eq $self->product->default_milestone) {
ThrowUserError('milestone_is_default', { milestone => $self });
Expand Down Expand Up @@ -158,8 +165,9 @@ sub remove_from_db {
Bugzilla->user->id, $timestamp);
}
}
$self->SUPER::remove_from_db();

$dbh->do('DELETE FROM milestones WHERE id = ?', undef, $self->id);
$dbh->bz_commit_transaction();
}

################################
Expand Down

0 comments on commit 3ff56a8

Please sign in to comment.