* Since the CGI had to drop the wiki lock to avoid deadlocking the
authorjoey <joey@0fa5a96a-9a0e-0410-b3b2-a0fd24251071>
Wed, 21 Feb 2007 08:55:28 +0000 (08:55 +0000)
committerjoey <joey@0fa5a96a-9a0e-0410-b3b2-a0fd24251071>
Wed, 21 Feb 2007 08:55:28 +0000 (08:55 +0000)
  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.

IkiWiki.pm
IkiWiki/CGI.pm
IkiWiki/Plugin/poll.pm
IkiWiki/Setup/Standard.pm
debian/changelog
ikiwiki.in
po/ikiwiki.pot

index 40622a5beacdbd2844d7a09f1584436f2b7064bf..c8d959edd11c4edf31b46fab0935e29bd0ff261e 100644 (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>) {
index e23ef8afd748e66caefe29b0e09a69e06e7d1229..e62bcf477601ec38be1178ba3ce3207c1e588293 100644 (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");
        }
 } #}}}
 
index a3321a32e51d57d898facca21ce50f143ea028f6..4eae6a349383e50edfa60b336cde3aca6a479cb3 100644 (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};
index 0c4272286d13eace9ee24c393717b6edd93066b9..f3f7bae5a746d0b6d0ef67b7866a8bcad80aa3f3 100644 (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);
index 6a6dc8f8246db63adbdb0fa3cbcefc8f36d06a67..3625d4c91cb4f884a94c765ac55352edd1409a82 100644 (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.
-
- -- Joey Hess <joeyh@debian.org>  Tue, 20 Feb 2007 19:14:39 -0500
+  * 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>  Wed, 21 Feb 2007 03:35:52 -0500
 
 ikiwiki (1.43) unstable; urgency=low
 
index 24b02bc415f9ad73908eef2c809d2efa1111400e..5dd1064f9fd6900d302dcde2128f35f67cffb09d 100755 (executable)
@@ -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();
index 76337ff7efa2f53dae46d1ef097f8a4e6d17b06e..4399a031776a9208c88869d8e535c15da4cea6ed 100644 (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 ""