* Since the CGI had to drop the wiki lock to avoid deadlocking the

commit hook, it was possible for one CGI to race another one and "win"
  the commit of both their files. This race has been fixed by adding a new
  commitlock, which when locked by the CGI, disables the commit hook
  (except for commit mails). The CGI then takes care of the updates the
  commit hook would have done.
master
joey 2007-02-21 08:55:28 +00:00
parent 24b8343506
commit c60477228c
7 changed files with 91 additions and 51 deletions

View File

@ -38,6 +38,7 @@ sub defaultconfig () { #{{{
wikiname => "wiki", wikiname => "wiki",
default_pageext => "mdwn", default_pageext => "mdwn",
cgi => 0, cgi => 0,
post_commit => 0,
rcs => '', rcs => '',
notify => 0, notify => 0,
url => '', url => '',
@ -601,7 +602,7 @@ sub lockwiki () { #{{{
} }
open(WIKILOCK, ">$config{wikistatedir}/lockfile") || open(WIKILOCK, ">$config{wikistatedir}/lockfile") ||
error ("cannot write to $config{wikistatedir}/lockfile: $!"); error ("cannot write to $config{wikistatedir}/lockfile: $!");
if (! flock(WIKILOCK, 2 | 4)) { if (! flock(WIKILOCK, 2 | 4)) { # LOCK_EX | LOCK_NB
debug("wiki seems to be locked, waiting for lock"); debug("wiki seems to be locked, waiting for lock");
my $wait=600; # arbitrary, but don't hang forever to my $wait=600; # arbitrary, but don't hang forever to
# prevent process pileup # prevent process pileup
@ -617,6 +618,29 @@ sub unlockwiki () { #{{{
close WIKILOCK; close WIKILOCK;
} #}}} } #}}}
sub commit_hook_enabled () { #{{{
open(COMMITLOCK, "+>$config{wikistatedir}/commitlock") ||
error ("cannot write to $config{wikistatedir}/commitlock: $!");
if (! flock(WIKILOCK, 1 | 4)) { # LOCK_SH | LOCK_NB to test
close WIKILOCK;
return 0;
}
close WIKILOCK;
return 1;
} #}}}
sub disable_commit_hook () { #{{{
open(COMMITLOCK, ">$config{wikistatedir}/commitlock") ||
error ("cannot write to $config{wikistatedir}/commitlock: $!");
if (! flock(COMMITLOCK, 2)) { # LOCK_EX
error("failed to get commit lock");
}
} #}}}
sub enable_commit_hook () { #{{{
close COMMITLOCK;
} #}}}
sub loadindex () { #{{{ sub loadindex () { #{{{
open (IN, "$config{wikistatedir}/index") || return; open (IN, "$config{wikistatedir}/index") || return;
while (<IN>) { while (<IN>) {

View File

@ -513,6 +513,7 @@ sub cgi_editpage ($$) { #{{{
return; return;
} }
my $conflict;
if ($config{rcs}) { if ($config{rcs}) {
my $message=""; my $message="";
if (defined $form->field('comments') && if (defined $form->field('comments') &&
@ -523,44 +524,44 @@ sub cgi_editpage ($$) { #{{{
if ($newfile) { if ($newfile) {
rcs_add($file); rcs_add($file);
} }
# prevent deadlock with post-commit hook
unlockwiki(); # Prevent deadlock with post-commit hook by
# presumably the commit will trigger an update # signaling to it that it should not try to
# of the wiki # do anything (except send commit mails).
my $conflict=rcs_commit($file, $message, disable_commit_hook();
$conflict=rcs_commit($file, $message,
$form->field("rcsinfo"), $form->field("rcsinfo"),
$session->param("name"), $ENV{REMOTE_ADDR}); $session->param("name"), $ENV{REMOTE_ADDR});
enable_commit_hook();
rcs_update();
}
if (defined $conflict) { # Refresh even if there was a conflict, since other changes
$form->field(name => "rcsinfo", value => rcs_prepedit($file), # may have been committed while the post-commit hook was
force => 1); # disabled.
$form->tmpl_param("page_conflict", 1); require IkiWiki::Render;
$form->field("editcontent", value => $conflict, force => 1); refresh();
$form->field(name => "comments", value => $form->field('comments'), force => 1); saveindex();
$form->field("do", "edit)");
$form->tmpl_param("page_select", 0); if (defined $conflict) {
$form->field(name => "page", type => 'hidden'); $form->field(name => "rcsinfo", value => rcs_prepedit($file),
$form->field(name => "type", type => 'hidden'); force => 1);
$form->title(sprintf(gettext("editing %s"), $page)); $form->tmpl_param("page_conflict", 1);
print $form->render(submit => \@buttons); $form->field("editcontent", value => $conflict, force => 1);
return; $form->field(name => "comments", value => $form->field('comments'), force => 1);
} $form->field("do", "edit)");
else { $form->tmpl_param("page_select", 0);
# Make sure that the repo is up-to-date; $form->field(name => "page", type => 'hidden');
# locking prevents the post-commit hook $form->field(name => "type", type => 'hidden');
# from updating it. $form->title(sprintf(gettext("editing %s"), $page));
rcs_update(); print $form->render(submit => \@buttons);
} return;
} }
else { else {
require IkiWiki::Render; # The trailing question mark tries to avoid broken
refresh(); # caches and get the most recent version of the page.
saveindex(); redirect($q, "$config{url}/".htmlpage($page)."?updated");
} }
# The trailing question mark tries to avoid broken
# caches and get the most recent version of the page.
redirect($q, "$config{url}/".htmlpage($page)."?updated");
} }
} #}}} } #}}}

View File

@ -125,17 +125,17 @@ sub cgi ($) { #{{{
IkiWiki::cgi_savesession($session); IkiWiki::cgi_savesession($session);
$oldchoice=$session->param($choice_param); $oldchoice=$session->param($choice_param);
if ($config{rcs}) { if ($config{rcs}) {
# prevent deadlock with post-commit hook disable_commit_hook();
IkiWiki::unlockwiki();
IkiWiki::rcs_commit($pagesources{$page}, "poll vote ($choice)", IkiWiki::rcs_commit($pagesources{$page}, "poll vote ($choice)",
IkiWiki::rcs_prepedit($pagesources{$page}), IkiWiki::rcs_prepedit($pagesources{$page}),
$session->param("name"), $ENV{REMOTE_ADDR}); $session->param("name"), $ENV{REMOTE_ADDR});
enable_commit_hook();
rcs_update();
} }
else { require IkiWiki::Render;
require IkiWiki::Render; IkiWiki::refresh();
IkiWiki::refresh(); IkiWiki::saveindex();
IkiWiki::saveindex();
}
# Need to set cookie in same http response that does the # Need to set cookie in same http response that does the
# redir. # redir.
eval q{use CGI::Cookie}; eval q{use CGI::Cookie};

View File

@ -36,6 +36,9 @@ sub setup_standard {
foreach my $wrapper (@wrappers) { foreach my $wrapper (@wrappers) {
%config=(%startconfig, verbose => 0, %setup, %{$wrapper}); %config=(%startconfig, verbose => 0, %setup, %{$wrapper});
checkconfig(); checkconfig();
if (! $config{cgi} && ! $config{post_commit}) {
$config{post_commit}=1;
}
gen_wrapper(); gen_wrapper();
} }
%config=(%startconfig); %config=(%startconfig);

8
debian/changelog vendored
View File

@ -27,8 +27,14 @@ ikiwiki (1.44) UNRELEASED; urgency=low
* Smarter detection of no-op changes to po files. * Smarter detection of no-op changes to po files.
* Elegant patch from Ethan to clean up the display of page names in the * Elegant patch from Ethan to clean up the display of page names in the
dropdown when creating a new page. dropdown when creating a new page.
* Since the CGI had to drop the wiki lock to avoid deadlocking the
commit hook, it was possible for one CGI to race another one and "win"
the commit of both their files. This race has been fixed by adding a new
commitlock, which when locked by the CGI, disables the commit hook
(except for commit mails). The CGI then takes care of the updates the
commit hook would have done.
-- Joey Hess <joeyh@debian.org> Tue, 20 Feb 2007 19:14:39 -0500 -- Joey Hess <joeyh@debian.org> Wed, 21 Feb 2007 03:35:52 -0500
ikiwiki (1.43) unstable; urgency=low ikiwiki (1.43) unstable; urgency=low

View File

@ -117,6 +117,12 @@ sub main () { #{{{
require IkiWiki::Render; require IkiWiki::Render;
commandline_render(); commandline_render();
} }
elsif ($config{post_commit} && ! commit_hook_enabled()) {
if ($config{notify}) {
loadindex();
rcs_notify();
}
}
else { else {
lockwiki(); lockwiki();
loadindex(); loadindex();

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2007-02-20 19:07-0500\n" "POT-Creation-Date: 2007-02-21 03:51-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -41,16 +41,16 @@ msgstr ""
msgid "creating %s" msgid "creating %s"
msgstr "" msgstr ""
#: ../IkiWiki/CGI.pm:483 ../IkiWiki/CGI.pm:511 ../IkiWiki/CGI.pm:544 #: ../IkiWiki/CGI.pm:483 ../IkiWiki/CGI.pm:511 ../IkiWiki/CGI.pm:556
#, perl-format #, perl-format
msgid "editing %s" msgid "editing %s"
msgstr "" msgstr ""
#: ../IkiWiki/CGI.pm:652 #: ../IkiWiki/CGI.pm:653
msgid "You are banned." msgid "You are banned."
msgstr "" msgstr ""
#: ../IkiWiki/CGI.pm:684 #: ../IkiWiki/CGI.pm:685
msgid "login failed, perhaps you need to turn on cookies?" msgid "login failed, perhaps you need to turn on cookies?"
msgstr "" msgstr ""
@ -380,15 +380,15 @@ msgstr ""
msgid "generating wrappers.." msgid "generating wrappers.."
msgstr "" msgstr ""
#: ../IkiWiki/Setup/Standard.pm:68 #: ../IkiWiki/Setup/Standard.pm:71
msgid "rebuilding wiki.." msgid "rebuilding wiki.."
msgstr "" msgstr ""
#: ../IkiWiki/Setup/Standard.pm:71 #: ../IkiWiki/Setup/Standard.pm:74
msgid "refreshing wiki.." msgid "refreshing wiki.."
msgstr "" msgstr ""
#: ../IkiWiki/Setup/Standard.pm:80 #: ../IkiWiki/Setup/Standard.pm:83
msgid "done" msgid "done"
msgstr "" msgstr ""
@ -437,11 +437,11 @@ msgstr ""
msgid "usage: ikiwiki [options] source dest" msgid "usage: ikiwiki [options] source dest"
msgstr "" msgstr ""
#: ../IkiWiki.pm:103 #: ../IkiWiki.pm:104
msgid "Must specify url to wiki with --url when using --cgi" msgid "Must specify url to wiki with --url when using --cgi"
msgstr "" msgstr ""
#: ../IkiWiki.pm:150 ../IkiWiki.pm:151 #: ../IkiWiki.pm:151 ../IkiWiki.pm:152
msgid "Error" msgid "Error"
msgstr "" msgstr ""
@ -449,7 +449,7 @@ msgstr ""
#. translators: preprocessor directive name, #. translators: preprocessor directive name,
#. translators: the second a page name, the #. translators: the second a page name, the
#. translators: third a number. #. translators: third a number.
#: ../IkiWiki.pm:560 #: ../IkiWiki.pm:561
#, perl-format #, perl-format
msgid "%s preprocessing loop detected on %s at depth %i" msgid "%s preprocessing loop detected on %s at depth %i"
msgstr "" msgstr ""