Patchwork [3,of,6,V2] repoview: discard filtered changelog if the length differs from unfiltered

login
register
mail settings
Submitter Katsunori FUJIWARA
Date Dec. 16, 2015, 5:50 p.m.
Message ID <6455d3aac05ac9ab6913.1450288251@feefifofum>
Download mbox | patch
Permalink /patch/12071/
State Superseded
Headers show

Comments

Katsunori FUJIWARA - Dec. 16, 2015, 5:50 p.m.
# HG changeset patch
# User FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
# Date 1450287754 -32400
#      Thu Dec 17 02:42:34 2015 +0900
# Node ID 6455d3aac05ac9ab6913b162b46a924b9a512121
# Parent  4210557127a602a91ce9958bb957e2a32493856b
repoview: discard filtered changelog if the length differs from unfiltered

Before this patch, revisions rollbacked at failure of previous
transaction may be visible at subsequent operations unintentionally,
if repoview object is reused even after failure of transaction:
e.g. command server and HTTP server are typical cases.

'repoview' uses the tuple of values below of unfiltered changelog as
"the key" to examine validity of filtered changelog cache.

     - length
     - tip node
     - filtered revisions (as hashed value)
     - '_delayed' field

'repoview' compares between "the key" of unfiltered changelog at
previous caching and now, and reuses filtered changelog cache if no
change is detected.

But this comparison indicates only that there is no change between
unfiltered 'repo.changelog' at last caching and now, but not that
filtered changelog cache is valid for current unfiltered one.

'repoview' uses "shallow copy" of unfiltered changelog to create
filtered changelog cache. In this case, 'index' buffer of unfiltered
changelog is also referred by filtered changelog.

At failure of transaction, unfiltered changelog itself is invalidated
(= un-referred) on the 'repo' side (see 0a7610758c42 also). But
'index' of it still contains revisions to be rollbacked at this
failure, and is referred by filtered changelog.

Therefore, even if there is no change between unfiltered
'repo.changelog' at last caching and now, steps below makes rollbacked
revisions visible via filtered changelog unintentionally.

  1. instantiate unfiltered changelog as 'repo.changelog'
     (call it CL1)

  2. make filtered (= shallow copy of) CL1
     (call it FCL1)

  3. cache FCL1 with "the key" of CL1

  4. revisions are appended to 'index', which is shared by CL1 and FCL1

  5. invalidate 'repo.changelog' (= CL1) at failure of transaction

  6. instantiate 'repo.changelog' again at next operation
     (call it CL2)

     CL2 doesn't have revisions added at (4), because it is
     instantiated from '00changelog.i', which isn't changed while
     failed transaction.

  7. compare between "the key" of CL1 and CL2

  8. FCL1 cached at (3) is reused, because comparison at (7) doesn't
     detect change between CL1 at (1) and CL2

  9. revisions rollbacked at (5) are visible via FCL1 unintentionally,
     because FCL1 still refers 'index' changed at (4)

The root cause of this issue is that there is no examination about
validity of filtered changelog cache against current unfiltered one.

This patch discards filtered changelog if the length of it differs
from one of unfiltered.

"Difference of the length" means that filtered changelog isn't related
to unfiltered one, because the length of changelog isn't changed by
filtering.

This patch uses 'len(cl.index) - 1' directly instead of 'len(cl)', to
follow beda2c9dbbff, which bypasses changelog method for performance
reason.

BTW, at the time of this patch, redundant truncation of
'00changelog.i' at failure of transaction (see 0a7610758c42 for
detail) often prevents "hg serve" from making already rollbacked
revisions visible, because updating timestamps of '00changelog.i' by
truncation makes "hg serve" discard old repoview object with invalid
filtered changelog cache.

This is reason why this issue is overlooked before this patch, even
though test-bundle2-exchange.t has tests in similar situation: failure
of "hg push" via HTTP by pretxnclose hook on server side doesn't
prevent subsequent commands from looking up outgoing revisions
correctly.

But it can't be assumed that this avoidance always works as expected,
because timestamp on the filesystem doesn't have enough resolution for
recent computation power.

