Re: Commit Hooks Asynchronous?
From: Trent Nelson <trent_at_snakebite.org>
Date: Fri, 3 Feb 2012 19:51:49 -0500
On Fri, Feb 03, 2012 at 04:11:56PM -0800, Cory Finger wrote:
There's no way of doing that out-of-the-box, as far as I know.
You *can* manually enforce sequential access by blocking from within
I've got a hook framework* that analyzes commits; pre-commits are
The revprop metadata is inherently sequential; r6 depends on r5,
The end result was masses of ugly knee-jerk locking code, which I've
[*]: framework is called 'Enversion' (Enterprise Subversion). I'm in
Regards,
Trent.
Hideous locking code below. Note that this isn't indicative of the
--
def _init_evn(self):
self.is_rev_for_empty_repo = self.is_rev and self.rev == 0
self.is_rev_for_first_commit = self.is_rev and self.rev == 1
self.is_txn_for_first_commit = self.is_txn and self.base_rev == 0
self.is_normal_rev_or_txn = (
(self.is_txn and self.base_rev >= 1) or
(self.is_rev and self.rev > 1)
)
# Test mutually-exclusive invariants.
assert (
(self.is_rev_for_empty_repo and (
not self.is_txn_for_first_commit and
not self.is_rev_for_first_commit and
not self.is_normal_rev_or_txn
)) or (self.is_rev_for_first_commit and (
not self.is_rev_for_empty_repo and
not self.is_txn_for_first_commit and
not self.is_normal_rev_or_txn
)) or (self.is_txn_for_first_commit and (
not self.is_rev_for_empty_repo and
not self.is_rev_for_first_commit and
not self.is_normal_rev_or_txn
)) or (self.is_normal_rev_or_txn and (
not self.is_rev_for_empty_repo and
not self.is_rev_for_first_commit and
not self.is_txn_for_first_commit
))
)
rc0 = self.r0_revprop_conf
if self.is_rev_for_empty_repo and 'version' not in rc0:
rc0.last_rev = 0
rc0.version = ESVN_VERSION
version = self._load_evn_revprop_int('version', 1)
if version != ESVN_VERSION:
self.die(e.VersionMismatch % (ESVN_VERSION, version))
if version == 1:
self._init_evn_v1()
else:
raise UnexpectedCodePath
def _init_evn_v1(self):
if self.is_rev_for_empty_repo:
return
if self.is_txn_for_first_commit or self.is_rev_for_first_commit:
assert self.base_rev == 0
k = Dict()
if self.is_txn:
k.base_rev = 0
else:
k.rev = 1
c = self.rconf(**k)
self.__roots = (c, {})
return
assert self.base_rev > 0
if self.is_rev:
assert self.rev >= 2
else:
assert self.base_rev >= 1
max_revlock_waits = self.conf.get('general', 'max-revlock-waits')
# Quick sanity check of last_rev to make sure it's not higher than the
# highest rev of the repository.
self._reload_last_rev()
highest_rev = svn.fs.youngest_rev(self.fs, self.pool)
if self.last_rev > highest_rev:
self.die(e.LastRevTooHigh % (self.last_rev, highest_rev))
if self.is_rev and isinstance(self, RepositoryHook):
with open(self.rev_lockfile, 'w') as f:
f.write(str(os.getpid()))
f.flush()
f.close()
if self.good_last_rev and self.good_base_rev_roots:
self._last_rev_and_base_rev_roots_are_good()
return
dbg = self.log.debug
a = (str(self.rev_or_txn), self.base_rev, self.last_rev)
dbg('rev_or_txn: %s, base_rev: %d, last_rev: %d' % a)
found = False
fn = self.base_rev_lockfile
count = itertools.count()
while True:
c = count.next()
dbg('looking for revlock file: %s (attempt: %d)' % (fn, c))
if os.path.isfile(fn):
found = True
dbg('found revlock after %d attempts' % c)
break
time.sleep(1)
if c == max_revlock_waits:
dbg('no revlock found after %d attempts' % c)
break
if self.good_last_rev and self.good_base_rev_roots:
self._last_rev_and_base_rev_roots_are_good()
return
if found:
s = None
try:
s = open(fn, 'r').read()
except:
pass
if not s:
dbg('failed to open/read lock file %s' % fn)
else:
try:
pid = int(s)
dbg("pid for lock file %s: %d" % (fn, pid))
except:
dbg("invalid pid for lock file %s: %s" % (fn, s))
else:
count = itertools.count()
still_running = pid_exists(pid)
while still_running:
a = (count.next(), pid)
dbg("[%d] pid %d is still running" % a)
time.sleep(1)
still_running = pid_exists(pid)
good_last_rev = self.good_last_rev
good_base_rev_roots = self.good_base_rev_roots
if good_last_rev and good_base_rev_roots:
self._last_rev_and_base_rev_roots_are_good()
return
if not good_last_rev:
self._last_rev_is_bad()
if not good_base_rev_roots:
self._base_rev_roots_is_bad()
raise UnexpectedCodePath
@property
def good_last_rev(self):
self._reload_last_rev()
if isinstance(self, RepositoryHook):
return bool(self.last_rev == self.base_rev)
else:
assert self.is_rev
return bool(self.last_rev >= self.base_rev)
@property
def good_base_rev_roots(self):
brc = self.base_revprop_conf
brc._reload()
return bool(isinstance(brc.roots, Roots))
def _last_rev_is_bad(self):
if self.is_rev:
a = (self.rev, self.base_rev, self.last_rev, self.latest_rev)
if isinstance(self, RepositoryHook):
m = e.LastRevNotSetToBaseRevDuringPostCommit
else:
m = e.OutOfOrderRevisionProcessingAttempt
else:
assert self.is_txn
a = (self.base_rev, self.last_rev, self.latest_rev)
if self.base_rev < self.last_rev:
m = e.StaleTxnProbablyDueToHighLoad
else:
m = e.RepositoryOutOfSyncTxn
self.die(m % a)
def _base_rev_roots_is_bad(self):
if self.is_rev:
a = (self.rev, self.base_rev, self.last_rev, self.latest_rev)
if isinstance(self, RepositoryHook):
m = e.RootsMissingFromBaseRevDuringPostCommit
else:
m = e.InvalidRootsForRev
self.die(m % a)
else:
assert self.is_txn
a = (self.txn_name, self.base_rev, self.last_rev, self.latest_rev)
m = e.RootsMissingFromBaseRevTxn
self.die(m % a)
def _last_rev_and_base_rev_roots_are_good(self):
try:
os.unlink(self.base_rev_lockfile)
except:
pass
brc = self.base_revprop_conf
assert isinstance(brc.roots, Roots)
if self.is_txn:
self.__roots = (brc, brc.roots)
return
assert self.is_rev
rc = self.revprop_conf
# XXX force override for now.
self.__roots = (rc, self._inherit_roots(brc.roots))
return
if rc.roots is not None:
# We must be re-processing an already-processed revision.
assert isinstance(rc.roots, Roots)
self.__roots = (rc, dict(rc.roots))
else:
# Processing a revision for the first time outside the context
# of a post-commit hook, i.e. repo is being analysed. As with
# the post-commit processing logic above, we bring over a sim-
# plified version of the base_rev's roots via _inherit_roots.
self.__roots = (rc, self._inherit_roots(brc.roots))
Received on 2012-02-04 01:52:24 CET
|
This is an archived mail posted to the Subversion Dev mailing list.
This site is subject to the Apache Privacy Policy and the Apache Public Forum Archive Policy.