[dw-free] interest counts not being recorded properly
[commit: http://hg.dwscoalition.org/dw-free/rev/738d19fa977d]
http://bugs.dwscoalition.org/show_bug.cgi?id=1220
Don't pass in $old into set_interests (to avoid the chance it's out of
date); lock around the entirety of set_interests and interest_update to
avoid race conditions. We also now get the current list of intids straight
from the DB, and make sure that the interest exists (or doesn't) -- so we
don't do any work on the DB if we shouldn't need to.
Patch by
exor674.
Files modified:
http://bugs.dwscoalition.org/show_bug.cgi?id=1220
Don't pass in $old into set_interests (to avoid the chance it's out of
date); lock around the entirety of set_interests and interest_update to
avoid race conditions. We also now get the current list of intids straight
from the DB, and make sure that the interest exists (or doesn't) -- so we
don't do any work on the DB if we shouldn't need to.
Patch by
Files modified:
- cgi-bin/DW/Worker/ContentImporter/Local/Bio.pm
- cgi-bin/LJ/Keywords.pm
- cgi-bin/LJ/Setting/Interests.pm
- cgi-bin/LJ/Widget/CreateAccountProfile.pm
- htdocs/manage/profile/index.bml
--------------------------------------------------------------------------------
diff -r ee6ce2cebd46 -r 738d19fa977d cgi-bin/DW/Worker/ContentImporter/Local/Bio.pm
--- a/cgi-bin/DW/Worker/ContentImporter/Local/Bio.pm Thu Dec 15 00:09:14 2011 +0800
+++ b/cgi-bin/DW/Worker/ContentImporter/Local/Bio.pm Thu Dec 15 00:24:37 2011 +0800
@@ -45,7 +45,7 @@
push @all_ints, lc( $int ) unless defined( $old_interests->{$int} );
}
- $u->set_interests( $old_interests, \@all_ints );
+ $u->set_interests( \@all_ints );
}
=head2 C<< $class->merge_bio_items( $user, $hashref, $items ) >>
diff -r ee6ce2cebd46 -r 738d19fa977d cgi-bin/LJ/Keywords.pm
--- a/cgi-bin/LJ/Keywords.pm Thu Dec 15 00:09:14 2011 +0800
+++ b/cgi-bin/LJ/Keywords.pm Thu Dec 15 00:24:37 2011 +0800
@@ -302,6 +302,23 @@
my ( $add, $del ) = ( $opts{add}, $opts{del} );
return 1 unless $add || $del; # nothing to do
+ my $lock;
+ unless ( $opts{has_lock} ) {
+ while ( 1 ) {
+ $lock = LJ::locker()->trylock( 'interests:' . $u->userid );
+ last if $lock;
+
+ # pause for 0.0-0.3 seconds to shuffle things up. generally good behavior
+ # when you're contending for locks.
+ select undef, undef, undef, rand() * 0.3;
+ }
+ }
+
+ my %wanted_add = map { $_ => 1 } @$add;
+ my %wanted_del = map { $_ => 1 } @$del;
+
+ my %cur_ids = map { $_ => 1 } @{ $u->get_interests( { justids => 1, forceids => 1 } ) };
+
# track if we made changes to refresh memcache later.
my $did_mod = 0;
@@ -311,26 +328,29 @@
my $uid = $u->userid;
my $dbh = LJ::get_db_writer() or die LJ::Lang::ml( "error.nodb" );
- if ( $del && @$del ) {
+ my @filtered_del = grep { delete $cur_ids{$_} && ! $wanted_add{$_} } @$del;
+ if ( $del && @filtered_del ) {
$did_mod = 1;
- my $intid_in = join ',', map { $dbh->quote( $_ ) } @$del;
+ my $intid_in = join ',', map { $dbh->quote( $_ ) } @filtered_del;
+
$dbh->do( "DELETE FROM $uitable WHERE userid=$uid AND intid IN ($intid_in)" );
die $dbh->errstr if $dbh->err;
$dbh->do( "UPDATE interests SET intcount=intcount-1 WHERE intid IN ($intid_in)" );
die $dbh->errstr if $dbh->err;
}
- if ( $add && @$add ) {
+ my @filtered_add = grep { ! $cur_ids{$_}++ && ! $wanted_del{$_} } @$add;
+ if ( $add && @filtered_add ) {
# assume we've already checked maxinterests
$did_mod = 1;
- my $intid_in = join ',', map { $dbh->quote( $_ ) } @$add;
- my $sqlp = join ',', map { "(?,?)" } @$add;
+ my $intid_in = join ',', map { $dbh->quote( $_ ) } @filtered_add;
+ my $sqlp = join ',', map { "(?,?)" } @filtered_add;
$dbh->do( "REPLACE INTO $uitable (userid, intid) VALUES $sqlp",
- undef, map { ( $uid, $_ ) } @$add );
+ undef, map { ( $uid, $_ ) } @filtered_add );
die $dbh->errstr if $dbh->err;
# set a zero intcount for any new ints
$dbh->do( "INSERT IGNORE INTO interests (intid, intcount) VALUES $sqlp",
- undef, map { ( $_, 0 ) } @$add );
+ undef, map { ( $_, 0 ) } @filtered_add );
die $dbh->errstr if $dbh->err;
# now do the increment for all ints
$dbh->do( "UPDATE interests SET intcount=intcount+1 WHERE intid IN ($intid_in)" );
@@ -385,17 +405,26 @@
# des: Change a user's interests.
-# des-old: hashref of old interests (hashing being interest => intid)
# des-new: listref of new interests
# returns: 1 on success, undef on failure
sub set_interests {
- my ($u, $old, $new) = @_;
+ my ($u, $new) = @_;
$u = LJ::want_user( $u ) or return undef;
- return undef unless ref $old eq 'HASH';
return undef unless ref $new eq 'ARRAY';
+ my $lock;
+ while ( 1 ) {
+ $lock = LJ::locker()->trylock( 'interests:' . $u->userid );
+ last if $lock;
+
+ # pause for 0.0-0.3 seconds to shuffle things up. generally good behavior
+ # when you're contending for locks.
+ select undef, undef, undef, rand() * 0.3;
+ }
+
+ my $old = $u->interests( { forceids => 1 } );
my %int_add = ();
my %int_del = %$old; # assume deleting everything, unless in @$new
@@ -413,7 +442,7 @@
}
# Note this does NOT check against maxinterests, do that in the caller.
- $u->interest_update( add => \@new_intids, del => [ values %int_del ] );
+ $u->interest_update( add => \@new_intids, del => [ values %int_del ], has_lock => 1 );
LJ::Hooks::run_hooks("set_interests", $u, \%int_del, \@new_intids); # interest => intid
diff -r ee6ce2cebd46 -r 738d19fa977d cgi-bin/LJ/Setting/Interests.pm
--- a/cgi-bin/LJ/Setting/Interests.pm Thu Dec 15 00:09:14 2011 +0800
+++ b/cgi-bin/LJ/Setting/Interests.pm Thu Dec 15 00:24:37 2011 +0800
@@ -72,9 +72,8 @@
my $interest_list = $class->get_arg($args, "interests");
my @new_interests = LJ::interest_string_to_list($interest_list);
- my $old_interests = $u->interests;
- $u->set_interests($old_interests, \@new_interests);
+ $u->set_interests( \@new_interests );
}
1;
diff -r ee6ce2cebd46 -r 738d19fa977d cgi-bin/LJ/Widget/CreateAccountProfile.pm
--- a/cgi-bin/LJ/Widget/CreateAccountProfile.pm Thu Dec 15 00:09:14 2011 +0800
+++ b/cgi-bin/LJ/Widget/CreateAccountProfile.pm Thu Dec 15 00:24:37 2011 +0800
@@ -222,8 +222,6 @@
'int' => $err->[1]{int} } );
}
- my $old_interests = $u->interests;
-
# bio
$from_post{errors}->{bio} = LJ::Lang::ml('/manage/profile/index.bml.error.bio.toolong') if length $post->{bio} >= LJ::BMAX_BIO;
LJ::EmbedModule->parse_module_embed($u, \$post->{bio});
@@ -232,7 +230,7 @@
$u->update_self( { name => $post->{name} } );
$u->invalidate_directory_record;
$u->set_prop('gender', $post->{gender});
- $u->set_interests($old_interests, \@valid_ints);
+ $u->set_interests( \@valid_ints );
$u->set_bio($post->{bio}, $post->{bio_absent});
}
diff -r ee6ce2cebd46 -r 738d19fa977d htdocs/manage/profile/index.bml
--- a/htdocs/manage/profile/index.bml Thu Dec 15 00:09:14 2011 +0800
+++ b/htdocs/manage/profile/index.bml Thu Dec 15 00:24:37 2011 +0800
@@ -735,7 +735,7 @@
return LJ::bad_input( map { BML::ml(@$_) } @interrors );
}
- $u->set_interests( \%interests, \@valid_ints );
+ $u->set_interests( \@valid_ints );
}
LJ::Hooks::run_hooks('profile_save', $u);
--------------------------------------------------------------------------------
