[PATCH V3] transaction: allow running file generators after finalizers
Augie Fackler
raf at durin42.com
Fri Apr 8 21:52:19 EDT 2016
On Thu, Apr 07, 2016 at 02:11:41PM -0700, Durham Goode wrote:
> # HG changeset patch
> # User Durham Goode <durham at fb.com>
> # Date 1460063449 25200
> # Thu Apr 07 14:10:49 2016 -0700
> # Node ID 5e49537579979f11268847526f83bb9dc2baf8d8
> # Parent ea86cdcd9b50bf38c6b9dd7bbaa04b9c8cc0aefb
> transaction: allow running file generators after finalizers
>
queued, thanks
> Previously, transaction.close would run the file generators before running the
> finalizers (see the list below for what is in each). Since file generators
> contain the bookmarks and the dirstate, this meant we made the dirstate and
> bookmarks visible to external readers before we actually wrote the commits into
> the changelog, which could result in missing bookmarks and missing working copy
> parents (especially on servers with high commit throughput, since pulls might
> fail to see certain bookmarks in this situation).
>
> By moving the changelog writing to be before the bookmark/dirstate writing, we
> ensure the commits are present before they are referenced.
>
> This implementation allows certain file generators to be after the finalizers.
> We didn't want to move all of the generators, since it's important that things
> like phases actually run before the finalizers (otherwise you could expose
> commits as public when they really shouldn't be).
>
> For reference, file generators currently consist of: bookmarks, dirstate, and
> phases. Finalizers currently consist of: changelog, revbranchcache, and fncache.
>
> diff --git a/mercurial/transaction.py b/mercurial/transaction.py
> --- a/mercurial/transaction.py
> +++ b/mercurial/transaction.py
> @@ -23,6 +23,19 @@ from . import (
>
> version = 2
>
> +# These are the file generators that should only be executed after the
> +# finalizers are done, since they rely on the output of the finalizers (like
> +# the changelog having been written).
> +postfinalizegenerators = set([
> + 'bookmarks',
> + 'dirstate'
> +])
> +
> +class GenerationGroup(object):
> + ALL='all'
> + PREFINALIZE='prefinalize'
> + POSTFINALIZE='postfinalize'
> +
> def active(func):
> def _active(self, *args, **kwds):
> if self.count == 0:
> @@ -276,12 +289,19 @@ class transaction(object):
> # but for bookmarks that are handled outside this mechanism.
> self._filegenerators[genid] = (order, filenames, genfunc, location)
>
> - def _generatefiles(self, suffix=''):
> + def _generatefiles(self, suffix='', group=GenerationGroup.ALL):
> # write files registered for generation
> any = False
> - for entry in sorted(self._filegenerators.values()):
> + for id, entry in sorted(self._filegenerators.iteritems()):
> any = True
> order, filenames, genfunc, location = entry
> +
> + # for generation at closing, check if it's before or after finalize
> + postfinalize = group == GenerationGroup.POSTFINALIZE
> + if (group != GenerationGroup.ALL and
> + (id in postfinalizegenerators) != (postfinalize)):
> + continue
> +
> vfs = self._vfsmap[location]
> files = []
> try:
> @@ -407,10 +427,11 @@ class transaction(object):
> '''commit the transaction'''
> if self.count == 1:
> self.validator(self) # will raise exception if needed
> - self._generatefiles()
> + self._generatefiles(group=GenerationGroup.PREFINALIZE)
> categories = sorted(self._finalizecallback)
> for cat in categories:
> self._finalizecallback[cat](self)
> + self._generatefiles(group=GenerationGroup.POSTFINALIZE)
>
> self.count -= 1
> if self.count != 0:
> diff --git a/tests/test-bookmarks.t b/tests/test-bookmarks.t
> --- a/tests/test-bookmarks.t
> +++ b/tests/test-bookmarks.t
> @@ -846,3 +846,52 @@ updates the working directory and curren
> 6:81dcce76aa0b
> $ hg -R ../cloned-bookmarks-update bookmarks | grep ' Y '
> * Y 6:81dcce76aa0b
> +
> + $ cd ..
> +
> +ensure changelog is written before bookmarks
> + $ hg init orderrepo
> + $ cd orderrepo
> + $ touch a
> + $ hg commit -Aqm one
> + $ hg book mybook
> + $ echo a > a
> +
> + $ cat > $TESTTMP/pausefinalize.py <<EOF
> + > from mercurial import extensions, localrepo
> + > import os, time
> + > def transaction(orig, self, desc, report=None):
> + > tr = orig(self, desc, report)
> + > def sleep(*args, **kwargs):
> + > retry = 20
> + > while retry > 0 and not os.path.exists("$TESTTMP/unpause"):
> + > retry -= 1
> + > time.sleep(0.5)
> + > if os.path.exists("$TESTTMP/unpause"):
> + > os.remove("$TESTTMP/unpause")
> + > # It is important that this finalizer start with 'a', so it runs before
> + > # the changelog finalizer appends to the changelog.
> + > tr.addfinalize('a-sleep', sleep)
> + > return tr
> + >
> + > def extsetup(ui):
> + > # This extension inserts an artifical pause during the transaction
> + > # finalizer, so we can run commands mid-transaction-close.
> + > extensions.wrapfunction(localrepo.localrepository, 'transaction',
> + > transaction)
> + > EOF
> + $ hg commit -qm two --config extensions.pausefinalize=$TESTTMP/pausefinalize.py &
> + $ sleep 2
> + $ hg log -r .
> + changeset: 0:867bc5792c8c
> + bookmark: mybook
> + tag: tip
> + user: test
> + date: Thu Jan 01 00:00:00 1970 +0000
> + summary: one
> +
> + $ hg bookmarks
> + * mybook 0:867bc5792c8c
> + $ touch $TESTTMP/unpause
> +
> + $ cd ..
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel at mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
More information about the Mercurial-devel
mailing list