Therefore, without this patch, this issue may appear occasionally.
Yuya Nishihara - Dec. 20, 2015, 11:46 a.m.
On Thu, 17 Dec 2015 02:50:51 +0900, FUJIWARA Katsunori wrote:
> # HG changeset patch
> # User FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
> # Date 1450287754 -32400
> #      Thu Dec 17 02:42:34 2015 +0900
> # Node ID 6455d3aac05ac9ab6913b162b46a924b9a512121
> # Parent  4210557127a602a91ce9958bb957e2a32493856b
> repoview: discard filtered changelog if the length differs from unfiltered
> 
> Before this patch, revisions rollbacked at failure of previous
> transaction may be visible at subsequent operations unintentionally,
> if repoview object is reused even after failure of transaction:
> e.g. command server and HTTP server are typical cases.
> 
> 'repoview' uses the tuple of values below of unfiltered changelog as
> "the key" to examine validity of filtered changelog cache.
> 
>      - length
>      - tip node
>      - filtered revisions (as hashed value)
>      - '_delayed' field
> 
> 'repoview' compares between "the key" of unfiltered changelog at
> previous caching and now, and reuses filtered changelog cache if no
> change is detected.
> 
> But this comparison indicates only that there is no change between
> unfiltered 'repo.changelog' at last caching and now, but not that
> filtered changelog cache is valid for current unfiltered one.
> 
> 'repoview' uses "shallow copy" of unfiltered changelog to create
> filtered changelog cache. In this case, 'index' buffer of unfiltered
> changelog is also referred by filtered changelog.
> 
> At failure of transaction, unfiltered changelog itself is invalidated
> (= un-referred) on the 'repo' side (see 0a7610758c42 also). But
> 'index' of it still contains revisions to be rollbacked at this
> failure, and is referred by filtered changelog.
> 
> Therefore, even if there is no change between unfiltered
> 'repo.changelog' at last caching and now, steps below makes rollbacked
> revisions visible via filtered changelog unintentionally.
> 
>   1. instantiate unfiltered changelog as 'repo.changelog'
>      (call it CL1)
> 
>   2. make filtered (= shallow copy of) CL1
>      (call it FCL1)
> 
>   3. cache FCL1 with "the key" of CL1
> 
>   4. revisions are appended to 'index', which is shared by CL1 and FCL1
> 
>   5. invalidate 'repo.changelog' (= CL1) at failure of transaction
> 
>   6. instantiate 'repo.changelog' again at next operation
>      (call it CL2)
> 
>      CL2 doesn't have revisions added at (4), because it is
>      instantiated from '00changelog.i', which isn't changed while
>      failed transaction.
> 
>   7. compare between "the key" of CL1 and CL2
> 
>   8. FCL1 cached at (3) is reused, because comparison at (7) doesn't
>      detect change between CL1 at (1) and CL2
> 
>   9. revisions rollbacked at (5) are visible via FCL1 unintentionally,
>      because FCL1 still refers 'index' changed at (4)
> 
> The root cause of this issue is that there is no examination about
> validity of filtered changelog cache against current unfiltered one.
> 
> This patch discards filtered changelog if the length of it differs
> from one of unfiltered.
> 
> "Difference of the length" means that filtered changelog isn't related
> to unfiltered one, because the length of changelog isn't changed by
> filtering.
> 
> This patch uses 'len(cl.index) - 1' directly instead of 'len(cl)', to
> follow beda2c9dbbff, which bypasses changelog method for performance
> reason.
> 
> BTW, at the time of this patch, redundant truncation of
> '00changelog.i' at failure of transaction (see 0a7610758c42 for
> detail) often prevents "hg serve" from making already rollbacked
> revisions visible, because updating timestamps of '00changelog.i' by
> truncation makes "hg serve" discard old repoview object with invalid
> filtered changelog cache.
> 
> This is reason why this issue is overlooked before this patch, even
> though test-bundle2-exchange.t has tests in similar situation: failure
> of "hg push" via HTTP by pretxnclose hook on server side doesn't
> prevent subsequent commands from looking up outgoing revisions
> correctly.
> 
> But it can't be assumed that this avoidance always works as expected,
> because timestamp on the filesystem doesn't have enough resolution for
> recent computation power.
> 
> Therefore, without this patch, this issue may appear occasionally.
> 
> diff --git a/mercurial/repoview.py b/mercurial/repoview.py
> --- a/mercurial/repoview.py
> +++ b/mercurial/repoview.py
> @@ -308,7 +308,8 @@ class repoview(object):
>          revs = filterrevs(unfi, self.filtername)
>          cl = self._clcache
>          newkey = (unfilen, unfinode, hash(revs), unfichangelog._delayed)
> -        if cl is not None and newkey != self._clcachekey:
> +        if (cl is not None and
> +            (len(cl.index) - 1 != unfilen or newkey != self._clcachekey)):

