WC lock idea and patch with stub functions
authorjoey <joey@0fa5a96a-9a0e-0410-b3b2-a0fd24251071>
Wed, 21 Feb 2007 03:54:18 +0000 (03:54 +0000)
committerjoey <joey@0fa5a96a-9a0e-0410-b3b2-a0fd24251071>
Wed, 21 Feb 2007 03:54:18 +0000 (03:54 +0000)
doc/bugs/locking_fun.mdwn

index 8c4e0690bf0d0f632ef91bb2d280a74d82afc41c..5ecf9f846f17ea9015c765279d5ff15958aa6018 100644 (file)
@@ -3,19 +3,118 @@ changes at once with its commit message (see r2779). The loser gets a
 message that there were conflicts and gets to see his own edits as the
 conflicting edits he's supposed to resolve.
 
-This can happen because CGI.pm writes the change, then drops the lock
-before calling rcs_commit. It can't keep the lock because the commit hook
-needs to be able to lock.
+This can happen because CGI.pm writes the change, then drops the main wiki 
+lock before calling rcs_commit. It can't keep the lock because the commit
+hook needs to be able to lock.
 
-Using a shared reader lock plus an exclusive writer lock would seem to
-allow getting around this. The CGI would need the exclusive lock when
-editing the WC, then it could drop/convert that to the reader lock, keep
-the lock open, and lauch the post-commit hook, which would use the reader
-lock.
+We batted this around for an hour or two on irc. The best solution seems to
+be adding a subsidiary second lock, which is only used to lock the working
+copy and is a blocking read/write lock.
 
-One problem with the reader/writer idea is that the post-commit hook writes
-wiki state.
+* As before, the CGI will take the main wiki lock when starting up.
+* Before writing to the WC, the CGI takes an exclusive lock on the WC.
+* After writing to the WC, the CGI can downgrade it to a shared lock.
+  (This downgrade has to happen atomically, to prevent other CGIs from
+  stealing the exclusive lock.)
+* Then the CGI, as before, drops the main wiki lock to prevent deadlock. It
+  keeps its shared WC lock.
+* The commit hook takes first the main wiki lock and then the shared WC lock
+  when starting up, and holds them until it's done.
+* Once the commit is done, the CGI, as before, does not attempt to regain
+  the main wiki lock (that could deadlock). It does its final stuff and
+  exits, dropping the shared WC lock.
 
-An alternative approach might be setting a flag that prevents the
-post-commit hook from doing anything, and keeping the lock. Then the CGI
-would do the render & etc that the post-commit hook normally does.
+Sample patch, with stub functions for the new lock:
+
+<pre>
+Index: IkiWiki/CGI.pm
+===================================================================
+--- IkiWiki/CGI.pm     (revision 2774)
++++ IkiWiki/CGI.pm     (working copy)
+@@ -494,9 +494,14 @@
+               $content=~s/\r\n/\n/g;
+               $content=~s/\r/\n/g;
++              lockwc_exclusive();
++
+               $config{cgi}=0; # avoid cgi error message
+               eval { writefile($file, $config{srcdir}, $content) };
+               $config{cgi}=1;
++
++              lockwc_shared();
++
+               if ($@) {
+                       $form->field(name => "rcsinfo", value => rcs_prepedit($file),
+                               force => 1);
+Index: IkiWiki/Plugin/poll.pm
+===================================================================
+--- IkiWiki/Plugin/poll.pm     (revision 2770)
++++ IkiWiki/Plugin/poll.pm     (working copy)
+@@ -120,7 +120,9 @@
+               $content =~ s{(\\?)\[\[poll\s+([^]]+)\s*\]\]}{$edit->($1, $2)}seg;
+               # Store their vote, update the page, and redirect to it.
++              IkiWiki::lockwc_exclusive();
+               writefile($pagesources{$page}, $config{srcdir}, $content);
++              IkiWiki::lockwc_shared();
+               $session->param($choice_param, $choice);
+               IkiWiki::cgi_savesession($session);
+               $oldchoice=$session->param($choice_param);
+@@ -130,6 +132,10 @@
+                       IkiWiki::rcs_commit($pagesources{$page}, "poll vote ($choice)",
+                               IkiWiki::rcs_prepedit($pagesources{$page}),
+                               $session->param("name"), $ENV{REMOTE_ADDR});
++                      # Make sure that the repo is up-to-date;
++                      # locking prevents the post-commit hook
++                      # from updating it.
++                      rcs_update();
+               }
+               else {
+                       require IkiWiki::Render;
+Index: ikiwiki.in
+===================================================================
+--- ikiwiki.in (revision 2770)
++++ ikiwiki.in (working copy)
+@@ -121,6 +121,7 @@
+               lockwiki();
+               loadindex();
+               require IkiWiki::Render;
++              lockwc_shared();
+               rcs_update();
+               refresh();
+               rcs_notify() if $config{notify};
+Index: IkiWiki.pm
+===================================================================
+--- IkiWiki.pm (revision 2770)
++++ IkiWiki.pm (working copy)
+@@ -617,6 +617,29 @@
+       close WIKILOCK;
+ } #}}}
++sub lockwc_exclusive () { #{{{
++      # Take an exclusive lock on the working copy.
++      # The lock will be dropped on program exit.
++      # Note: This lock should only be taken _after_ the main wiki
++      # lock.
++      
++      # TODO
++} #}}}
++
++sub lockwc_shared () { #{{{
++      # Take a shared lock on the working copy. If an exclusive lock
++      # already exists, downgrade it to a shared lock.
++      # The lock will be dropped on program exit.
++      # Note: This lock should only be taken _after_ the main wiki
++      # lock.
++      
++      # TODO
++} #}}}
++
++sub unlockwc () { #{{{
++      close WIKIWCLOCK;
++} #}}}
++
+ sub loadindex () { #{{{
+       open (IN, "$config{wikistatedir}/index") || return;
+       while (<IN>) {
+</pre>