Mercurial > public > mercurial-scm > hg
comparison mercurial/scmutil.py @ 40424:7caf632e30c3
filecache: unimplement __set__() and __delete__() (API)
Implementing __set__() implies that the descriptor can't be overridden by
obj.__dict__, which means any property access involves slow function call.
"Data descriptors with __set__() and __get__() defined always override
a redefinition in an instance dictionary. In contrast, non-data descriptors
can be overridden by instances."
https://docs.python.org/2.7/reference/datamodel.html#invoking-descriptors
This patch basically backs out 236bb604dc39, "scmutil: update cached copy
when filecached attribute is assigned (issue3263)." The problem described
in issue3263 (which is #3264 in Bugzilla) should no longer happen since
repo._bookmarkcurrent has been moved to repo._bookmarks.active. We still
have a risk of introducing similar bugs, but I think that's the cost we
have to pay.
$ hg perfrevset 'branch(tip)' -R mercurial
(orig) wall 0.139511 comb 0.140000 user 0.140000 sys 0.000000 (best of 66)
(prev) wall 0.114195 comb 0.110000 user 0.110000 sys 0.000000 (best of 81)
(this) wall 0.099038 comb 0.110000 user 0.100000 sys 0.010000 (best of 93)
author | Yuya Nishihara <yuya@tcha.org> |
---|---|
date | Sat, 20 Oct 2018 17:56:00 +0900 |
parents | 597bb5a6867f |
children | acd17caa699a |
comparison
equal
deleted
inserted
replaced
40423:597bb5a6867f | 40424:7caf632e30c3 |
---|---|
1247 | 1247 |
1248 On first access, the files defined as arguments are stat()ed and the | 1248 On first access, the files defined as arguments are stat()ed and the |
1249 results cached. The decorated function is called. The results are stashed | 1249 results cached. The decorated function is called. The results are stashed |
1250 away in a ``_filecache`` dict on the object whose method is decorated. | 1250 away in a ``_filecache`` dict on the object whose method is decorated. |
1251 | 1251 |
1252 On subsequent access, the cached result is returned. | 1252 On subsequent access, the cached result is used as it is set to the |
1253 | 1253 instance dictionary. |
1254 On external property set operations, stat() calls are performed and the new | 1254 |
1255 value is cached. | 1255 On external property set/delete operations, the caller must update the |
1256 | 1256 corresponding _filecache entry appropriately. Use __class__.<attr>.set() |
1257 On property delete operations, cached data is removed. | 1257 instead of directly setting <attr>. |
1258 | 1258 |
1259 When using the property API, cached data is always returned, if available: | 1259 When using the property API, the cached data is always used if available. |
1260 no stat() is performed to check if the file has changed and if the function | 1260 No stat() is performed to check if the file has changed. |
1261 needs to be called to reflect file changes. | |
1262 | 1261 |
1263 Others can muck about with the state of the ``_filecache`` dict. e.g. they | 1262 Others can muck about with the state of the ``_filecache`` dict. e.g. they |
1264 can populate an entry before the property's getter is called. In this case, | 1263 can populate an entry before the property's getter is called. In this case, |
1265 entries in ``_filecache`` will be used during property operations, | 1264 entries in ``_filecache`` will be used during property operations, |
1266 if available. If the underlying file changes, it is up to external callers | 1265 if available. If the underlying file changes, it is up to external callers |
1289 | 1288 |
1290 def __get__(self, obj, type=None): | 1289 def __get__(self, obj, type=None): |
1291 # if accessed on the class, return the descriptor itself. | 1290 # if accessed on the class, return the descriptor itself. |
1292 if obj is None: | 1291 if obj is None: |
1293 return self | 1292 return self |
1294 # do we need to check if the file changed? | 1293 |
1295 try: | 1294 assert self.sname not in obj.__dict__ |
1296 return obj.__dict__[self.sname] | |
1297 except KeyError: | |
1298 pass | |
1299 | 1295 |
1300 entry = obj._filecache.get(self.name) | 1296 entry = obj._filecache.get(self.name) |
1301 | 1297 |
1302 if entry: | 1298 if entry: |
1303 if entry.changed(): | 1299 if entry.changed(): |
1313 obj._filecache[self.name] = entry | 1309 obj._filecache[self.name] = entry |
1314 | 1310 |
1315 obj.__dict__[self.sname] = entry.obj | 1311 obj.__dict__[self.sname] = entry.obj |
1316 return entry.obj | 1312 return entry.obj |
1317 | 1313 |
1318 def __set__(self, obj, value): | 1314 # don't implement __set__(), which would make __dict__ lookup as slow as |
1315 # function call. | |
1316 | |
1317 def set(self, obj, value): | |
1319 if self.name not in obj._filecache: | 1318 if self.name not in obj._filecache: |
1320 # we add an entry for the missing value because X in __dict__ | 1319 # we add an entry for the missing value because X in __dict__ |
1321 # implies X in _filecache | 1320 # implies X in _filecache |
1322 paths = [self.join(obj, path) for path in self.paths] | 1321 paths = [self.join(obj, path) for path in self.paths] |
1323 ce = filecacheentry(paths, False) | 1322 ce = filecacheentry(paths, False) |
1325 else: | 1324 else: |
1326 ce = obj._filecache[self.name] | 1325 ce = obj._filecache[self.name] |
1327 | 1326 |
1328 ce.obj = value # update cached copy | 1327 ce.obj = value # update cached copy |
1329 obj.__dict__[self.sname] = value # update copy returned by obj.x | 1328 obj.__dict__[self.sname] = value # update copy returned by obj.x |
1330 | |
1331 def __delete__(self, obj): | |
1332 try: | |
1333 del obj.__dict__[self.sname] | |
1334 except KeyError: | |
1335 raise AttributeError(self.sname) | |
1336 | 1329 |
1337 def extdatasource(repo, source): | 1330 def extdatasource(repo, source): |
1338 """Gather a map of rev -> value dict from the specified source | 1331 """Gather a map of rev -> value dict from the specified source |
1339 | 1332 |
1340 A source spec is treated as a URL, with a special case shell: type | 1333 A source spec is treated as a URL, with a special case shell: type |