From your comment, this checks if cl.index is not unfiindex, right?

Instead of validating lengths, I think _clcache should be nullified when
_filecache['changelog'] is deleted. But I'm not sure if it's possible by
repoview.
Katsunori FUJIWARA - Dec. 20, 2015, 4:06 p.m.
At Sun, 20 Dec 2015 20:46:04 +0900,
Yuya Nishihara wrote:
> 
> On Thu, 17 Dec 2015 02:50:51 +0900, FUJIWARA Katsunori wrote:
> > # HG changeset patch
> > # User FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
> > # Date 1450287754 -32400
> > #      Thu Dec 17 02:42:34 2015 +0900
> > # Node ID 6455d3aac05ac9ab6913b162b46a924b9a512121
> > # Parent  4210557127a602a91ce9958bb957e2a32493856b
> > repoview: discard filtered changelog if the length differs from unfiltered
> > 
> > Before this patch, revisions rollbacked at failure of previous
> > transaction may be visible at subsequent operations unintentionally,
> > if repoview object is reused even after failure of transaction:
> > e.g. command server and HTTP server are typical cases.
> > 
> > 'repoview' uses the tuple of values below of unfiltered changelog as
> > "the key" to examine validity of filtered changelog cache.
> > 
> >      - length
> >      - tip node
> >      - filtered revisions (as hashed value)
> >      - '_delayed' field
> > 
> > 'repoview' compares between "the key" of unfiltered changelog at
> > previous caching and now, and reuses filtered changelog cache if no
> > change is detected.
> > 
> > But this comparison indicates only that there is no change between
> > unfiltered 'repo.changelog' at last caching and now, but not that
> > filtered changelog cache is valid for current unfiltered one.
> > 
> > 'repoview' uses "shallow copy" of unfiltered changelog to create
> > filtered changelog cache. In this case, 'index' buffer of unfiltered
> > changelog is also referred by filtered changelog.
> > 
> > At failure of transaction, unfiltered changelog itself is invalidated
> > (= un-referred) on the 'repo' side (see 0a7610758c42 also). But
> > 'index' of it still contains revisions to be rollbacked at this
> > failure, and is referred by filtered changelog.
> > 
> > Therefore, even if there is no change between unfiltered
> > 'repo.changelog' at last caching and now, steps below makes rollbacked
> > revisions visible via filtered changelog unintentionally.
> > 
> >   1. instantiate unfiltered changelog as 'repo.changelog'
> >      (call it CL1)
> > 
> >   2. make filtered (= shallow copy of) CL1
> >      (call it FCL1)
> > 
> >   3. cache FCL1 with "the key" of CL1
> > 
> >   4. revisions are appended to 'index', which is shared by CL1 and FCL1
> > 
> >   5. invalidate 'repo.changelog' (= CL1) at failure of transaction
> > 
> >   6. instantiate 'repo.changelog' again at next operation
> >      (call it CL2)
> > 
> >      CL2 doesn't have revisions added at (4), because it is
> >      instantiated from '00changelog.i', which isn't changed while
> >      failed transaction.
> > 
> >   7. compare between "the key" of CL1 and CL2
> > 
> >   8. FCL1 cached at (3) is reused, because comparison at (7) doesn't
> >      detect change between CL1 at (1) and CL2
> > 
> >   9. revisions rollbacked at (5) are visible via FCL1 unintentionally,
> >      because FCL1 still refers 'index' changed at (4)
> > 
> > The root cause of this issue is that there is no examination about
> > validity of filtered changelog cache against current unfiltered one.
> > 
> > This patch discards filtered changelog if the length of it differs
> > from one of unfiltered.
> > 
> > "Difference of the length" means that filtered changelog isn't related
> > to unfiltered one, because the length of changelog isn't changed by
> > filtering.
> > 
> > This patch uses 'len(cl.index) - 1' directly instead of 'len(cl)', to
> > follow beda2c9dbbff, which bypasses changelog method for performance
> > reason.
> > 
> > BTW, at the time of this patch, redundant truncation of
> > '00changelog.i' at failure of transaction (see 0a7610758c42 for
> > detail) often prevents "hg serve" from making already rollbacked
> > revisions visible, because updating timestamps of '00changelog.i' by
> > truncation makes "hg serve" discard old repoview object with invalid
> > filtered changelog cache.
> > 
> > This is reason why this issue is overlooked before this patch, even
> > though test-bundle2-exchange.t has tests in similar situation: failure
> > of "hg push" via HTTP by pretxnclose hook on server side doesn't
> > prevent subsequent commands from looking up outgoing revisions
> > correctly.
> > 
> > But it can't be assumed that this avoidance always works as expected,
> > because timestamp on the filesystem doesn't have enough resolution for
> > recent computation power.
> > 
> > Therefore, without this patch, this issue may appear occasionally.
> > 
> > diff --git a/mercurial/repoview.py b/mercurial/repoview.py
> > --- a/mercurial/repoview.py
> > +++ b/mercurial/repoview.py
> > @@ -308,7 +308,8 @@ class repoview(object):
> >          revs = filterrevs(unfi, self.filtername)
> >          cl = self._clcache
> >          newkey = (unfilen, unfinode, hash(revs), unfichangelog._delayed)
> > -        if cl is not None and newkey != self._clcachekey:
> > +        if (cl is not None and
> > +            (len(cl.index) - 1 != unfilen or newkey != self._clcachekey)):
> 
> From your comment, this checks if cl.index is not unfiindex, right?

