1 <?xml version=
"1.0" encoding=
"UTF-8"?>
2 <!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.1//EN"
3 "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
4 <html xmlns=
"http://www.w3.org/1999/xhtml" xml:
lang=
"en">
6 <meta http-equiv=
"Content-Type" content=
"application/xhtml+xml; charset=UTF-8" />
7 <meta name=
"generator" content=
"AsciiDoc 10.2.0" />
8 <title>Parallel Checkout Design Notes
</title>
9 <style type=
"text/css">
10 /* Shared CSS for AsciiDoc xhtml11 and html5 backends */
14 font-family: Georgia,serif;
18 h1, h2, h3, h4, h5, h6,
19 div.title, caption.title,
20 thead, p.table.header,
22 #author, #revnumber, #revdate, #revremark,
24 font-family: Arial,Helvetica,sans-serif;
28 margin:
1em
5%
1em
5%;
33 text-decoration: underline;
49 h1, h2, h3, h4, h5, h6 {
57 border-bottom:
2px solid silver;
77 border:
1px solid silver;
88 ul
> li { color: #aaa; }
89 ul
> li
> * { color: black; }
91 .monospaced, code, pre {
92 font-family:
"Courier New", Courier, monospace;
99 white-space: pre-wrap;
109 #revnumber, #revdate, #revremark {
114 border-top:
2px solid silver;
120 padding-bottom:
0.5em;
124 padding-bottom:
0.5em;
129 margin-bottom:
1.5em;
131 div.imageblock, div.exampleblock, div.verseblock,
132 div.quoteblock, div.literalblock, div.listingblock, div.sidebarblock,
133 div.admonitionblock {
135 margin-bottom:
1.5em;
137 div.admonitionblock {
139 margin-bottom:
2.0em;
144 div.content { /* Block element content. */
148 /* Block element titles. */
149 div.title, caption.title {
154 margin-bottom:
0.5em;
160 td div.title:first-child {
163 div.content div.title:first-child {
166 div.content + div.title {
170 div.sidebarblock
> div.content {
172 border:
1px solid #dddddd;
173 border-left:
4px solid #f0f0f0;
177 div.listingblock
> div.content {
178 border:
1px solid #dddddd;
179 border-left:
5px solid #f0f0f0;
184 div.quoteblock, div.verseblock {
188 border-left:
5px solid #f0f0f0;
192 div.quoteblock
> div.attribution {
197 div.verseblock
> pre.content {
198 font-family: inherit;
201 div.verseblock
> div.attribution {
205 /* DEPRECATED: Pre version
8.2.7 verse style literal block. */
206 div.verseblock + div.attribution {
210 div.admonitionblock .icon {
214 text-decoration: underline;
216 padding-right:
0.5em;
218 div.admonitionblock td.content {
220 border-left:
3px solid #dddddd;
223 div.exampleblock
> div.content {
224 border-left:
3px solid #dddddd;
228 div.imageblock div.content { padding-left:
0; }
229 span.image img { border-style: none; vertical-align: text-bottom; }
230 a.image:visited { color: white; }
234 margin-bottom:
0.8em;
247 list-style-position: outside;
250 list-style-type: decimal;
253 list-style-type: lower-alpha;
256 list-style-type: upper-alpha;
259 list-style-type: lower-roman;
262 list-style-type: upper-roman;
265 div.compact ul, div.compact ol,
266 div.compact p, div.compact p,
267 div.compact div, div.compact div {
269 margin-bottom:
0.1em;
281 margin-bottom:
0.8em;
284 padding-bottom:
15px;
286 dt.hdlist1.strong, td.hdlist1.strong {
292 padding-right:
0.8em;
298 div.hdlist.compact tr {
307 .footnote, .footnoteref {
311 span.footnote, span.footnoteref {
312 vertical-align: super;
316 margin:
20px
0 20px
0;
320 #footnotes div.footnote {
326 border-top:
1px solid silver;
335 padding-right:
0.5em;
336 padding-bottom:
0.3em;
344 #footer-badges { display: none; }
348 margin-bottom:
2.5em;
356 margin-bottom:
0.1em;
359 div.toclevel0, div.toclevel1, div.toclevel2, div.toclevel3, div.toclevel4 {
376 span.aqua { color: aqua; }
377 span.black { color: black; }
378 span.blue { color: blue; }
379 span.fuchsia { color: fuchsia; }
380 span.gray { color: gray; }
381 span.green { color: green; }
382 span.lime { color: lime; }
383 span.maroon { color: maroon; }
384 span.navy { color: navy; }
385 span.olive { color: olive; }
386 span.purple { color: purple; }
387 span.red { color: red; }
388 span.silver { color: silver; }
389 span.teal { color: teal; }
390 span.white { color: white; }
391 span.yellow { color: yellow; }
393 span.aqua-background { background: aqua; }
394 span.black-background { background: black; }
395 span.blue-background { background: blue; }
396 span.fuchsia-background { background: fuchsia; }
397 span.gray-background { background: gray; }
398 span.green-background { background: green; }
399 span.lime-background { background: lime; }
400 span.maroon-background { background: maroon; }
401 span.navy-background { background: navy; }
402 span.olive-background { background: olive; }
403 span.purple-background { background: purple; }
404 span.red-background { background: red; }
405 span.silver-background { background: silver; }
406 span.teal-background { background: teal; }
407 span.white-background { background: white; }
408 span.yellow-background { background: yellow; }
410 span.big { font-size:
2em; }
411 span.small { font-size:
0.6em; }
413 span.underline { text-decoration: underline; }
414 span.overline { text-decoration: overline; }
415 span.line-through { text-decoration: line-through; }
417 div.unbreakable { page-break-inside: avoid; }
427 margin-bottom:
1.5em;
429 div.tableblock
> table {
430 border:
3px solid #
527bbd;
432 thead, p.table.header {
439 /* Because the table frame attribute is overridden by CSS in most browsers. */
440 div.tableblock
> table[
frame=
"void"] {
443 div.tableblock
> table[
frame=
"hsides"] {
444 border-left-style: none;
445 border-right-style: none;
447 div.tableblock
> table[
frame=
"vsides"] {
448 border-top-style: none;
449 border-bottom-style: none;
460 margin-bottom:
1.5em;
462 thead, p.tableblock.header {
473 border-color: #
527bbd;
474 border-collapse: collapse;
476 th.tableblock, td.tableblock {
480 border-color: #
527bbd;
483 table.tableblock.frame-topbot {
484 border-left-style: hidden;
485 border-right-style: hidden;
487 table.tableblock.frame-sides {
488 border-top-style: hidden;
489 border-bottom-style: hidden;
491 table.tableblock.frame-none {
492 border-style: hidden;
495 th.tableblock.halign-left, td.tableblock.halign-left {
498 th.tableblock.halign-center, td.tableblock.halign-center {
501 th.tableblock.halign-right, td.tableblock.halign-right {
505 th.tableblock.valign-top, td.tableblock.valign-top {
508 th.tableblock.valign-middle, td.tableblock.valign-middle {
509 vertical-align: middle;
511 th.tableblock.valign-bottom, td.tableblock.valign-bottom {
512 vertical-align: bottom;
523 padding-bottom:
0.5em;
524 border-top:
2px solid silver;
525 border-bottom:
2px solid silver;
530 body.manpage div.sectionbody {
535 body.manpage div#toc { display: none; }
540 <script type=
"text/javascript">
542 var asciidoc = { // Namespace.
544 /////////////////////////////////////////////////////////////////////
545 // Table Of Contents generator
546 /////////////////////////////////////////////////////////////////////
548 /* Author: Mihai Bazon, September
2002
549 * http://students.infoiasi.ro/~mishoo
551 * Table Of Content generator
554 * Feel free to use this script under the terms of the GNU General Public
555 * License, as long as you do not remove or alter this notice.
558 /* modified by Troy D. Hanson, September
2006. License: GPL */
559 /* modified by Stuart Rackham,
2006,
2009. License: GPL */
562 toc: function (toclevels) {
564 function getText(el) {
566 for (var i = el.firstChild; i != null; i = i.nextSibling) {
567 if (i.nodeType ==
3 /* Node.TEXT_NODE */) // IE doesn't speak constants.
569 else if (i.firstChild != null)
575 function TocEntry(el, text, toclevel) {
578 this.toclevel = toclevel;
581 function tocEntries(el, toclevels) {
582 var result = new Array;
583 var re = new RegExp('[hH]([
1-'+(toclevels+
1)+'])');
584 // Function that scans the DOM tree for header elements (the DOM2
585 // nodeIterator API would be a better technique but not supported by all
587 var iterate = function (el) {
588 for (var i = el.firstChild; i != null; i = i.nextSibling) {
589 if (i.nodeType ==
1 /* Node.ELEMENT_NODE */) {
590 var mo = re.exec(i.tagName);
591 if (mo && (i.getAttribute(
"class") || i.getAttribute(
"className")) !=
"float") {
592 result[result.length] = new TocEntry(i, getText(i), mo[
1]-
1);
602 var toc = document.getElementById(
"toc");
607 // Delete existing TOC entries in case we're reloading the TOC.
608 var tocEntriesToRemove = [];
610 for (i =
0; i < toc.childNodes.length; i++) {
611 var entry = toc.childNodes[i];
612 if (entry.nodeName.toLowerCase() == 'div'
613 && entry.getAttribute(
"class")
614 && entry.getAttribute(
"class").match(/^toclevel/))
615 tocEntriesToRemove.push(entry);
617 for (i =
0; i < tocEntriesToRemove.length; i++) {
618 toc.removeChild(tocEntriesToRemove[i]);
621 // Rebuild TOC entries.
622 var entries = tocEntries(document.getElementById(
"content"), toclevels);
623 for (var i =
0; i < entries.length; ++i) {
624 var entry = entries[i];
625 if (entry.element.id ==
"")
626 entry.element.id =
"_toc_" + i;
627 var a = document.createElement(
"a");
628 a.href =
"#" + entry.element.id;
629 a.appendChild(document.createTextNode(entry.text));
630 var div = document.createElement(
"div");
632 div.className =
"toclevel" + entry.toclevel;
633 toc.appendChild(div);
635 if (entries.length ==
0)
636 toc.parentNode.removeChild(toc);
640 /////////////////////////////////////////////////////////////////////
641 // Footnotes generator
642 /////////////////////////////////////////////////////////////////////
644 /* Based on footnote generation code from:
645 * http://www.brandspankingnew.net/archive/
2005/
07/format_footnote.html
648 footnotes: function () {
649 // Delete existing footnote entries in case we're reloading the footnodes.
651 var noteholder = document.getElementById(
"footnotes");
655 var entriesToRemove = [];
656 for (i =
0; i < noteholder.childNodes.length; i++) {
657 var entry = noteholder.childNodes[i];
658 if (entry.nodeName.toLowerCase() == 'div' && entry.getAttribute(
"class") ==
"footnote")
659 entriesToRemove.push(entry);
661 for (i =
0; i < entriesToRemove.length; i++) {
662 noteholder.removeChild(entriesToRemove[i]);
665 // Rebuild footnote entries.
666 var cont = document.getElementById(
"content");
667 var spans = cont.getElementsByTagName(
"span");
670 for (i=
0; i
<spans.length; i++) {
671 if (spans[i].className ==
"footnote") {
673 var note = spans[i].getAttribute(
"data-note");
675 // Use [\s\S] in place of . so multi-line matches work.
676 // Because JavaScript has no s (dotall) regex flag.
677 note = spans[i].innerHTML.match(/\s*\[([\s\S]*)]\s*/)[
1];
679 "[<a id='_footnoteref_" + n +
"' href='#_footnote_" + n +
680 "' title='View footnote' class='footnote'>" + n +
"</a>]";
681 spans[i].setAttribute(
"data-note", note);
683 noteholder.innerHTML +=
684 "<div class='footnote' id='_footnote_" + n +
"'>" +
685 "<a href='#_footnoteref_" + n +
"' title='Return to text'>" +
686 n +
"</a>. " + note +
"</div>";
687 var id =spans[i].getAttribute(
"id");
688 if (id != null) refs[
"#"+id] = n;
692 noteholder.parentNode.removeChild(noteholder);
694 // Process footnoterefs.
695 for (i=
0; i
<spans.length; i++) {
696 if (spans[i].className ==
"footnoteref") {
697 var href = spans[i].getElementsByTagName(
"a")[
0].getAttribute(
"href");
698 href = href.match(/#.*/)[
0]; // Because IE return full URL.
701 "[<a href='#_footnote_" + n +
702 "' title='View footnote' class='footnote'>" + n +
"</a>]";
708 install: function(toclevels) {
711 function reinstall() {
712 asciidoc.footnotes();
714 asciidoc.toc(toclevels);
718 function reinstallAndRemoveTimer() {
719 clearInterval(timerId);
723 timerId = setInterval(reinstall,
500);
724 if (document.addEventListener)
725 document.addEventListener(
"DOMContentLoaded", reinstallAndRemoveTimer, false);
727 window.onload = reinstallAndRemoveTimer;
735 <body class=
"article">
737 <h1>Parallel Checkout Design Notes
</h1>
738 <span id=
"revdate">2023-
08-
21</span>
742 <div class=
"sectionbody">
743 <div class=
"paragraph"><p>The
"Parallel Checkout" feature attempts to use multiple processes to
744 parallelize the work of uncompressing the blobs, applying in-core
745 filters, and writing the resulting contents to the working tree during a
746 checkout operation. It can be used by all checkout-related commands,
747 such as
<code>clone
</code>,
<code>checkout
</code>,
<code>reset
</code>,
<code>sparse-checkout
</code>, and others.
</p></div>
748 <div class=
"paragraph"><p>These commands share the following basic structure:
</p></div>
749 <div class=
"ulist"><ul>
752 Step
1: Read the current index file into memory.
757 Step
2: Modify the in-memory index based upon the command, and
758 temporarily mark all cache entries that need to be updated.
763 Step
3: Populate the working tree to match the new candidate index.
764 This includes iterating over all of the to-be-updated cache entries
765 and delete, create, or overwrite the associated files in the working
771 Step
4: Write the new index to disk.
775 <div class=
"paragraph"><p>Step
3 is the focus of the
"parallel checkout" effort described here.
</p></div>
779 <h2 id=
"_sequential_implementation">Sequential Implementation
</h2>
780 <div class=
"sectionbody">
781 <div class=
"paragraph"><p>For the purposes of discussion here, the current sequential
782 implementation of Step
3 is divided in
3 parts, each one implemented in
783 its own function:
</p></div>
784 <div class=
"ulist"><ul>
787 Step
3a:
<code>unpack-trees.c:check_updates()
</code> contains a series of
788 sequential loops iterating over the
<code>cache_entry
</code>'s array. The main
789 loop in this function calls the Step
3b function for each of the
790 to-be-updated entries.
795 Step
3b:
<code>entry.c:checkout_entry()
</code> examines the existing working tree
796 for file conflicts, collisions, and unsaved changes. It removes files
797 and creates leading directories as necessary. It calls the Step
3c
798 function for each entry to be written.
803 Step
3c:
<code>entry.c:write_entry()
</code> loads the blob into memory, smudges
804 it if necessary, creates the file in the working tree, writes the
805 smudged contents, calls
<code>fstat()
</code> or
<code>lstat()
</code>, and updates the
806 associated
<code>cache_entry
</code> struct with the stat information gathered.
810 <div class=
"paragraph"><p>It wouldn
’t be safe to perform Step
3b in parallel, as there could be
811 race conditions between file creations and removals. Instead, the
812 parallel checkout framework lets the sequential code handle Step
3b,
813 and uses parallel workers to replace the sequential
814 <code>entry.c:write_entry()
</code> calls from Step
3c.
</p></div>
818 <h2 id=
"_rejected_multi_threaded_solution">Rejected Multi-Threaded Solution
</h2>
819 <div class=
"sectionbody">
820 <div class=
"paragraph"><p>The most
"straightforward" implementation would be to spread the set of
821 to-be-updated cache entries across multiple threads. But due to the
822 thread-unsafe functions in the object database code, we would have to use locks to
823 coordinate the parallel operation. An early prototype of this solution
824 showed that the multi-threaded checkout would bring performance
825 improvements over the sequential code, but there was still too much lock
826 contention. A
<code>perf
</code> profiling indicated that around
20% of the runtime
827 during a local Linux clone (on an SSD) was spent in locking functions.
828 For this reason this approach was rejected in favor of using multiple
829 child processes, which led to a better performance.
</p></div>
833 <h2 id=
"_multi_process_solution">Multi-Process Solution
</h2>
834 <div class=
"sectionbody">
835 <div class=
"paragraph"><p>Parallel checkout alters the aforementioned Step
3 to use multiple
836 <code>checkout--worker
</code> background processes to distribute the work. The
837 long-running worker processes are controlled by the foreground Git
838 command using the existing run-command API.
</p></div>
840 <h3 id=
"_overview">Overview
</h3>
841 <div class=
"paragraph"><p>Step
3b is only slightly altered; for each entry to be checked out, the
842 main process performs the following steps:
</p></div>
843 <div class=
"ulist"><ul>
846 M1: Check whether there is any untracked or unclean file in the
847 working tree which would be overwritten by this entry, and decide
848 whether to proceed (removing the file(s)) or not.
853 M2: Create the leading directories.
858 M3: Load the conversion attributes for the entry
’s path.
863 M4: Check, based on the entry
’s type and conversion attributes,
864 whether the entry is eligible for parallel checkout (more on this
865 later). If it is eligible, enqueue the entry and the loaded
866 attributes to later write the entry in parallel. If not, write the
867 entry right away, using the default sequential code.
871 <div class=
"paragraph"><p>Note: we save the conversion attributes associated with each entry
872 because the workers don
’t have access to the main process' index state,
873 so they can
’t load the attributes by themselves (and the attributes are
874 needed to properly smudge the entry). Additionally, this has a positive
875 impact on performance as (
1) we don
’t need to load the attributes twice
876 and (
2) the attributes machinery is optimized to handle paths in
877 sequential order.
</p></div>
878 <div class=
"paragraph"><p>After all entries have passed through the above steps, the main process
879 checks if the number of enqueued entries is sufficient to spread among
880 the workers. If not, it just writes them sequentially. Otherwise, it
881 spawns the workers and distributes the queued entries uniformly in
882 continuous chunks. This aims to minimize the chances of two workers
883 writing to the same directory simultaneously, which could increase lock
884 contention in the kernel.
</p></div>
885 <div class=
"paragraph"><p>Then, for each assigned item, each worker:
</p></div>
886 <div class=
"ulist"><ul>
889 W1: Checks if there is any non-directory file in the leading part of
890 the entry
’s path or if there already exists a file at the entry' path.
891 If so, mark the entry with
<code>PC_ITEM_COLLIDED
</code> and skip it (more on
897 W2: Creates the file (with O_CREAT and O_EXCL).
902 W3: Loads the blob into memory (inflating and delta reconstructing
908 W4: Applies any required in-process filter, like end-of-line
909 conversion and re-encoding.
914 W5: Writes the result to the file descriptor opened at W2.
919 W6: Calls
<code>fstat()
</code> or lstat()` on the just-written path, and sends
920 the result back to the main process, together with the end status of
921 the operation and the item
’s identification number.
925 <div class=
"paragraph"><p>Note that, when possible, steps W3 to W5 are delegated to the streaming
926 machinery, removing the need to keep the entire blob in memory.
</p></div>
927 <div class=
"paragraph"><p>If the worker fails to read the blob or to write it to the working tree,
928 it removes the created file to avoid leaving empty files behind. This is
929 the
<strong>only
</strong> time a worker is allowed to remove a file.
</p></div>
930 <div class=
"paragraph"><p>As mentioned earlier, it is the responsibility of the main process to
931 remove any file that blocks the checkout operation (or abort if the
932 removal(s) would cause data loss and the user didn
’t ask to
<code>--force
</code>).
933 This is crucial to avoid race conditions and also to properly detect
934 path collisions at Step W1.
</p></div>
935 <div class=
"paragraph"><p>After the workers finish writing the items and sending back the required
936 information, the main process handles the results in two steps:
</p></div>
937 <div class=
"ulist"><ul>
940 First, it updates the in-memory index with the
<code>lstat()
</code> information
941 sent by the workers. (This must be done first as this information
942 might me required in the following step.)
947 Then it writes the items which collided on disk (i.e. items marked
948 with
<code>PC_ITEM_COLLIDED
</code>). More on this below.
956 <h2 id=
"_path_collisions">Path Collisions
</h2>
957 <div class=
"sectionbody">
958 <div class=
"paragraph"><p>Path collisions happen when two different paths correspond to the same
959 entry in the file system. E.g. the paths
<em>a
</em> and
<em>A
</em> would collide in a
960 case-insensitive file system.
</p></div>
961 <div class=
"paragraph"><p>The sequential checkout deals with collisions in the same way that it
962 deals with files that were already present in the working tree before
963 checkout. Basically, it checks if the path that it wants to write
964 already exists on disk, makes sure the existing file doesn
’t have
965 unsaved data, and then overwrites it. (To be more pedantic: it deletes
966 the existing file and creates the new one.) So, if there are multiple
967 colliding files to be checked out, the sequential code will write each
968 one of them but only the last will actually survive on disk.
</p></div>
969 <div class=
"paragraph"><p>Parallel checkout aims to reproduce the same behavior. However, we
970 cannot let the workers racily write to the same file on disk. Instead,
971 the workers detect when the entry that they want to check out would
972 collide with an existing file, and mark it with
<code>PC_ITEM_COLLIDED
</code>.
973 Later, the main process can sequentially feed these entries back to
974 <code>checkout_entry()
</code> without the risk of race conditions. On clone, this
975 also has the effect of marking the colliding entries to later emit a
976 warning for the user, like the classic sequential checkout does.
</p></div>
977 <div class=
"paragraph"><p>The workers are able to detect both collisions among the entries being
978 concurrently written and collisions between a parallel-eligible entry
979 and an ineligible entry. The general idea for collision detection is
980 quite straightforward: for each parallel-eligible entry, the main
981 process must remove all files that prevent this entry from being written
982 (before enqueueing it). This includes any non-directory file in the
983 leading path of the entry. Later, when a worker gets assigned the entry,
984 it looks again for the non-directories files and for an already existing
985 file at the entry
’s path. If any of these checks finds something, the
986 worker knows that there was a path collision.
</p></div>
987 <div class=
"paragraph"><p>Because parallel checkout can distinguish path collisions from the case
988 where the file was already present in the working tree before checkout,
989 we could alternatively choose to skip the checkout of colliding entries.
990 However, each entry that doesn
’t get written would have NULL
<code>lstat()
</code>
991 fields on the index. This could cause performance penalties for
992 subsequent commands that need to refresh the index, as they would have
993 to go to the file system to see if the entry is dirty. Thus, if we have
994 N entries in a colliding group and we decide to write and
<code>lstat()
</code> only
995 one of them, every subsequent
<code>git-status
</code> will have to read, convert,
996 and hash the written file N -
1 times. By checking out all colliding
997 entries (like the sequential code does), we only pay the overhead once,
998 during checkout.
</p></div>
1002 <h2 id=
"_eligible_entries_for_parallel_checkout">Eligible Entries for Parallel Checkout
</h2>
1003 <div class=
"sectionbody">
1004 <div class=
"paragraph"><p>As previously mentioned, not all entries passed to
<code>checkout_entry()
</code>
1005 will be considered eligible for parallel checkout. More specifically, we
1007 <div class=
"ulist"><ul>
1010 Symbolic links; to avoid race conditions that, in combination with
1011 path collisions, could cause workers to write files at the wrong
1012 place. For example, if we were to concurrently check out a symlink
1013 <em>a
</em> → <em>b
</em> and a regular file
<em>A/f
</em> in a case-insensitive file system,
1014 we could potentially end up writing the file
<em>A/f
</em> at
<em>a/f
</em>, due to a
1020 Regular files that require external filters (either
"one shot" filters
1021 or long-running process filters). These filters are black-boxes to Git
1022 and may have their own internal locking or non-concurrent assumptions.
1023 So it might not be safe to run multiple instances in parallel.
1025 <div class=
"paragraph"><p>Besides, long-running filters may use the delayed checkout feature to
1026 postpone the return of some filtered blobs. The delayed checkout queue
1027 and the parallel checkout queue are not compatible and should remain
1029 <div class=
"paragraph"><p>Note: regular files that only require internal filters, like end-of-line
1030 conversion and re-encoding, are eligible for parallel checkout.
</p></div>
1033 <div class=
"paragraph"><p>Ineligible entries are checked out by the classic sequential codepath
1034 <strong>before
</strong> spawning workers.
</p></div>
1035 <div class=
"paragraph"><p>Note: submodules
’s files are also eligible for parallel checkout (as
1036 long as they don
’t fall into any of the excluding categories mentioned
1037 above). But since each submodule is checked out in its own child
1038 process, we don
’t mix the superproject
’s and the submodules' files in
1039 the same parallel checkout process or queue.
</p></div>
1043 <h2 id=
"_the_api">The API
</h2>
1044 <div class=
"sectionbody">
1045 <div class=
"paragraph"><p>The parallel checkout API was designed with the goal of minimizing
1046 changes to the current users of the checkout machinery. This means that
1047 they don
’t have to call a different function for sequential or parallel
1048 checkout. As already mentioned,
<code>checkout_entry()
</code> will automatically
1049 insert the given entry in the parallel checkout queue when this feature
1050 is enabled and the entry is eligible; otherwise, it will just write the
1051 entry right away, using the sequential code. In general, callers of the
1052 parallel checkout API should look similar to this:
</p></div>
1053 <div class=
"listingblock">
1054 <div class=
"content">
1055 <pre><code>int pc_workers, pc_threshold, err =
0;
1056 struct checkout state;
1058 get_parallel_checkout_configs(
&pc_workers,
&pc_threshold);
1061 * This check is not strictly required, but it
1062 * should save some time in sequential mode.
1064 if (pc_workers
> 1)
1065 init_parallel_checkout();
1067 for (each cache_entry ce to-be-updated)
1068 err |= checkout_entry(ce,
&state, NULL, NULL);
1070 err |= run_parallel_checkout(
&state, pc_workers, pc_threshold, NULL, NULL);
</code></pre>
1075 <div id=
"footnotes"><hr /></div>
1077 <div id=
"footer-text">
1079 2022-
11-
11 23:
55:
30 PST