WC lock idea and patch with stub functions
[ikiwiki] / doc / bugs / locking_fun.mdwn
1 It's possible for concurrent web edits to race and the winner commits both
2 changes at once with its commit message (see r2779). The loser gets a
3 message that there were conflicts and gets to see his own edits as the
4 conflicting edits he's supposed to resolve.
5
6 This can happen because CGI.pm writes the change, then drops the main wiki 
7 lock before calling rcs_commit. It can't keep the lock because the commit
8 hook needs to be able to lock.
9
10 We batted this around for an hour or two on irc. The best solution seems to
11 be adding a subsidiary second lock, which is only used to lock the working
12 copy and is a blocking read/write lock.
13
14 * As before, the CGI will take the main wiki lock when starting up.
15 * Before writing to the WC, the CGI takes an exclusive lock on the WC.
16 * After writing to the WC, the CGI can downgrade it to a shared lock.
17   (This downgrade has to happen atomically, to prevent other CGIs from
18   stealing the exclusive lock.)
19 * Then the CGI, as before, drops the main wiki lock to prevent deadlock. It
20   keeps its shared WC lock.
21 * The commit hook takes first the main wiki lock and then the shared WC lock
22   when starting up, and holds them until it's done.
23 * Once the commit is done, the CGI, as before, does not attempt to regain
24   the main wiki lock (that could deadlock). It does its final stuff and
25   exits, dropping the shared WC lock.
26
27 Sample patch, with stub functions for the new lock:
28
29 <pre>
30 Index: IkiWiki/CGI.pm
31 ===================================================================
32 --- IkiWiki/CGI.pm      (revision 2774)
33 +++ IkiWiki/CGI.pm      (working copy)
34 @@ -494,9 +494,14 @@
35                 $content=~s/\r\n/\n/g;
36                 $content=~s/\r/\n/g;
37  
38 +               lockwc_exclusive();
39 +
40                 $config{cgi}=0; # avoid cgi error message
41                 eval { writefile($file, $config{srcdir}, $content) };
42                 $config{cgi}=1;
43 +
44 +               lockwc_shared();
45 +
46                 if ($@) {
47                         $form->field(name => "rcsinfo", value => rcs_prepedit($file),
48                                 force => 1);
49 Index: IkiWiki/Plugin/poll.pm
50 ===================================================================
51 --- IkiWiki/Plugin/poll.pm      (revision 2770)
52 +++ IkiWiki/Plugin/poll.pm      (working copy)
53 @@ -120,7 +120,9 @@
54                 $content =~ s{(\\?)\[\[poll\s+([^]]+)\s*\]\]}{$edit->($1, $2)}seg;
55  
56                 # Store their vote, update the page, and redirect to it.
57 +               IkiWiki::lockwc_exclusive();
58                 writefile($pagesources{$page}, $config{srcdir}, $content);
59 +               IkiWiki::lockwc_shared();
60                 $session->param($choice_param, $choice);
61                 IkiWiki::cgi_savesession($session);
62                 $oldchoice=$session->param($choice_param);
63 @@ -130,6 +132,10 @@
64                         IkiWiki::rcs_commit($pagesources{$page}, "poll vote ($choice)",
65                                 IkiWiki::rcs_prepedit($pagesources{$page}),
66                                 $session->param("name"), $ENV{REMOTE_ADDR});
67 +                       # Make sure that the repo is up-to-date;
68 +                       # locking prevents the post-commit hook
69 +                       # from updating it.
70 +                       rcs_update();
71                 }
72                 else {
73                         require IkiWiki::Render;
74 Index: ikiwiki.in
75 ===================================================================
76 --- ikiwiki.in  (revision 2770)
77 +++ ikiwiki.in  (working copy)
78 @@ -121,6 +121,7 @@
79                 lockwiki();
80                 loadindex();
81                 require IkiWiki::Render;
82 +               lockwc_shared();
83                 rcs_update();
84                 refresh();
85                 rcs_notify() if $config{notify};
86 Index: IkiWiki.pm
87 ===================================================================
88 --- IkiWiki.pm  (revision 2770)
89 +++ IkiWiki.pm  (working copy)
90 @@ -617,6 +617,29 @@
91         close WIKILOCK;
92  } #}}}
93  
94 +sub lockwc_exclusive () { #{{{
95 +       # Take an exclusive lock on the working copy.
96 +       # The lock will be dropped on program exit.
97 +       # Note: This lock should only be taken _after_ the main wiki
98 +       # lock.
99 +       
100 +       # TODO
101 +} #}}}
102 +
103 +sub lockwc_shared () { #{{{
104 +       # Take a shared lock on the working copy. If an exclusive lock
105 +       # already exists, downgrade it to a shared lock.
106 +       # The lock will be dropped on program exit.
107 +       # Note: This lock should only be taken _after_ the main wiki
108 +       # lock.
109 +       
110 +       # TODO
111 +} #}}}
112 +
113 +sub unlockwc () { #{{{
114 +       close WIKIWCLOCK;
115 +} #}}}
116 +
117  sub loadindex () { #{{{
118         open (IN, "$config{wikistatedir}/index") || return;
119         while (<IN>) {
120 </pre>