Oh, it looks better > 'if cl.index is not unfiindex'

BTW, it returns immediately without comparison of entries in index
buffer, if cl.index and unfiindex are separate objects, doesn't it ?
(I don't know well about fallbacking around __eq__ or so for C
implementation :-<)

> Instead of validating lengths, I think _clcache should be nullified when
> _filecache['changelog'] is deleted. But I'm not sure if it's possible by
> repoview.

Yes, it is ideal that invalidating changelog cache at failure of
transaction implies not only deleting _filecache['changelog'] but also
clearing _clcache of repoview.

But there is no reference from repo to repoview, and introducing it
(or registering a kind of callback to clear 'repoview._clcache' with
repo) seems to introduce also bad cyclic reference between them.


----------------------------------------------------------------------
[FUJIWARA Katsunori]                             foozy@lares.dti.ne.jp
Katsunori FUJIWARA - Dec. 20, 2015, 5:20 p.m.
At Mon, 21 Dec 2015 01:06:49 +0900,
FUJIWARA Katsunori wrote:
> 
> 
> At Sun, 20 Dec 2015 20:46:04 +0900,
> Yuya Nishihara wrote:
> > 
> > On Thu, 17 Dec 2015 02:50:51 +0900, FUJIWARA Katsunori wrote:
> > > # HG changeset patch
> > > # User FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
> > > # Date 1450287754 -32400
> > > #      Thu Dec 17 02:42:34 2015 +0900
> > > # Node ID 6455d3aac05ac9ab6913b162b46a924b9a512121
> > > # Parent  4210557127a602a91ce9958bb957e2a32493856b
> > > repoview: discard filtered changelog if the length differs from unfiltered
> > > 
> > > Before this patch, revisions rollbacked at failure of previous
> > > transaction may be visible at subsequent operations unintentionally,
> > > if repoview object is reused even after failure of transaction:
> > > e.g. command server and HTTP server are typical cases.
> > > 
> > > 'repoview' uses the tuple of values below of unfiltered changelog as
> > > "the key" to examine validity of filtered changelog cache.
> > > 
> > >      - length
> > >      - tip node
> > >      - filtered revisions (as hashed value)
> > >      - '_delayed' field
> > > 
> > > 'repoview' compares between "the key" of unfiltered changelog at
> > > previous caching and now, and reuses filtered changelog cache if no
> > > change is detected.
> > > 
> > > But this comparison indicates only that there is no change between
> > > unfiltered 'repo.changelog' at last caching and now, but not that
> > > filtered changelog cache is valid for current unfiltered one.
> > > 
> > > 'repoview' uses "shallow copy" of unfiltered changelog to create
> > > filtered changelog cache. In this case, 'index' buffer of unfiltered
> > > changelog is also referred by filtered changelog.
> > > 
> > > At failure of transaction, unfiltered changelog itself is invalidated
> > > (= un-referred) on the 'repo' side (see 0a7610758c42 also). But
> > > 'index' of it still contains revisions to be rollbacked at this
> > > failure, and is referred by filtered changelog.
> > > 
> > > Therefore, even if there is no change between unfiltered
> > > 'repo.changelog' at last caching and now, steps below makes rollbacked
> > > revisions visible via filtered changelog unintentionally.
> > > 
> > >   1. instantiate unfiltered changelog as 'repo.changelog'
> > >      (call it CL1)
> > > 
> > >   2. make filtered (= shallow copy of) CL1
> > >      (call it FCL1)
> > > 
> > >   3. cache FCL1 with "the key" of CL1
> > > 
> > >   4. revisions are appended to 'index', which is shared by CL1 and FCL1
> > > 
> > >   5. invalidate 'repo.changelog' (= CL1) at failure of transaction
> > > 
> > >   6. instantiate 'repo.changelog' again at next operation
> > >      (call it CL2)
> > > 
> > >      CL2 doesn't have revisions added at (4), because it is
> > >      instantiated from '00changelog.i', which isn't changed while
> > >      failed transaction.
> > > 
> > >   7. compare between "the key" of CL1 and CL2
> > > 
> > >   8. FCL1 cached at (3) is reused, because comparison at (7) doesn't
> > >      detect change between CL1 at (1) and CL2
> > > 
> > >   9. revisions rollbacked at (5) are visible via FCL1 unintentionally,
> > >      because FCL1 still refers 'index' changed at (4)
> > > 
> > > The root cause of this issue is that there is no examination about
> > > validity of filtered changelog cache against current unfiltered one.
> > > 
> > > This patch discards filtered changelog if the length of it differs
> > > from one of unfiltered.
> > > 
> > > "Difference of the length" means that filtered changelog isn't related
> > > to unfiltered one, because the length of changelog isn't changed by
> > > filtering.
> > > 
> > > This patch uses 'len(cl.index) - 1' directly instead of 'len(cl)', to
> > > follow beda2c9dbbff, which bypasses changelog method for performance
> > > reason.
> > > 
> > > BTW, at the time of this patch, redundant truncation of
> > > '00changelog.i' at failure of transaction (see 0a7610758c42 for
> > > detail) often prevents "hg serve" from making already rollbacked
> > > revisions visible, because updating timestamps of '00changelog.i' by
> > > truncation makes "hg serve" discard old repoview object with invalid
> > > filtered changelog cache.
> > > 
> > > This is reason why this issue is overlooked before this patch, even
> > > though test-bundle2-exchange.t has tests in similar situation: failure
> > > of "hg push" via HTTP by pretxnclose hook on server side doesn't
> > > prevent subsequent commands from looking up outgoing revisions
> > > correctly.
> > > 
> > > But it can't be assumed that this avoidance always works as expected,
> > > because timestamp on the filesystem doesn't have enough resolution for
> > > recent computation power.
> > > 
> > > Therefore, without this patch, this issue may appear occasionally.
> > > 
> > > diff --git a/mercurial/repoview.py b/mercurial/repoview.py
> > > --- a/mercurial/repoview.py
> > > +++ b/mercurial/repoview.py
> > > @@ -308,7 +308,8 @@ class repoview(object):
> > >          revs = filterrevs(unfi, self.filtername)
> > >          cl = self._clcache
> > >          newkey = (unfilen, unfinode, hash(revs), unfichangelog._delayed)
> > > -        if cl is not None and newkey != self._clcachekey:
> > > +        if (cl is not None and
> > > +            (len(cl.index) - 1 != unfilen or newkey != self._clcachekey)):
> > 
> > From your comment, this checks if cl.index is not unfiindex, right?
> 
> Oh, it looks better > 'if cl.index is not unfiindex'
> 
> BTW, it returns immediately without comparison of entries in index
> buffer, if cl.index and unfiindex are separate objects, doesn't it ?
> (I don't know well about fallbacking around __eq__ or so for C
> implementation :-<)

