* 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",
default_pageext => "mdwn",
cgi => 0,
post_commit => 0,
rcs => '',
notify => 0,
url => '',
@ -601,7 +602,7 @@ sub lockwiki () { #{{{
}
open(WIKILOCK, ">$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");
my $wait=600; # arbitrary, but don't hang forever to
# prevent process pileup
@ -617,6 +618,29 @@ sub unlockwiki () { #{{{
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 () { #{{{
open (IN, "$config{wikistatedir}/index") || return;
while (<IN>) {

View File

@ -513,6 +513,7 @@ sub cgi_editpage ($$) { #{{{
return;
}
my $conflict;
if ($config{rcs}) {
my $message="";
if (defined $form->field('comments') &&
@ -523,44 +524,44 @@ sub cgi_editpage ($$) { #{{{
if ($newfile) {
rcs_add($file);
}
# prevent deadlock with post-commit hook
unlockwiki();
# presumably the commit will trigger an update
# of the wiki
my $conflict=rcs_commit($file, $message,
# Prevent deadlock with post-commit hook by
# signaling to it that it should not try to
# do anything (except send commit mails).
disable_commit_hook();
$conflict=rcs_commit($file, $message,
$form->field("rcsinfo"),
$session->param("name"), $ENV{REMOTE_ADDR});
enable_commit_hook();
rcs_update();
}
if (defined $conflict) {
$form->field(name => "rcsinfo", value => rcs_prepedit($file),
force => 1);
$form->tmpl_param("page_conflict", 1);
$form->field("editcontent", value => $conflict, force => 1);
$form->field(name => "comments", value => $form->field('comments'), force => 1);
$form->field("do", "edit)");
$form->tmpl_param("page_select", 0);
$form->field(name => "page", type => 'hidden');
$form->field(name => "type", type => 'hidden');
$form->title(sprintf(gettext("editing %s"), $page));
print $form->render(submit => \@buttons);
return;
}
else {
# Make sure that the repo is up-to-date;
# locking prevents the post-commit hook
# from updating it.
rcs_update();
}
# Refresh even if there was a conflict, since other changes
# may have been committed while the post-commit hook was
# disabled.
require IkiWiki::Render;
refresh();
saveindex();
if (defined $conflict) {
$form->field(name => "rcsinfo", value => rcs_prepedit($file),
force => 1);
$form->tmpl_param("page_conflict", 1);
$form->field("editcontent", value => $conflict, force => 1);
$form->field(name => "comments", value => $form->field('comments'), force => 1);
$form->field("do", "edit)");
$form->tmpl_param("page_select", 0);
$form->field(name => "page", type => 'hidden');
$form->field(name => "type", type => 'hidden');
$form->title(sprintf(gettext("editing %s"), $page));
print $form->render(submit => \@buttons);
return;
}
else {
require IkiWiki::Render;
refresh();
saveindex();
# 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");
}
# 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);
$oldchoice=$session->param($choice_param);
if ($config{rcs}) {
# prevent deadlock with post-commit hook
IkiWiki::unlockwiki();
disable_commit_hook();
IkiWiki::rcs_commit($pagesources{$page}, "poll vote ($choice)",
IkiWiki::rcs_prepedit($pagesources{$page}),
$session->param("name"), $ENV{REMOTE_ADDR});
enable_commit_hook();
rcs_update();
}
else {
require IkiWiki::Render;
IkiWiki::refresh();
IkiWiki::saveindex();
}
require IkiWiki::Render;
IkiWiki::refresh();
IkiWiki::saveindex();
# Need to set cookie in same http response that does the
# redir.
eval q{use CGI::Cookie};

View File

@ -36,6 +36,9 @@ sub setup_standard {
foreach my $wrapper (@wrappers) {
%config=(%startconfig, verbose => 0, %setup, %{$wrapper});
checkconfig();
if (! $config{cgi} && ! $config{post_commit}) {
$config{post_commit}=1;
}
gen_wrapper();
}
%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.
* Elegant patch from Ethan to clean up the display of page names in the
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

View File

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

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -41,16 +41,16 @@ msgstr ""
msgid "creating %s"
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
msgid "editing %s"
msgstr ""
#: ../IkiWiki/CGI.pm:652
#: ../IkiWiki/CGI.pm:653
msgid "You are banned."
msgstr ""
#: ../IkiWiki/CGI.pm:684
#: ../IkiWiki/CGI.pm:685
msgid "login failed, perhaps you need to turn on cookies?"
msgstr ""
@ -380,15 +380,15 @@ msgstr ""
msgid "generating wrappers.."
msgstr ""
#: ../IkiWiki/Setup/Standard.pm:68
#: ../IkiWiki/Setup/Standard.pm:71
msgid "rebuilding wiki.."
msgstr ""
#: ../IkiWiki/Setup/Standard.pm:71
#: ../IkiWiki/Setup/Standard.pm:74
msgid "refreshing wiki.."
msgstr ""
#: ../IkiWiki/Setup/Standard.pm:80
#: ../IkiWiki/Setup/Standard.pm:83
msgid "done"
msgstr ""
@ -437,11 +437,11 @@ msgstr ""
msgid "usage: ikiwiki [options] source dest"
msgstr ""
#: ../IkiWiki.pm:103
#: ../IkiWiki.pm:104
msgid "Must specify url to wiki with --url when using --cgi"
msgstr ""
#: ../IkiWiki.pm:150 ../IkiWiki.pm:151
#: ../IkiWiki.pm:151 ../IkiWiki.pm:152
msgid "Error"
msgstr ""
@ -449,7 +449,7 @@ msgstr ""
#. translators: preprocessor directive name,
#. translators: the second a page name, the
#. translators: third a number.
#: ../IkiWiki.pm:560
#: ../IkiWiki.pm:561
#, perl-format
msgid "%s preprocessing loop detected on %s at depth %i"
msgstr ""