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