OK, I understood about difference between '==' and 'is'. This is my
first time to apply 'is'/'is not' on other than 'None' :-)

> > Instead of validating lengths, I think _clcache should be nullified when
> > _filecache['changelog'] is deleted. But I'm not sure if it's possible by
> > repoview.
> 
> Yes, it is ideal that invalidating changelog cache at failure of
> transaction implies not only deleting _filecache['changelog'] but also
> clearing _clcache of repoview.
> 
> But there is no reference from repo to repoview, and introducing it
> (or registering a kind of callback to clear 'repoview._clcache' with
> repo) seems to introduce also bad cyclic reference between them.
> 
> 
> ----------------------------------------------------------------------
> [FUJIWARA Katsunori]                             foozy@lares.dti.ne.jp
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@selenic.com
> https://selenic.com/mailman/listinfo/mercurial-devel

----------------------------------------------------------------------
[FUJIWARA Katsunori]                             foozy@lares.dti.ne.jp
Yuya Nishihara - Dec. 21, 2015, 2:05 p.m.
On Mon, 21 Dec 2015 02:20:37 +0900, FUJIWARA Katsunori wrote:
> At Mon, 21 Dec 2015 01:06:49 +0900,
> FUJIWARA Katsunori wrote:
> > At Sun, 20 Dec 2015 20:46:04 +0900,
> > Yuya Nishihara wrote:
> > > On Thu, 17 Dec 2015 02:50:51 +0900, FUJIWARA Katsunori wrote:
> > > > diff --git a/mercurial/repoview.py b/mercurial/repoview.py
> > > > --- a/mercurial/repoview.py
> > > > +++ b/mercurial/repoview.py
> > > > @@ -308,7 +308,8 @@ class repoview(object):
> > > >          revs = filterrevs(unfi, self.filtername)
> > > >          cl = self._clcache
> > > >          newkey = (unfilen, unfinode, hash(revs), unfichangelog._delayed)
> > > > -        if cl is not None and newkey != self._clcachekey:
> > > > +        if (cl is not None and
> > > > +            (len(cl.index) - 1 != unfilen or newkey != self._clcachekey)):
> > > 
> > > From your comment, this checks if cl.index is not unfiindex, right?
> > 
> > Oh, it looks better > 'if cl.index is not unfiindex'
> > 
> > BTW, it returns immediately without comparison of entries in index
> > buffer, if cl.index and unfiindex are separate objects, doesn't it ?
> > (I don't know well about fallbacking around __eq__ or so for C
> > implementation :-<)
> 
> OK, I understood about difference between '==' and 'is'. This is my
> first time to apply 'is'/'is not' on other than 'None' :-)

