Show More
@@ -1157,6 +1157,7 b' coreconfigitem(' | |||||
1157 | # - for censoring operation |
|
1157 | # - for censoring operation | |
1158 | # - for stripping operation |
|
1158 | # - for stripping operation | |
1159 | # - for rollback operation |
|
1159 | # - for rollback operation | |
|
1160 | # * proper streaming (race free) of the docket file | |||
1160 | # * store the data size in the docket to simplify sidedata rewrite. |
|
1161 | # * store the data size in the docket to simplify sidedata rewrite. | |
1161 | # * track garbage data to evemtually allow rewriting -existing- sidedata. |
|
1162 | # * track garbage data to evemtually allow rewriting -existing- sidedata. | |
1162 | # * Exchange-wise, we will also need to do something more efficient than |
|
1163 | # * Exchange-wise, we will also need to do something more efficient than |
@@ -739,6 +739,8 b' def makelocalrepository(baseui, path, in' | |||||
739 |
|
739 | |||
740 | if requirementsmod.REVLOGV2_REQUIREMENT in requirements: |
|
740 | if requirementsmod.REVLOGV2_REQUIREMENT in requirements: | |
741 | features.add(repository.REPO_FEATURE_SIDE_DATA) |
|
741 | features.add(repository.REPO_FEATURE_SIDE_DATA) | |
|
742 | # the revlogv2 docket introduced race condition that we need to fix | |||
|
743 | features.discard(repository.REPO_FEATURE_STREAM_CLONE) | |||
742 |
|
744 | |||
743 | # The cache vfs is used to manage cache files. |
|
745 | # The cache vfs is used to manage cache files. | |
744 | cachevfs = vfsmod.vfs(cachepath, cacheaudited=True) |
|
746 | cachevfs = vfsmod.vfs(cachepath, cacheaudited=True) |
@@ -453,7 +453,7 b' class revlog(object):' | |||||
453 | force_nodemap = opts.get(b'devel-force-nodemap', False) |
|
453 | force_nodemap = opts.get(b'devel-force-nodemap', False) | |
454 | return new_header, mmapindexthreshold, force_nodemap |
|
454 | return new_header, mmapindexthreshold, force_nodemap | |
455 |
|
455 | |||
456 | def _get_data(self, filepath, mmap_threshold): |
|
456 | def _get_data(self, filepath, mmap_threshold, size=None): | |
457 | """return a file content with or without mmap |
|
457 | """return a file content with or without mmap | |
458 |
|
458 | |||
459 | If the file is missing return the empty string""" |
|
459 | If the file is missing return the empty string""" | |
@@ -462,10 +462,19 b' class revlog(object):' | |||||
462 | if mmap_threshold is not None: |
|
462 | if mmap_threshold is not None: | |
463 | file_size = self.opener.fstat(fp).st_size |
|
463 | file_size = self.opener.fstat(fp).st_size | |
464 | if file_size >= mmap_threshold: |
|
464 | if file_size >= mmap_threshold: | |
|
465 | if size is not None: | |||
|
466 | # avoid potentiel mmap crash | |||
|
467 | size = min(file_size, size) | |||
465 | # TODO: should .close() to release resources without |
|
468 | # TODO: should .close() to release resources without | |
466 | # relying on Python GC |
|
469 | # relying on Python GC | |
|
470 | if size is None: | |||
467 | return util.buffer(util.mmapread(fp)) |
|
471 | return util.buffer(util.mmapread(fp)) | |
|
472 | else: | |||
|
473 | return util.buffer(util.mmapread(fp, size)) | |||
|
474 | if size is None: | |||
468 | return fp.read() |
|
475 | return fp.read() | |
|
476 | else: | |||
|
477 | return fp.read(size) | |||
469 | except IOError as inst: |
|
478 | except IOError as inst: | |
470 | if inst.errno != errno.ENOENT: |
|
479 | if inst.errno != errno.ENOENT: | |
471 | raise |
|
480 | raise | |
@@ -518,7 +527,17 b' class revlog(object):' | |||||
518 | else: |
|
527 | else: | |
519 | self._docket = docketutil.parse_docket(self, entry_data) |
|
528 | self._docket = docketutil.parse_docket(self, entry_data) | |
520 | self._indexfile = self._docket.index_filepath() |
|
529 | self._indexfile = self._docket.index_filepath() | |
521 | index_data = self._get_data(self._indexfile, mmapindexthreshold) |
|
530 | index_data = b'' | |
|
531 | index_size = self._docket.index_end | |||
|
532 | if index_size > 0: | |||
|
533 | index_data = self._get_data( | |||
|
534 | self._indexfile, mmapindexthreshold, size=index_size | |||
|
535 | ) | |||
|
536 | if len(index_data) < index_size: | |||
|
537 | msg = _(b'too few index data for %s: got %d, expected %d') | |||
|
538 | msg %= (self.display_id, len(index_data), index_size) | |||
|
539 | raise error.RevlogError(msg) | |||
|
540 | ||||
522 | self._inline = False |
|
541 | self._inline = False | |
523 | # generaldelta implied by version 2 revlogs. |
|
542 | # generaldelta implied by version 2 revlogs. | |
524 | self._generaldelta = True |
|
543 | self._generaldelta = True | |
@@ -619,7 +638,10 b' class revlog(object):' | |||||
619 | f = self.opener( |
|
638 | f = self.opener( | |
620 | self._indexfile, mode=b"r+", checkambig=self._checkambig |
|
639 | self._indexfile, mode=b"r+", checkambig=self._checkambig | |
621 | ) |
|
640 | ) | |
|
641 | if self._docket is None: | |||
622 | f.seek(0, os.SEEK_END) |
|
642 | f.seek(0, os.SEEK_END) | |
|
643 | else: | |||
|
644 | f.seek(self._docket.index_end, os.SEEK_SET) | |||
623 | return f |
|
645 | return f | |
624 | except IOError as inst: |
|
646 | except IOError as inst: | |
625 | if inst.errno != errno.ENOENT: |
|
647 | if inst.errno != errno.ENOENT: | |
@@ -2022,6 +2044,8 b' class revlog(object):' | |||||
2022 | header = self.index.pack_header(header) |
|
2044 | header = self.index.pack_header(header) | |
2023 | e = header + e |
|
2045 | e = header + e | |
2024 | fp.write(e) |
|
2046 | fp.write(e) | |
|
2047 | if self._docket is not None: | |||
|
2048 | self._docket.index_end = fp.tell() | |||
2025 | # the temp file replace the real index when we exit the context |
|
2049 | # the temp file replace the real index when we exit the context | |
2026 | # manager |
|
2050 | # manager | |
2027 |
|
2051 | |||
@@ -2440,7 +2464,10 b' class revlog(object):' | |||||
2440 | msg = b'adding revision outside `revlog._writing` context' |
|
2464 | msg = b'adding revision outside `revlog._writing` context' | |
2441 | raise error.ProgrammingError(msg) |
|
2465 | raise error.ProgrammingError(msg) | |
2442 | ifh, dfh = self._writinghandles |
|
2466 | ifh, dfh = self._writinghandles | |
|
2467 | if self._docket is None: | |||
2443 | ifh.seek(0, os.SEEK_END) |
|
2468 | ifh.seek(0, os.SEEK_END) | |
|
2469 | else: | |||
|
2470 | ifh.seek(self._docket.index_end, os.SEEK_SET) | |||
2444 | if dfh: |
|
2471 | if dfh: | |
2445 | dfh.seek(0, os.SEEK_END) |
|
2472 | dfh.seek(0, os.SEEK_END) | |
2446 |
|
2473 | |||
@@ -2463,6 +2490,9 b' class revlog(object):' | |||||
2463 | if sidedata: |
|
2490 | if sidedata: | |
2464 | ifh.write(sidedata) |
|
2491 | ifh.write(sidedata) | |
2465 | self._enforceinlinesize(transaction) |
|
2492 | self._enforceinlinesize(transaction) | |
|
2493 | if self._docket is not None: | |||
|
2494 | self._docket.index_end = self._writinghandles[0].tell() | |||
|
2495 | ||||
2466 | nodemaputil.setup_persistent_nodemap(transaction, self) |
|
2496 | nodemaputil.setup_persistent_nodemap(transaction, self) | |
2467 |
|
2497 | |||
2468 | def addgroup( |
|
2498 | def addgroup( | |
@@ -2632,6 +2662,11 b' class revlog(object):' | |||||
2632 | end += rev * self.index.entry_size |
|
2662 | end += rev * self.index.entry_size | |
2633 |
|
2663 | |||
2634 | transaction.add(self._indexfile, end) |
|
2664 | transaction.add(self._indexfile, end) | |
|
2665 | if self._docket is not None: | |||
|
2666 | # XXX we could, leverage the docket while stripping. However it is | |||
|
2667 | # not powerfull enough at the time of this comment | |||
|
2668 | self._docket.index_end = end | |||
|
2669 | self._docket.write(transaction, stripping=True) | |||
2635 |
|
2670 | |||
2636 | # then reset internal state in memory to forget those revisions |
|
2671 | # then reset internal state in memory to forget those revisions | |
2637 | self._revisioncache = None |
|
2672 | self._revisioncache = None |
@@ -28,36 +28,55 b' from . import (' | |||||
28 | # * 4 bytes: revlog version |
|
28 | # * 4 bytes: revlog version | |
29 | # | This is mandatory as docket must be compatible with the previous |
|
29 | # | This is mandatory as docket must be compatible with the previous | |
30 | # | revlog index header. |
|
30 | # | revlog index header. | |
31 | S_HEADER = struct.Struct(constants.INDEX_HEADER.format) |
|
31 | # * 8 bytes: size of index data | |
|
32 | S_HEADER = struct.Struct(constants.INDEX_HEADER.format + 'L') | |||
32 |
|
33 | |||
33 |
|
34 | |||
34 | class RevlogDocket(object): |
|
35 | class RevlogDocket(object): | |
35 | """metadata associated with revlog""" |
|
36 | """metadata associated with revlog""" | |
36 |
|
37 | |||
37 | def __init__(self, revlog, version_header=None): |
|
38 | def __init__(self, revlog, version_header=None, index_end=0): | |
38 | self._version_header = version_header |
|
39 | self._version_header = version_header | |
39 | self._dirty = False |
|
40 | self._dirty = False | |
40 | self._radix = revlog.radix |
|
41 | self._radix = revlog.radix | |
41 | self._path = revlog._docket_file |
|
42 | self._path = revlog._docket_file | |
42 | self._opener = revlog.opener |
|
43 | self._opener = revlog.opener | |
|
44 | self._index_end = index_end | |||
43 |
|
45 | |||
44 | def index_filepath(self): |
|
46 | def index_filepath(self): | |
45 | """file path to the current index file associated to this docket""" |
|
47 | """file path to the current index file associated to this docket""" | |
46 | # very simplistic version at first |
|
48 | # very simplistic version at first | |
47 | return b"%s.idx" % self._radix |
|
49 | return b"%s.idx" % self._radix | |
48 |
|
50 | |||
49 | def write(self, transaction): |
|
51 | @property | |
|
52 | def index_end(self): | |||
|
53 | return self._index_end | |||
|
54 | ||||
|
55 | @index_end.setter | |||
|
56 | def index_end(self, new_size): | |||
|
57 | if new_size != self._index_end: | |||
|
58 | self._index_end = new_size | |||
|
59 | self._dirty = True | |||
|
60 | ||||
|
61 | def write(self, transaction, stripping=False): | |||
50 | """write the modification of disk if any |
|
62 | """write the modification of disk if any | |
51 |
|
63 | |||
52 | This make the new content visible to all process""" |
|
64 | This make the new content visible to all process""" | |
53 | if self._dirty: |
|
65 | if self._dirty: | |
|
66 | if not stripping: | |||
|
67 | # XXX we could, leverage the docket while stripping. However it | |||
|
68 | # is not powerfull enough at the time of this comment | |||
54 | transaction.addbackup(self._path, location=b'store') |
|
69 | transaction.addbackup(self._path, location=b'store') | |
55 | with self._opener(self._path, mode=b'w', atomictemp=True) as f: |
|
70 | with self._opener(self._path, mode=b'w', atomictemp=True) as f: | |
56 | f.write(self._serialize()) |
|
71 | f.write(self._serialize()) | |
57 | self._dirty = False |
|
72 | self._dirty = False | |
58 |
|
73 | |||
59 | def _serialize(self): |
|
74 | def _serialize(self): | |
60 | return S_HEADER.pack(self._version_header) |
|
75 | data = ( | |
|
76 | self._version_header, | |||
|
77 | self._index_end, | |||
|
78 | ) | |||
|
79 | return S_HEADER.pack(*data) | |||
61 |
|
80 | |||
62 |
|
81 | |||
63 | def default_docket(revlog, version_header): |
|
82 | def default_docket(revlog, version_header): | |
@@ -72,9 +91,10 b' def default_docket(revlog, version_heade' | |||||
72 | def parse_docket(revlog, data): |
|
91 | def parse_docket(revlog, data): | |
73 | """given some docket data return a docket object for the given revlog""" |
|
92 | """given some docket data return a docket object for the given revlog""" | |
74 | header = S_HEADER.unpack(data[: S_HEADER.size]) |
|
93 | header = S_HEADER.unpack(data[: S_HEADER.size]) | |
75 |
|
|
94 | version_header, index_size = header | |
76 | docket = RevlogDocket( |
|
95 | docket = RevlogDocket( | |
77 | revlog, |
|
96 | revlog, | |
78 | version_header=version_header, |
|
97 | version_header=version_header, | |
|
98 | index_end=index_size, | |||
79 | ) |
|
99 | ) | |
80 | return docket |
|
100 | return docket |
General Comments 0
You need to be logged in to leave comments.
Login now