or id(unfiindex) might be used as a cache key. But that would be tricky anyway,
because what we want to know is if changelog was recreated or not.

> > > Instead of validating lengths, I think _clcache should be nullified when
> > > _filecache['changelog'] is deleted. But I'm not sure if it's possible by
> > > repoview.
> > 
> > Yes, it is ideal that invalidating changelog cache at failure of
> > transaction implies not only deleting _filecache['changelog'] but also
> > clearing _clcache of repoview.
> > 
> > But there is no reference from repo to repoview, and introducing it
> > (or registering a kind of callback to clear 'repoview._clcache' with
> > repo) seems to introduce also bad cyclic reference between them.

Patch

diff --git a/mercurial/repoview.py b/mercurial/repoview.py
--- a/mercurial/repoview.py
+++ b/mercurial/repoview.py
@@ -308,7 +308,8 @@  class repoview(object):
         revs = filterrevs(unfi, self.filtername)
         cl = self._clcache
         newkey = (unfilen, unfinode, hash(revs), unfichangelog._delayed)
-        if cl is not None and newkey != self._clcachekey:
+        if (cl is not None and
+            (len(cl.index) - 1 != unfilen or newkey != self._clcachekey)):
             cl = None
         # could have been made None by the previous if
         if cl is None:
diff --git a/tests/test-commandserver.t b/tests/test-commandserver.t
--- a/tests/test-commandserver.t
+++ b/tests/test-commandserver.t
@@ -725,3 +725,133 @@  unix domain socket:
   [255]
 
 #endif
+
+  $ cd ..
+
+Test that accessing to invalid changelog cache is avoided at
+subsequent operations even if repo object is reused even after failure
+of transaction (see 0a7610758c42 also)
+
+"hg log" after failure of transaction is needed to detect invalid
+cache in repoview: this can't detect by "hg verify" only.
+
+Combination of "finalization" and "empty-ness of changelog" (2 x 2 =
+4) are tested, because '00changelog.i' are differently changed in each
+cases.
+
+  $ cat > $TESTTMP/failafterfinalize.py <<EOF
+  > # extension to abort transaction after finalization forcibly
+  > from mercurial import commands, error, extensions, lock as lockmod
+  > def fail(tr):
+  >     raise error.Abort('fail after finalization')
+  > def reposetup(ui, repo):
+  >     class failrepo(repo.__class__):
+  >         def commitctx(self, ctx, error=False):
+  >             if self.ui.configbool('failafterfinalize', 'fail'):
+  >                 # 'sorted()' by ASCII code on category names causes
+  >                 # invoking 'fail' after finalization of changelog
+  >                 # using "'cl-%i' % id(self)" as category name
+  >                 self.currenttransaction().addfinalize('zzzzzzzz', fail)
+  >             return super(failrepo, self).commitctx(ctx, error)
+  >     repo.__class__ = failrepo
+  > EOF
+
+  $ hg init repo3
+  $ cd repo3
+
+  $ cat <<EOF >> $HGRCPATH
+  > [ui]
+  > logtemplate = {rev} {desc|firstline} ({files})\n
+  > 
+  > [extensions]
+  > failafterfinalize = $TESTTMP/failafterfinalize.py
+  > EOF
+
+- test failure with "empty changelog"
+
+  $ echo foo > foo
+  $ hg add foo
+
+(failuer before finalization)
+
+  >>> from hgclient import readchannel, runcommand, check
+  >>> @check
+  ... def abort(server):
+  ...     readchannel(server)
+  ...     runcommand(server, ['commit',
+  ...                         '--config', 'hooks.pretxncommit=false',
+  ...                         '-mfoo'])
+  ...     runcommand(server, ['log'])
+  ...     runcommand(server, ['verify', '-q'])
+  *** runcommand commit --config hooks.pretxncommit=false -mfoo
+  transaction abort!
+  rollback completed
+  abort: pretxncommit hook exited with status 1
+   [255]
+  *** runcommand log
+  *** runcommand verify -q
+
+(failuer after finalization)
+
+  >>> from hgclient import readchannel, runcommand, check
+  >>> @check
+  ... def abort(server):
+  ...     readchannel(server)
+  ...     runcommand(server, ['commit',
+  ...                         '--config', 'failafterfinalize.fail=true',
+  ...                         '-mfoo'])
+  ...     runcommand(server, ['log'])
+  ...     runcommand(server, ['verify', '-q'])
+  *** runcommand commit --config failafterfinalize.fail=true -mfoo
+  transaction abort!
+  rollback completed
+  abort: fail after finalization
+   [255]
+  *** runcommand log
+  *** runcommand verify -q
+
+- test failure with "not-empty changelog"
+
+  $ echo bar > bar
+  $ hg add bar
+  $ hg commit -mbar bar
+
+(failure before finalization)
+
+  >>> from hgclient import readchannel, runcommand, check
+  >>> @check
+  ... def abort(server):
+  ...     readchannel(server)
+  ...     runcommand(server, ['commit',
+  ...                         '--config', 'hooks.pretxncommit=false',
+  ...                         '-mfoo', 'foo'])
+  ...     runcommand(server, ['log'])
+  ...     runcommand(server, ['verify', '-q'])
+  *** runcommand commit --config hooks.pretxncommit=false -mfoo foo
+  transaction abort!
+  rollback completed
+  abort: pretxncommit hook exited with status 1
+   [255]
+  *** runcommand log
+  0 bar (bar)
+  *** runcommand verify -q
+
+(failure after finalization)
+
+  >>> from hgclient import readchannel, runcommand, check
+  >>> @check
+  ... def abort(server):
+  ...     readchannel(server)
+  ...     runcommand(server, ['commit',
+  ...                         '--config', 'failafterfinalize.fail=true',
+  ...                         '-mfoo', 'foo'])
+  ...     runcommand(server, ['log'])
+  ...     runcommand(server, ['verify', '-q'])
+  *** runcommand commit --config failafterfinalize.fail=true -mfoo foo
+  transaction abort!
+  rollback completed
+  abort: fail after finalization
+   [255]
+  *** runcommand log
+  0 bar (bar)
+  *** runcommand verify -q