From 05270ff12ac5f40826c7f9157c66c8b4ad7e0099 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Thu, 11 Dec 2025 10:28:30 +0900 Subject: [PATCH 01/15] [LCHIB-674] parallel processing of repositories --- .../ingest/commits/CommitGraphCollector.java | 305 +++++++++++------- .../ingest/commits/ProgressReporter.java | 93 ++++++ .../commits/ProgressReportingConsumer.java | 74 ----- .../ingest/commits/AllTests.java | 2 +- .../commits/CommitGraphCollectorTest.java | 10 +- .../ingest/commits/ProgressReporterTest.java | 95 ++++++ .../ProgressReportingConsumerTest.java | 31 -- 7 files changed, 379 insertions(+), 231 deletions(-) create mode 100644 src/main/java/com/launchableinc/ingest/commits/ProgressReporter.java delete mode 100644 src/main/java/com/launchableinc/ingest/commits/ProgressReportingConsumer.java create mode 100644 src/test/java/com/launchableinc/ingest/commits/ProgressReporterTest.java delete mode 100644 src/test/java/com/launchableinc/ingest/commits/ProgressReportingConsumerTest.java diff --git a/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java b/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java index 3ac660f2b..84c609c18 100644 --- a/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java +++ b/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java @@ -58,7 +58,12 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.Vector; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.Supplier; import java.util.zip.GZIPOutputStream; @@ -87,7 +92,7 @@ public class CommitGraphCollector { */ private final Repository root; - private int commitsSent, filesSent; + private AtomicInteger commitsSent = new AtomicInteger(), filesSent = new AtomicInteger(); private boolean collectCommitMessage, collectFiles; @@ -119,11 +124,11 @@ public CommitGraphCollector(String name, Repository git) { /** How many commits did we transfer? */ public int getCommitsSent() { - return commitsSent; + return commitsSent.get(); } public int getFilesSent() { - return filesSent; + return filesSent.get(); } private String dumpHeaderAsJson(Header[] headers) throws JsonProcessingException { @@ -271,10 +276,37 @@ public void transfer( Collection advertised, IOConsumer commitSender, TreeReceiver treeReceiver, IOConsumer fileSender, int chunkSize) throws IOException { ByRepository r = new ByRepository(root, rootName); - try (CommitChunkStreamer cs = new CommitChunkStreamer(commitSender, chunkSize); - FileChunkStreamer fs = new FileChunkStreamer(fileSender, chunkSize); - ProgressReportingConsumer fsr = new ProgressReportingConsumer<>(fs, VirtualFile::path, Duration.ofSeconds(3))) { - r.transfer(advertised, cs, treeReceiver, fsr); + + ExecutorService es = Executors.newFixedThreadPool(4); + // for debugging +// ExecutorService es = MoreExecutors.newDirectExecutorService(); + + ProgressReporter pr = new ProgressReporter<>(VirtualFile::path, Duration.ofSeconds(3)); + try { + r.forEachSubModule(es, br -> { + if (collectFiles) { + // record all the necessary BLOBs first, before attempting to record its commit. + // this way, if the file collection fails, the server won't see this commit, so the future + // "record commit" invocation will retry the file collection, thereby making the behavior idempotent. + // TODO: file transfer can be parallelized more aggressively, where we send chunks in parallel + try (FileChunkStreamer fs = new FileChunkStreamer(fileSender, chunkSize); + ProgressReporter.Consumer fsr = pr.newConsumer(fs)) { + br.collectFiles(advertised, treeReceiver, fsr); + } + } + + // we need to send commits in the topological order, so any parallelization within a repository + // is probably not worth the effort. + // TODO: If we process a repository and that doesn't create enough commits + // to form a full chunk, then it makes sense to concatenate them with other commits from other repositories. + // Even when # of repos is large, incremental transfer typically only produces a small amount of commits + // per repo, so this will considerably reduce the connection setup / tear down overhead. + try (CommitChunkStreamer cs = new CommitChunkStreamer(commitSender, chunkSize)) { + br.collectCommits(advertised, cs); + } + }); + } finally { + es.shutdown(); } } @@ -331,75 +363,29 @@ final class ByRepository implements AutoCloseable { this.shallowCommits = objectReader.getShallowCommits(); } - /** - * Writes delta between local commits to the advertised to JSON stream. - * - * @param commitReceiver Receives commits that should be sent, one by one. - */ - public void transfer(Collection advertised, Consumer commitReceiver, TreeReceiver treeReceiver, FlushableConsumer fileReceiver) - throws IOException { - try (RevWalk walk = new RevWalk(git); TreeWalk treeWalk = new TreeWalk(git)) { - // walk reverse topological order, so that older commits get added to the server earlier. - // This way, the connectivity of the commit graph will be always maintained - walk.sort(RevSort.TOPO); - walk.sort(RevSort.REVERSE, true); - // also combine this with commit time based ordering, so that we can stop walking when we - // find old enough commits AFAICT, this is no-op in JGit and it always sorts things in - // commit time order, but it is in the contract, so I'm assuming we shouldn't rely on the - // implementation optimization that's currently enabling this all the time - walk.sort(RevSort.COMMIT_TIME_DESC, true); - - ObjectId headId = git.resolve("HEAD"); - RevCommit start = walk.parseCommit(headId); - walk.markStart(start); - treeWalk.addTree(start.getTree()); - - // don't walk commits too far back. - // for our purpose of computing CUT, these are unlikely to contribute meaningfully - // and it drastically cuts down the initial commit consumption of a new large repository. - // ... except we do want to capture the head commit, as that makes it easier to spot integration problems - // when `record build` and `record commit` are separated. - - // two RevFilters are order sensitive. This is because CommitTimeRevFilter.after doesn't return false to - // filter things out, it throws StopWalkException to terminate the walk, never giving a chance for the other - // branch of OR to be evaluated. So we need to put ObjectRevFilter first. - walk.setRevFilter( - OrRevFilter.create( - new ObjectRevFilter(headId), - CommitTimeRevFilter.after(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(maxDays)))); - - for (ObjectId id : advertised) { - try { - RevCommit c = walk.parseCommit(id); - walk.markUninteresting(c); - if (!reportAllFiles) { - treeWalk.addTree(c.getTree()); - } - } catch (MissingObjectException e) { - // it's possible that the server advertises a commit we don't have. - // - // TODO: how does git-push handles the case when the client doesn't recognize commits? - // Unless it tries to negotiate further what commits they have in common, - // git-upload-pack can end up creating a big pack with lots of redundant objects - // - // think about a case when a client is pushing a new branch against - // the master branch that moved on the server. - } - } - - // record all the necessary BLOBs first, before attempting to record its commit. - // this way, if the file collection fails, the server won't see this commit, so the future - // "record commit" invocation will retry the file collection, thereby making the behavior idempotent. - collectFiles(start, treeWalk, treeReceiver, fileReceiver); - fileReceiver.flush(); - - // walk the commits, transform them, and send them to the commitReceiver - for (RevCommit c : walk) { - commitReceiver.accept(transform(c)); - commitsSent++; + void forEachSubModule(ExecutorService threadPool, IOConsumer consumer) throws IOException { + for (Future f : forEachSubModuleAsync(threadPool, consumer)) { + try { + f.get(); + } catch (Exception e) { + throw new IOException("Failed to process a repository", e); } } + } + /** + * Recursively iterate all the sub-modules and apply the given consumer to them asynchronously, using the given + * thread pool. + * + *

+ * The way this function mixes (1) synchronous call to consumer with {@code this}, (2) use thread pool to recursively + * process submodules might be a bit hard to follow. This was motivated by the fact that {@link ByRepository} for + * sub-modules need to be closed, while {@code this} shouldn't be closed. + * + * @return all the async jobs that are forked off, to allow the caller to wait for their completion. + */ + Collection> forEachSubModuleAsync(ExecutorService threadPool, IOConsumer consumer) throws IOException { + Vector> jobs = new Vector<>(); /* Git submodule support ===================== @@ -418,73 +404,114 @@ That is, find submodules that are available in the working tree (thus `!isBare() if (!git.isBare()) { try (SubmoduleWalk swalk = SubmoduleWalk.forIndex(git)) { while (swalk.next()) { - try (Repository subRepo = swalk.getRepository()) { - if (subRepo != null) { - try { - try (ByRepository br = new ByRepository(subRepo, name + "/" + swalk.getModulesPath())) { - br.transfer(advertised, commitReceiver, treeReceiver, fileReceiver); + Repository subRepo = swalk.getRepository(); + if (subRepo != null) { + try { + ByRepository br = new ByRepository(subRepo, name + "/" + swalk.getModulesPath()); + jobs.add(threadPool.submit(() -> { + try { + jobs.addAll(br.forEachSubModuleAsync(threadPool, consumer)); + return null; + } finally { + br.close(); + subRepo.close(); } - } catch (ConfigInvalidException e) { - throw new IOException("Invalid Git submodule configuration: " + git.getDirectory(), e); - } + })); + } catch (ConfigInvalidException e) { + throw new IOException("Invalid Git submodule configuration: " + git.getDirectory(), e); } } } } } + + consumer.accept(this); + + return jobs; + } + + private void parseEachCommit(RevWalk walk, Collection advertised, IOConsumer consumer) throws IOException { + for (ObjectId id : advertised) { + try { + RevCommit c = walk.parseCommit(id); + consumer.accept(c); + } catch (MissingObjectException e) { + // it's possible that the server advertises a commit we don't have. + // + // TODO: how does git-push handles the case when the client doesn't recognize commits? + // Unless it tries to negotiate further what commits they have in common, + // git-upload-pack can end up creating a big pack with lots of redundant objects + // + // think about a case when a client is pushing a new branch against + // the master branch that moved on the server. + } + } } /** - * treeWalk contains the HEAD (the interesting commit) at the 0th position, then all the commits - * the server advertised in the 1st, 2nd, ... - * Our goal here is to find all the files that the server hasn't seen yet. We'll send them to the tree receiver, - * which further responds with the actual files we need to send to the server. + * Records all the necessary BLOBs first */ - private void collectFiles(RevCommit start, TreeWalk treeWalk, TreeReceiver treeReceiver, Consumer fileReceiver) throws IOException { - if (!collectFiles) { - return; - } + void collectFiles(Collection advertised, TreeReceiver treeReceiver, FlushableConsumer fileReceiver) throws IOException { + try (TreeWalk treeWalk = new TreeWalk(git)) { + ObjectId headId = git.resolve("HEAD"); + RevCommit start = git.parseCommit(headId); + treeWalk.addTree(start.getTree()); + if (!reportAllFiles) { + // to optimize data transfer, skip files that the server has already seen + // i.e., files that are present in any of the advertised commits + // if the reportAllFiles flag is on, then skip this optimization on the client side. + // treeReceiver will still provide an opportunity for the server to be selective. + try (RevWalk walk = new RevWalk(git)) { + parseEachCommit(walk, advertised, c -> treeWalk.addTree(c.getTree())); + } + } - int c = treeWalk.getTreeCount(); - OUTER: - while (treeWalk.next()) { - ObjectId head = treeWalk.getObjectId(0); - for (int i = 1; i < c; i++) { - if (head.equals(treeWalk.getObjectId(i))) { - // file at the head is identical to one of the uninteresting commits, - // meaning we have already seen this file/directory on the server. - // if it is a dir, there's no need to visit this whole subtree, so skip over - continue OUTER; + int c = treeWalk.getTreeCount(); + + OUTER: + while (treeWalk.next()) { + ObjectId head = treeWalk.getObjectId(0); + for (int i = 1; i < c; i++) { + if (head.equals(treeWalk.getObjectId(i))) { + // file at the head is identical to one of the uninteresting commits, + // meaning we have already seen this file/directory on the server. + // if it is a dir, there's no need to visit this whole subtree, so skip over + continue OUTER; + } } - } - if (treeWalk.isSubtree()) { - treeWalk.enterSubtree(); - } else { - if ((treeWalk.getFileMode(0).getBits() & FileMode.TYPE_MASK) == FileMode.TYPE_FILE) { - GitFile f = new GitFile(name, treeWalk.getPathString(), head, objectReader); - // to avoid excessive data transfer, skip files that are too big - if (f.size() < 1024 * 1024 && f.isText() && !f.path.equals(HEADER_FILE)) { - treeReceiver.accept(f); + if (treeWalk.isSubtree()) { + treeWalk.enterSubtree(); + } else { + if ((treeWalk.getFileMode(0).getBits() & FileMode.TYPE_MASK) == FileMode.TYPE_FILE) { + GitFile f = new GitFile(name, treeWalk.getPathString(), head, objectReader); + // to avoid excessive data transfer, skip files that are too big + if (f.size() < 1024 * 1024 && f.isText() && !f.path.equals(HEADER_FILE)) { + treeReceiver.accept(f); + } } } } - } - // Note(Konboi): To balance the order, since words like "test" and "spec" tend to appear - // toward the end in alphabetical sorting. - List files = new ArrayList<>(treeReceiver.response()); - if (!files.isEmpty()) { - fileReceiver.accept(buildHeader(start)); - filesSent++; - - Collections.shuffle(files); - for (VirtualFile f : files) { - fileReceiver.accept(f); - filesSent++; + // Now let the server select the files it actually wants to see + List files = new ArrayList<>(treeReceiver.response()); + + // Note(Konboi): To balance the order, since words like "test" and "spec" tend to appear + // toward the end in alphabetical sorting. + if (!files.isEmpty()) { + fileReceiver.accept(buildHeader(start)); + filesSent.incrementAndGet(); + + Collections.shuffle(files); + for (VirtualFile f : files) { + fileReceiver.accept(f); + filesSent.incrementAndGet(); + } } + + fileReceiver.flush(); } } @@ -516,6 +543,44 @@ private VirtualFile buildHeader(RevCommit start) throws IOException { return VirtualFile.from(name, HEADER_FILE, ObjectId.zeroId(), os.toByteArray()); } + void collectCommits(Collection advertised, Consumer commitReceiver) throws IOException { + try (RevWalk walk = new RevWalk(git)) { + // walk reverse topological order, so that older commits get added to the server earlier. + // This way, the connectivity of the commit graph will be always maintained + walk.sort(RevSort.TOPO); + walk.sort(RevSort.REVERSE, true); + // also combine this with commit time based ordering, so that we can stop walking when we + // find old enough commits AFAICT, this is no-op in JGit and it always sorts things in + // commit time order, but it is in the contract, so I'm assuming we shouldn't rely on the + // implementation optimization that's currently enabling this all the time + walk.sort(RevSort.COMMIT_TIME_DESC, true); + + ObjectId headId = git.resolve("HEAD"); + walk.markStart(walk.parseCommit(headId)); + + // don't walk commits too far back. + // for our purpose of computing CUT, these are unlikely to contribute meaningfully + // and it drastically cuts down the initial commit consumption of a new large repository. + // ... except we do want to capture the head commit, as that makes it easier to spot integration problems + // when `record build` and `record commit` are separated. + + // two RevFilters are order sensitive. This is because CommitTimeRevFilter.after doesn't return false to + // filter things out, it throws StopWalkException to terminate the walk, never giving a chance for the other + // branch of OR to be evaluated. So we need to put ObjectRevFilter first. + walk.setRevFilter( + OrRevFilter.create( + new ObjectRevFilter(headId), + CommitTimeRevFilter.after(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(maxDays)))); + + parseEachCommit(walk, advertised, walk::markUninteresting); + + // walk the commits, transform them, and send them to the commitReceiver + for (RevCommit c : walk) { + commitReceiver.accept(transform(c)); + commitsSent.incrementAndGet(); + } + } + } private JSCommit transform(RevCommit r) throws IOException { JSCommit c = new JSCommit(); diff --git a/src/main/java/com/launchableinc/ingest/commits/ProgressReporter.java b/src/main/java/com/launchableinc/ingest/commits/ProgressReporter.java new file mode 100644 index 000000000..1de1f19e3 --- /dev/null +++ b/src/main/java/com/launchableinc/ingest/commits/ProgressReporter.java @@ -0,0 +1,93 @@ +package com.launchableinc.ingest.commits; + +import java.io.IOException; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; + +import static java.time.Instant.now; + +/** + * Given multiple concurrent slow {@link Consumer}s, each g oing over a large + * number of items in parallel, + * provide a progress report to show that the work is still in progress. + */ +class ProgressReporter { + private final Function printer; + private final Duration reportInterval; + private Instant nextReportTime; + + /** + * Number of items that need to be processed, across all consumers. + */ + private final AtomicInteger workload = new AtomicInteger(); + /** + * Number of items that have already been processed, across all consumers. + */ + private final AtomicInteger completed = new AtomicInteger(); + + ProgressReporter(Function printer, Duration reportInterval) { + this.printer = printer; + this.reportInterval = reportInterval; + this.nextReportTime = now().plus(reportInterval); + } + + /** + * Deals with one serial stream of work. + */ + class Consumer implements FlushableConsumer, AutoCloseable { + private final FlushableConsumer base; + private final List pool = new ArrayList<>(); + + Consumer(FlushableConsumer base) { + this.base = base; + } + + @Override + public void accept(T t) { + pool.add(t); + workload.incrementAndGet(); + } + + @Override + public void flush() throws IOException { + for (T x : pool) { + synchronized (ProgressReporter.this) { + if (now().isAfter(nextReportTime)) { + print(completed.get(), workload.get(), x); + nextReportTime = now().plus(reportInterval); + } + } + base.accept(x); + completed.incrementAndGet(); + } + pool.clear(); + base.flush(); + } + + @Override + public void close() throws IOException { + flush(); + } + } + + Consumer newConsumer(FlushableConsumer base) { + return new Consumer(base); + } + + protected void print(int c, int w, T x) { + int width = String.valueOf(w).length(); + System.err.printf("%s/%d: %s%n", pad(c, width), w, printer.apply(x)); + } + + static String pad(int i, int width) { + String s = String.valueOf(i); + while (s.length() < width) { + s = " " + s; + } + return s; + } +} diff --git a/src/main/java/com/launchableinc/ingest/commits/ProgressReportingConsumer.java b/src/main/java/com/launchableinc/ingest/commits/ProgressReportingConsumer.java deleted file mode 100644 index 858f63a47..000000000 --- a/src/main/java/com/launchableinc/ingest/commits/ProgressReportingConsumer.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.launchableinc.ingest.commits; - -import java.io.IOException; -import java.time.Duration; -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; -import java.util.function.Function; - -import static java.time.Instant.now; - -/** - * Given a slow {@link Consumer} that goes over a large number of items, - * provide a progress report to show that the work is still in progress. - */ -class ProgressReportingConsumer implements FlushableConsumer, AutoCloseable { - private final FlushableConsumer base; - private final List pool = new ArrayList<>(); - private final Function printer; - private final Duration reportInterval; - private int round = 1; - - ProgressReportingConsumer(FlushableConsumer base, Function printer, Duration reportInterval) { - this.base = base; - this.printer = printer; - this.reportInterval = reportInterval; - } - - @Override - public void accept(T t) { - pool.add(t); - } - - @Override - public void flush() throws IOException { - Instant nextReportTime = now().plus(reportInterval); - int width = String.valueOf(pool.size()).length(); - int i = 0; - for (T x : pool) { - i++; - if (now().isAfter(nextReportTime)) { - System.err.printf("%s%s/%d: %s%n", round(), pad(i, width), pool.size(), printer.apply(x)); - nextReportTime = now().plus(reportInterval); - } - base.accept(x); - } - pool.clear(); - base.flush(); - round++; - } - - private String round() { - if (round==1) { - // most of the time, there's only one round, so let's not bother - return ""; - } else { - return String.format("#%d ", round); - } - } - - @Override - public void close() throws IOException { - flush(); - } - - static String pad(int i, int width) { - String s = String.valueOf(i); - while (s.length() < width) { - s = " " + s; - } - return s; - } -} diff --git a/src/test/java/com/launchableinc/ingest/commits/AllTests.java b/src/test/java/com/launchableinc/ingest/commits/AllTests.java index dc1315cab..e5d2426ef 100644 --- a/src/test/java/com/launchableinc/ingest/commits/AllTests.java +++ b/src/test/java/com/launchableinc/ingest/commits/AllTests.java @@ -10,6 +10,6 @@ MainTest.class, FileChunkStreamerTest.class, SSLBypassTest.class, - ProgressReportingConsumerTest.class + ProgressReporterTest.class }) public class AllTests {} diff --git a/src/test/java/com/launchableinc/ingest/commits/CommitGraphCollectorTest.java b/src/test/java/com/launchableinc/ingest/commits/CommitGraphCollectorTest.java index b069bee94..b2afa82e5 100644 --- a/src/test/java/com/launchableinc/ingest/commits/CommitGraphCollectorTest.java +++ b/src/test/java/com/launchableinc/ingest/commits/CommitGraphCollectorTest.java @@ -177,13 +177,13 @@ public void header() throws Exception { CommitGraphCollector cgc = new CommitGraphCollector("test", mainrepo.getRepository()); cgc.collectFiles(true); cgc.new ByRepository(mainrepo.getRepository(), "main") - .transfer(Collections.emptyList(), c -> {}, + .collectFiles(Collections.emptyList(), new PassThroughTreeReceiverImpl(), FlushableConsumer.of(files::add)); - // header for the main repo, 'gitmodules', header for the sub repo, 'a', and 'x' in the sub repo - assertThat(files).hasSize(5); - VirtualFile header = files.get(2); + // header for the main repo, 'gitmodules' + assertThat(files).hasSize(2); + VirtualFile header = files.get(0); assertThat(header.path()).isEqualTo(CommitGraphCollector.HEADER_FILE); JsonNode tree = assertValidJson(header::writeTo).get("tree"); assertThat(tree.isArray()).isTrue(); @@ -193,7 +193,7 @@ cgc.new ByRepository(mainrepo.getRepository(), "main") paths.add(i.get("path").asText()); } - assertThat(paths).containsExactly("a", "x"); + assertThat(paths).containsExactly(".gitmodules", "sub"); } } diff --git a/src/test/java/com/launchableinc/ingest/commits/ProgressReporterTest.java b/src/test/java/com/launchableinc/ingest/commits/ProgressReporterTest.java new file mode 100644 index 000000000..b518c73d9 --- /dev/null +++ b/src/test/java/com/launchableinc/ingest/commits/ProgressReporterTest.java @@ -0,0 +1,95 @@ +package com.launchableinc.ingest.commits; + +import org.junit.Test; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import static com.google.common.truth.Truth.*; +import static java.util.Collections.*; + +public class ProgressReporterTest { + ProgressReporter pr = new ProgressReporter(String::valueOf, Duration.ofMillis(100)) { + int cc = 0; + int ww = 0; + @Override + protected void print(int c, int w, String x) { + super.print(c, w, x); + + // ensure numbers are monotonically increasing + assertThat(c).isAtLeast(cc); + assertThat(w).isAtLeast(ww); + cc = c; + ww = w; + } + }; + + /** + * Tests the most important bit -- that all items are processed. + */ + @Test + public void serial() throws Exception { + List done = new ArrayList<>(); + try (ProgressReporter.Consumer x = pr.newConsumer(FlushableConsumer.of(s -> { + done.add(s); + sleep(); + }))) { + for (int i = 0; i < 100; i++) { + x.accept("item " + i); + } + } + assertThat(done.size()).isEqualTo(100); + } + + /** + * Perform work in parallel and make sure they all do get processed. + */ + @Test + public void parallel() throws Exception { + Set done = synchronizedSet(new HashSet<>()); + + ExecutorService es = Executors.newFixedThreadPool(10); + List> all = new ArrayList<>(); + for (int i=0; i<10; i++) { + final int ii = i; + all.add(es.submit(() -> { + try (ProgressReporter.Consumer x = pr.newConsumer(FlushableConsumer.of(s -> {done.add(s);sleep();}))) { + for (int j = 0; j < 100; j++) { + x.accept("item " + (ii*100+j)); + } + return null; + } + })); + } + for (Future f : all) { + f.get(); + } + es.shutdown(); + + assertThat(done.size()).isEqualTo(1000); + for (int i=0; i<1000; i++) { + assertThat(done).contains("item " + i); + } + } + + private static void sleep() { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + throw new UnsupportedOperationException(); + } + } + + @Test + public void pad() { + assertThat(ProgressReporter.pad(5,3)).isEqualTo(" 5"); + assertThat(ProgressReporter.pad(15,3)).isEqualTo(" 15"); + assertThat(ProgressReporter.pad(1234,3)).isEqualTo("1234"); + } +} diff --git a/src/test/java/com/launchableinc/ingest/commits/ProgressReportingConsumerTest.java b/src/test/java/com/launchableinc/ingest/commits/ProgressReportingConsumerTest.java deleted file mode 100644 index 3c655a95c..000000000 --- a/src/test/java/com/launchableinc/ingest/commits/ProgressReportingConsumerTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.launchableinc.ingest.commits; - -import com.google.common.truth.Truth; -import org.junit.Test; - -import java.io.IOException; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -public class ProgressReportingConsumerTest { - @Test - public void basic() throws IOException { - List done = new ArrayList<>(); - try (ProgressReportingConsumer x = new ProgressReportingConsumer<>(FlushableConsumer.of(s -> {done.add(s);sleep();}), String::valueOf, Duration.ofMillis(100))) { - for (int i = 0; i < 100; i++) { - x.accept("item " + i); - } - } - Truth.assertThat(done.size()).isEqualTo(100); - } - - private static void sleep() { - try { - Thread.sleep(10); - } catch (InterruptedException e) { - throw new UnsupportedOperationException(); - } - } -} From 601bd1951930ade3ce99e6e08dabd306982b9391 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Thu, 11 Dec 2025 16:36:32 +0900 Subject: [PATCH 02/15] [chore] README for how to test this with local server --- README.md | 3 +++ .../ingest/commits/ChunkStreamer.java | 3 +-- .../ingest/commits/CommitGraphCollector.java | 23 +++++++---------- .../ingest/commits/FileChunkStreamer.java | 25 +++++++++++++------ .../ingest/commits/FlushableConsumer.java | 10 ++++++-- .../ingest/commits/ProgressReporter.java | 2 +- .../commits/CommitGraphCollectorTest.java | 12 +++------ .../ingest/commits/FileChunkStreamerTest.java | 8 +++--- 8 files changed, 47 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index d613e0d4c..029e74558 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,9 @@ configuration for [pre-commit](https://pre-commit.com). Install the hook with pipenv shell ``` +From this shell, run `pip install -e .` to create `launchable` wrapper script from the workspace. +This is useful to test the CLI with real/local server. + ## Run tests cli ```shell diff --git a/src/main/java/com/launchableinc/ingest/commits/ChunkStreamer.java b/src/main/java/com/launchableinc/ingest/commits/ChunkStreamer.java index 7be425e2a..d6ef08fce 100644 --- a/src/main/java/com/launchableinc/ingest/commits/ChunkStreamer.java +++ b/src/main/java/com/launchableinc/ingest/commits/ChunkStreamer.java @@ -2,7 +2,6 @@ import org.apache.http.entity.ContentProducer; -import java.io.Closeable; import java.io.IOException; import java.io.OutputStream; import java.io.UncheckedIOException; @@ -12,7 +11,7 @@ /** * Accepts T, buffers them, and writes them out as a batch. */ -abstract class ChunkStreamer implements FlushableConsumer, Closeable { +abstract class ChunkStreamer implements FlushableConsumer { /** * Encapsulation of how batches are sent. */ diff --git a/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java b/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java index 84c609c18..30951ebb7 100644 --- a/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java +++ b/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java @@ -92,7 +92,7 @@ public class CommitGraphCollector { */ private final Repository root; - private AtomicInteger commitsSent = new AtomicInteger(), filesSent = new AtomicInteger(); + private final AtomicInteger commitsSent = new AtomicInteger(), filesSent = new AtomicInteger(); private boolean collectCommitMessage, collectFiles; @@ -289,7 +289,7 @@ public void transfer( // this way, if the file collection fails, the server won't see this commit, so the future // "record commit" invocation will retry the file collection, thereby making the behavior idempotent. // TODO: file transfer can be parallelized more aggressively, where we send chunks in parallel - try (FileChunkStreamer fs = new FileChunkStreamer(fileSender, chunkSize); + try (FileChunkStreamer fs = new FileChunkStreamer(r.buildHeader(), fileSender, chunkSize); ProgressReporter.Consumer fsr = pr.newConsumer(fs)) { br.collectFiles(advertised, treeReceiver, fsr); } @@ -355,12 +355,14 @@ final class ByRepository implements AutoCloseable { private final ObjectReader objectReader; private final Set shallowCommits; + private final ObjectId headId; ByRepository(Repository git, String name) throws IOException { this.name = name; this.git = git; this.objectReader = git.newObjectReader(); this.shallowCommits = objectReader.getShallowCommits(); + this.headId = git.resolve("HEAD"); } void forEachSubModule(ExecutorService threadPool, IOConsumer consumer) throws IOException { @@ -453,7 +455,6 @@ private void parseEachCommit(RevWalk walk, Collection advertised, IOCo */ void collectFiles(Collection advertised, TreeReceiver treeReceiver, FlushableConsumer fileReceiver) throws IOException { try (TreeWalk treeWalk = new TreeWalk(git)) { - ObjectId headId = git.resolve("HEAD"); RevCommit start = git.parseCommit(headId); treeWalk.addTree(start.getTree()); @@ -500,15 +501,10 @@ void collectFiles(Collection advertised, TreeReceiver treeReceiver, Fl // Note(Konboi): To balance the order, since words like "test" and "spec" tend to appear // toward the end in alphabetical sorting. - if (!files.isEmpty()) { - fileReceiver.accept(buildHeader(start)); + Collections.shuffle(files); + for (VirtualFile f : files) { + fileReceiver.accept(f); filesSent.incrementAndGet(); - - Collections.shuffle(files); - for (VirtualFile f : files) { - fileReceiver.accept(f); - filesSent.incrementAndGet(); - } } fileReceiver.flush(); @@ -519,7 +515,7 @@ void collectFiles(Collection advertised, TreeReceiver treeReceiver, Fl * Creates a per repository "header" file as a {@link VirtualFile}. * Currently, this is just the list of files in the repository. */ - private VirtualFile buildHeader(RevCommit start) throws IOException { + VirtualFile buildHeader() throws IOException { ByteArrayOutputStream os = new ByteArrayOutputStream(); try (JsonGenerator w = new JsonFactory().createGenerator(os)) { w.setCodec(objectMapper); @@ -527,7 +523,7 @@ private VirtualFile buildHeader(RevCommit start) throws IOException { w.writeArrayFieldStart("tree"); try (TreeWalk tw = new TreeWalk(git)) { - tw.addTree(start.getTree()); + tw.addTree(git.parseCommit(headId).getTree()); tw.setRecursive(true); while (tw.next()) { @@ -555,7 +551,6 @@ void collectCommits(Collection advertised, Consumer commitRe // implementation optimization that's currently enabling this all the time walk.sort(RevSort.COMMIT_TIME_DESC, true); - ObjectId headId = git.resolve("HEAD"); walk.markStart(walk.parseCommit(headId)); // don't walk commits too far back. diff --git a/src/main/java/com/launchableinc/ingest/commits/FileChunkStreamer.java b/src/main/java/com/launchableinc/ingest/commits/FileChunkStreamer.java index 698c69438..fad3dab02 100644 --- a/src/main/java/com/launchableinc/ingest/commits/FileChunkStreamer.java +++ b/src/main/java/com/launchableinc/ingest/commits/FileChunkStreamer.java @@ -14,8 +14,11 @@ * Receives {@link GitFile}, buffers them, and writes them out in a gzipped tar file. */ final class FileChunkStreamer extends ChunkStreamer { - FileChunkStreamer(IOConsumer sender, int chunkSize) { + private final VirtualFile header; + + FileChunkStreamer(VirtualFile header, IOConsumer sender, int chunkSize) { super(sender, chunkSize); + this.header = header; } @Override @@ -23,14 +26,22 @@ protected void writeTo(List files, OutputStream os) throws IOExcept try (TarArchiveOutputStream tar = new TarArchiveOutputStream(os, "UTF-8")) { tar.setLongFileMode(LONGFILE_POSIX); + if (header!=null) { + write(header, tar); + } + for (VirtualFile f : files) { - TarArchiveEntry e = new TarArchiveEntry(f.path()); - e.setSize(f.size()); - e.setGroupName(f.blob().name()); // HACK - reuse the group name field to store the blob ID - tar.putArchiveEntry(e); - f.writeTo(tar); - tar.closeArchiveEntry(); + write(f, tar); } } } + + private static void write(VirtualFile f, TarArchiveOutputStream tar) throws IOException { + TarArchiveEntry e = new TarArchiveEntry(f.path()); + e.setSize(f.size()); + e.setGroupName(f.blob().name()); // HACK - reuse the group name field to store the blob ID + tar.putArchiveEntry(e); + f.writeTo(tar); + tar.closeArchiveEntry(); + } } diff --git a/src/main/java/com/launchableinc/ingest/commits/FlushableConsumer.java b/src/main/java/com/launchableinc/ingest/commits/FlushableConsumer.java index 7eb8c701d..8e3272d14 100644 --- a/src/main/java/com/launchableinc/ingest/commits/FlushableConsumer.java +++ b/src/main/java/com/launchableinc/ingest/commits/FlushableConsumer.java @@ -1,12 +1,13 @@ package com.launchableinc.ingest.commits; +import java.io.Closeable; import java.io.IOException; import java.util.function.Consumer; /** * Consumers that spool items it accepts and process them in bulk. */ -public interface FlushableConsumer extends Consumer { +public interface FlushableConsumer extends Consumer, Closeable { /** * Process all items that have been accepted so far. */ @@ -15,7 +16,12 @@ public interface FlushableConsumer extends Consumer { static FlushableConsumer of(Consumer c) { return new FlushableConsumer() { @Override - public void flush() throws IOException { + public void flush() { + // noop + } + + @Override + public void close() { // noop } diff --git a/src/main/java/com/launchableinc/ingest/commits/ProgressReporter.java b/src/main/java/com/launchableinc/ingest/commits/ProgressReporter.java index 1de1f19e3..d5ba0e7de 100644 --- a/src/main/java/com/launchableinc/ingest/commits/ProgressReporter.java +++ b/src/main/java/com/launchableinc/ingest/commits/ProgressReporter.java @@ -38,7 +38,7 @@ class ProgressReporter { /** * Deals with one serial stream of work. */ - class Consumer implements FlushableConsumer, AutoCloseable { + class Consumer implements FlushableConsumer { private final FlushableConsumer base; private final List pool = new ArrayList<>(); diff --git a/src/test/java/com/launchableinc/ingest/commits/CommitGraphCollectorTest.java b/src/test/java/com/launchableinc/ingest/commits/CommitGraphCollectorTest.java index b2afa82e5..117e6ab85 100644 --- a/src/test/java/com/launchableinc/ingest/commits/CommitGraphCollectorTest.java +++ b/src/test/java/com/launchableinc/ingest/commits/CommitGraphCollectorTest.java @@ -90,7 +90,7 @@ public void bareRepo() throws Exception { try (Repository r = Git.open(barerepoDir).getRepository()) { CommitGraphCollector cgc = collectCommit(r, ImmutableList.of()); assertThat(cgc.getCommitsSent()).isEqualTo(1); - assertThat(cgc.getFilesSent()).isEqualTo(2); // header + .gitmodules + assertThat(cgc.getFilesSent()).isEqualTo(1); // .gitmodules } } @@ -121,7 +121,7 @@ public void chunking() throws Exception { 2); } assertThat(councCommitChunks[0]).isEqualTo(2); - assertThat(countFilesChunks[0]).isEqualTo(3); // header, a, .gitmodules, and header, sub/x, 5 files, 3 chunks + assertThat(countFilesChunks[0]).isEqualTo(2); // a, .gitmodules, and sub/x, 3 files, 2 chunks } private void assertValidTar(ContentProducer content) throws IOException { @@ -176,14 +176,8 @@ public void header() throws Exception { CommitGraphCollector cgc = new CommitGraphCollector("test", mainrepo.getRepository()); cgc.collectFiles(true); - cgc.new ByRepository(mainrepo.getRepository(), "main") - .collectFiles(Collections.emptyList(), - new PassThroughTreeReceiverImpl(), - FlushableConsumer.of(files::add)); - // header for the main repo, 'gitmodules' - assertThat(files).hasSize(2); - VirtualFile header = files.get(0); + VirtualFile header = cgc.new ByRepository(mainrepo.getRepository(), "main").buildHeader(); assertThat(header.path()).isEqualTo(CommitGraphCollector.HEADER_FILE); JsonNode tree = assertValidJson(header::writeTo).get("tree"); assertThat(tree.isArray()).isTrue(); diff --git a/src/test/java/com/launchableinc/ingest/commits/FileChunkStreamerTest.java b/src/test/java/com/launchableinc/ingest/commits/FileChunkStreamerTest.java index 3d9cc7e70..01b946558 100644 --- a/src/test/java/com/launchableinc/ingest/commits/FileChunkStreamerTest.java +++ b/src/test/java/com/launchableinc/ingest/commits/FileChunkStreamerTest.java @@ -20,7 +20,7 @@ public class FileChunkStreamerTest { @Test public void no_op_if_no_content() throws Exception { - try (FileChunkStreamer fs = new FileChunkStreamer(content -> fail(), 2)) { + try (FileChunkStreamer fs = new FileChunkStreamer(null, content -> fail(), 2)) { // no write } } @@ -28,13 +28,13 @@ public void no_op_if_no_content() throws Exception { @Test public void basics() throws Exception { int[] count = new int[1]; - try (FileChunkStreamer fs = new FileChunkStreamer(content -> { + try (FileChunkStreamer fs = new FileChunkStreamer(new VirtualFileImpl("header.txt"), content -> { switch(count[0]++) { case 0: - assertThat(readEntries(content)).containsExactly("foo.txt", "bar.txt").inOrder(); + assertThat(readEntries(content)).containsExactly("header.txt", "foo.txt", "bar.txt").inOrder(); break; case 1: - assertThat(readEntries(content)).containsExactly("zot.txt").inOrder(); + assertThat(readEntries(content)).containsExactly("header.txt", "zot.txt").inOrder(); break; default: fail(); From 26db053cb2c30bb623824df4d077555be2223003 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Wed, 31 Dec 2025 12:02:46 -0800 Subject: [PATCH 03/15] Restore the locality of file submission We are going back & forth on this one, but with the introduction of async file submission and additional algorithm enhancements on the server side to use more LLMs, overall we'd be better off with locality --- .../ingest/commits/CommitGraphCollector.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java b/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java index 30951ebb7..4c4864b31 100644 --- a/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java +++ b/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java @@ -52,7 +52,6 @@ import java.time.Duration; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -68,8 +67,8 @@ import java.util.function.Supplier; import java.util.zip.GZIPOutputStream; -import static com.google.common.collect.ImmutableList.*; -import static java.util.Arrays.*; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static java.util.Arrays.stream; /** * Compares what commits the local repository and the remote repository have, then send delta over. @@ -497,11 +496,8 @@ void collectFiles(Collection advertised, TreeReceiver treeReceiver, Fl } // Now let the server select the files it actually wants to see - List files = new ArrayList<>(treeReceiver.response()); + Collection files = treeReceiver.response(); - // Note(Konboi): To balance the order, since words like "test" and "spec" tend to appear - // toward the end in alphabetical sorting. - Collections.shuffle(files); for (VirtualFile f : files) { fileReceiver.accept(f); filesSent.incrementAndGet(); From aad347de65073033ed3975f5cdd868a623c83023 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Wed, 31 Dec 2025 12:06:35 -0800 Subject: [PATCH 04/15] In anticipation of parallel concurrent sender, stop reusing spool --- .../java/com/launchableinc/ingest/commits/ChunkStreamer.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/launchableinc/ingest/commits/ChunkStreamer.java b/src/main/java/com/launchableinc/ingest/commits/ChunkStreamer.java index d6ef08fce..29605ffc1 100644 --- a/src/main/java/com/launchableinc/ingest/commits/ChunkStreamer.java +++ b/src/main/java/com/launchableinc/ingest/commits/ChunkStreamer.java @@ -17,7 +17,7 @@ abstract class ChunkStreamer implements FlushableConsumer { */ private final IOConsumer sender; private final int chunkSize; - private final List spool = new ArrayList<>(); + private List spool = new ArrayList<>(); ChunkStreamer(IOConsumer sender, int chunkSize) { this.sender = sender; @@ -49,6 +49,7 @@ public void flush() throws IOException { try { sender.accept(os -> writeTo(spool,os)); + // let sender own the list -- do not reuse } finally { spool.clear(); } From 183ee56236913330f3249e7ee070b5118a17d08d Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Wed, 31 Dec 2025 12:08:20 -0800 Subject: [PATCH 05/15] Bumping up the chunk size Recent algorithm change on the server side prefers a larger batch for more efficiency --- .../com/launchableinc/ingest/commits/CommitGraphCollector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java b/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java index 4c4864b31..381391eee 100644 --- a/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java +++ b/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java @@ -169,7 +169,7 @@ public void transfer(URL service, Authenticator authenticator, boolean enableTim (ContentProducer commits) -> sendCommits(service, client, commits), new TreeReceiverImpl(service, client), (ContentProducer files) -> sendFiles(service, client, files), - 256); + 1024); } } From b128c6dd731e6f17e099ccbca6a3c93ccbd105c7 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Wed, 31 Dec 2025 12:16:03 -0800 Subject: [PATCH 06/15] Refactoring --- .../ingest/commits/CommitGraphCollector.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java b/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java index 381391eee..83c45e115 100644 --- a/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java +++ b/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java @@ -81,6 +81,7 @@ public class CommitGraphCollector { * Repository header is sent using this reserved file name */ static final String HEADER_FILE = ".launchable"; + private static final String APPLICATION_JSON = "application/json"; private final String rootName; @@ -198,6 +199,7 @@ private void sendFiles(URL service, CloseableHttpClient client, ContentProducer URL url = new URL(service, "collect/files"); HttpPost request = new HttpPost(url.toExternalForm()); request.setHeader("Content-Type", "application/octet-stream"); + request.setHeader("Accept", "application/json; mode=async"); // no content encoding, since .tar.gz is considered content request.setEntity(new EntityTemplate(os -> files.writeTo(new GZIPOutputStream(os)))); @@ -231,6 +233,14 @@ private void sendFiles(URL service, CloseableHttpClient client, ContentProducer handleError(url, client.execute(request)).close(); } + private T readResponse(CloseableHttpResponse response, Class type) throws IOException { + try (JsonParser parser = new JsonFactory().createParser(response.getEntity().getContent())) { + return objectMapper.readValue(parser, type); + } finally { + response.close(); + } + } + private void honorControlHeaders(HttpResponse response) { // When a user incorrectly configures shallow clone, the incremental nature of commit collection // makes it really hard for us and users to collaboratively reset and repopulate the commit data. @@ -248,9 +258,8 @@ private void honorControlHeaders(HttpResponse response) { } } - private ImmutableList getAdvertisedRefs(HttpResponse response) throws IOException { - JsonParser parser = new JsonFactory().createParser(response.getEntity().getContent()); - String[] ids = objectMapper.readValue(parser, String[].class); + private ImmutableList getAdvertisedRefs(CloseableHttpResponse response) throws IOException { + String[] ids = readResponse(response, String[].class); return stream(ids) .map( s -> { @@ -678,7 +687,7 @@ public Collection response() { try { URL url = new URL(service, "collect/tree"); HttpPost request = new HttpPost(url.toExternalForm()); - request.setHeader("Content-Type", "application/json"); + request.setHeader("Content-Type", APPLICATION_JSON); request.setHeader("Content-Encoding", "gzip"); request.setEntity(new EntityTemplate(raw -> { try (OutputStream os = new GZIPOutputStream(raw)) { @@ -695,10 +704,7 @@ public Collection response() { } // even in dry run, this method needs to execute in order to show what files we'll be collecting - try (CloseableHttpResponse response = handleError(url, client.execute(request)); - JsonParser parser = new JsonFactory().createParser(response.getEntity().getContent())) { - return select(objectMapper.readValue(parser, String[].class)); - } + return select(readResponse(handleError(url, client.execute(request)), String[].class)); } catch (IOException e) { throw new UncheckedIOException(e); } finally { From c079d20150471a8d5541d53305e3017faab010e4 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Wed, 31 Dec 2025 12:28:19 -0800 Subject: [PATCH 07/15] [AIENG-289] Implemented asynchronous file collection --- .../ingest/commits/BackgroundWorkStatus.java | 8 +++++++ .../ingest/commits/CommitGraphCollector.java | 24 ++++++++++++++++++- .../JSAsyncFileCollectionProgress.java | 10 ++++++++ .../JSAsyncFileCollectionResponse.java | 8 +++++++ 4 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/launchableinc/ingest/commits/BackgroundWorkStatus.java create mode 100644 src/main/java/com/launchableinc/ingest/commits/JSAsyncFileCollectionProgress.java create mode 100644 src/main/java/com/launchableinc/ingest/commits/JSAsyncFileCollectionResponse.java diff --git a/src/main/java/com/launchableinc/ingest/commits/BackgroundWorkStatus.java b/src/main/java/com/launchableinc/ingest/commits/BackgroundWorkStatus.java new file mode 100644 index 000000000..3a3708473 --- /dev/null +++ b/src/main/java/com/launchableinc/ingest/commits/BackgroundWorkStatus.java @@ -0,0 +1,8 @@ +package com.launchableinc.ingest.commits; + +public enum BackgroundWorkStatus { + IN_PROGRESS, + SUCCEEDED, + FAILED, + ABANDONED +} diff --git a/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java b/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java index 83c45e115..88c37ec94 100644 --- a/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java +++ b/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java @@ -230,7 +230,29 @@ private void sendFiles(URL service, CloseableHttpClient client, ContentProducer if (dryRun) { return; } - handleError(url, client.execute(request)).close(); + + int workId = readResponse(handleError(url, client.execute(request)), JSAsyncFileCollectionResponse.class).workId; + URL workUrl = new URL(service, "collect/files/work/" + workId); + while (true) { + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + // not expecting this to happen, sufficient to fail + throw new IOException(); + } + // TODO: utilize numFiles for progress report + HttpGet get = new HttpGet(url.toExternalForm()); + JSAsyncFileCollectionProgress status = readResponse(handleError(workUrl,client.execute(get)), JSAsyncFileCollectionProgress.class); + switch (status.status) { + case IN_PROGRESS: + break; // keep polling + case SUCCEEDED: + return; + case FAILED: + case ABANDONED: + throw new IOException("File collection failed: " + status.status); + } + } } private T readResponse(CloseableHttpResponse response, Class type) throws IOException { diff --git a/src/main/java/com/launchableinc/ingest/commits/JSAsyncFileCollectionProgress.java b/src/main/java/com/launchableinc/ingest/commits/JSAsyncFileCollectionProgress.java new file mode 100644 index 000000000..ac736f8d7 --- /dev/null +++ b/src/main/java/com/launchableinc/ingest/commits/JSAsyncFileCollectionProgress.java @@ -0,0 +1,10 @@ +package com.launchableinc.ingest.commits; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class JSAsyncFileCollectionProgress { + @JsonProperty + public BackgroundWorkStatus status; + @JsonProperty + public int filesProcessed; +} diff --git a/src/main/java/com/launchableinc/ingest/commits/JSAsyncFileCollectionResponse.java b/src/main/java/com/launchableinc/ingest/commits/JSAsyncFileCollectionResponse.java new file mode 100644 index 000000000..c506f5938 --- /dev/null +++ b/src/main/java/com/launchableinc/ingest/commits/JSAsyncFileCollectionResponse.java @@ -0,0 +1,8 @@ +package com.launchableinc.ingest.commits; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class JSAsyncFileCollectionResponse { + @JsonProperty + public int workId; +} From 35dea06ca088b59b3fbf3c679a8a79275078132a Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Wed, 31 Dec 2025 15:09:35 -0800 Subject: [PATCH 08/15] [AIENG-289] Implemented parallel file collection --- CLAUDE.md | 2 + .../commits/BoundedExecutorService.java | 73 +++++++++++ .../ingest/commits/CommitGraphCollector.java | 46 ++++++- .../ingest/commits/ConcurrentConsumer.java | 45 +++++++ .../ingest/commits/AllTests.java | 4 +- .../commits/BoundedExecutorServiceTest.java | 93 +++++++++++++ .../commits/ConcurrentConsumerTest.java | 124 ++++++++++++++++++ 7 files changed, 379 insertions(+), 8 deletions(-) create mode 100644 CLAUDE.md create mode 100644 src/main/java/com/launchableinc/ingest/commits/BoundedExecutorService.java create mode 100644 src/main/java/com/launchableinc/ingest/commits/ConcurrentConsumer.java create mode 100644 src/test/java/com/launchableinc/ingest/commits/BoundedExecutorServiceTest.java create mode 100644 src/test/java/com/launchableinc/ingest/commits/ConcurrentConsumerTest.java diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..b818f3fb7 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,2 @@ +- In Java code, use JUnit 4 & Google truth +- See README.md & CONTRIBUTING.md diff --git a/src/main/java/com/launchableinc/ingest/commits/BoundedExecutorService.java b/src/main/java/com/launchableinc/ingest/commits/BoundedExecutorService.java new file mode 100644 index 000000000..d2d745d05 --- /dev/null +++ b/src/main/java/com/launchableinc/ingest/commits/BoundedExecutorService.java @@ -0,0 +1,73 @@ +package com.launchableinc.ingest.commits; + +import java.util.List; +import java.util.concurrent.AbstractExecutorService; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +/** + * {@link ExecutorService} decorator that limits the number of concurrent tasks, + * and make the caller block when the limit is reached. + */ +class BoundedExecutorService extends AbstractExecutorService { + private final ExecutorService delegate; + private final Semaphore semaphore; + + BoundedExecutorService(int limit) { + this(Executors.newFixedThreadPool(limit), limit); + } + + BoundedExecutorService(ExecutorService delegate, int limit) { + this.delegate = delegate; + this.semaphore = new Semaphore(limit); + } + + @Override + public void execute(Runnable command) { + try { + semaphore.acquire(); + } catch (InterruptedException e) { + throw new RejectedExecutionException(e); + } + try { + delegate.execute(() -> { + try { + command.run(); + } finally { + semaphore.release(); + } + }); + } catch (RejectedExecutionException e) { + semaphore.release(); + throw e; + } + } + + @Override + public void shutdown() { + delegate.shutdown(); + } + + @Override + public List shutdownNow() { + return delegate.shutdownNow(); + } + + @Override + public boolean isShutdown() { + return delegate.isShutdown(); + } + + @Override + public boolean isTerminated() { + return delegate.isTerminated(); + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + return delegate.awaitTermination(timeout, unit); + } +} diff --git a/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java b/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java index 88c37ec94..db40eb6a9 100644 --- a/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java +++ b/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java @@ -61,6 +61,8 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; @@ -69,6 +71,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static java.util.Arrays.stream; +import static java.util.concurrent.TimeUnit.MILLISECONDS; /** * Compares what commits the local repository and the remote repository have, then send delta over. @@ -307,19 +310,47 @@ public void transfer( throws IOException { ByRepository r = new ByRepository(root, rootName); - ExecutorService es = Executors.newFixedThreadPool(4); - // for debugging -// ExecutorService es = MoreExecutors.newDirectExecutorService(); + /* + Concurrency design + ================== + + The work ahead of us is: + - for each repository + - send all the files we need to send + - then record commits + + Commit recording has to happen after file sending, to ensure the server is in possession of all the files + relevant to the commits being recorded. This constraint allows for two places of parallelism: + 1. process multiple repositories in parallel + 2. within a repository, send (or "collect") files in parallel + + We exploit both of them. But by using two pools. The "scan" pool is used to parallelize the outer loop of + "for each repository". The "transfer" pool is used to parallelize file collection network I/O with the server. + + Those are matched M:N -- any scan thread can hand over work to any available transfer thread. This way, + the user gets speed boost, whether it's a single massive repo or a lot of small repos. + The separate transfer pool limit cap the concurrent server connections, to avoid overwhelming the server. + + Both thread pool are bounded, meaning the work producer gets blocked until the work consumer keeps up. + This creates natural work throttling, keeping overall memory consumption in check + */ + + ExecutorService scanPool = new BoundedExecutorService(4); +// ExecutorService scanPool = MoreExecutors.newDirectExecutorService(); // for debugging + ExecutorService transferPool = new BoundedExecutorService(4); ProgressReporter pr = new ProgressReporter<>(VirtualFile::path, Duration.ofSeconds(3)); try { - r.forEachSubModule(es, br -> { + r.forEachSubModule(scanPool, br -> { if (collectFiles) { // record all the necessary BLOBs first, before attempting to record its commit. // this way, if the file collection fails, the server won't see this commit, so the future // "record commit" invocation will retry the file collection, thereby making the behavior idempotent. - // TODO: file transfer can be parallelized more aggressively, where we send chunks in parallel - try (FileChunkStreamer fs = new FileChunkStreamer(r.buildHeader(), fileSender, chunkSize); + + // ConcurrentConsumer parallelizes file sending within a repository. When it leads the try block + // it ensures all the submissions have completed. + try (ConcurrentConsumer parallel = new ConcurrentConsumer<>(fileSender, transferPool); + FileChunkStreamer fs = new FileChunkStreamer(r.buildHeader(), parallel, chunkSize); ProgressReporter.Consumer fsr = pr.newConsumer(fs)) { br.collectFiles(advertised, treeReceiver, fsr); } @@ -336,7 +367,8 @@ public void transfer( } }); } finally { - es.shutdown(); + scanPool.shutdown(); + transferPool.shutdown(); } } diff --git a/src/main/java/com/launchableinc/ingest/commits/ConcurrentConsumer.java b/src/main/java/com/launchableinc/ingest/commits/ConcurrentConsumer.java new file mode 100644 index 000000000..fccaa2b53 --- /dev/null +++ b/src/main/java/com/launchableinc/ingest/commits/ConcurrentConsumer.java @@ -0,0 +1,45 @@ +package com.launchableinc.ingest.commits; + +import java.io.Closeable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.function.Consumer; + +/** + * A decorator of {@link IOConsumer}/{@link Consumer} that concurrently/asynchronously processes accepted items. + *

+ * {@link #close()} method would wait for all the processing to complete, and fail if any of them throw an exception. + */ +class ConcurrentConsumer implements IOConsumer, Consumer, Closeable { + private final IOConsumer delegate; + private final ExecutorService executor; + private final List> jobs = new ArrayList<>(); + + ConcurrentConsumer(IOConsumer delegate, ExecutorService executor) { + this.delegate = delegate; + this.executor = executor; + } + + @Override + public void accept(T t) { + jobs.add(executor.submit(() -> { + delegate.accept(t); + return null; // to use Callable interface so as not to wrap an exception + })); + } + + @Override + public void close() throws IOException { + try { + for (Future job : jobs) { + job.get(); + } + } catch (InterruptedException|ExecutionException e) { + throw new IOException(e); + } + } +} diff --git a/src/test/java/com/launchableinc/ingest/commits/AllTests.java b/src/test/java/com/launchableinc/ingest/commits/AllTests.java index e5d2426ef..1124da7df 100644 --- a/src/test/java/com/launchableinc/ingest/commits/AllTests.java +++ b/src/test/java/com/launchableinc/ingest/commits/AllTests.java @@ -6,9 +6,11 @@ @RunWith(Suite.class) @SuiteClasses({ + BoundedExecutorServiceTest.class, CommitGraphCollectorTest.class, - MainTest.class, + ConcurrentConsumerTest.class, FileChunkStreamerTest.class, + MainTest.class, SSLBypassTest.class, ProgressReporterTest.class }) diff --git a/src/test/java/com/launchableinc/ingest/commits/BoundedExecutorServiceTest.java b/src/test/java/com/launchableinc/ingest/commits/BoundedExecutorServiceTest.java new file mode 100644 index 000000000..2c45411ab --- /dev/null +++ b/src/test/java/com/launchableinc/ingest/commits/BoundedExecutorServiceTest.java @@ -0,0 +1,93 @@ +package com.launchableinc.ingest.commits; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.google.common.truth.Truth.assertThat; +import static java.util.concurrent.TimeUnit.SECONDS; + +public class BoundedExecutorServiceTest { + @Test + public void basicExecution() throws InterruptedException { + BoundedExecutorService executor = new BoundedExecutorService(2); + List results = Collections.synchronizedList(new ArrayList<>()); + + try { + executor.execute(() -> results.add(1)); + executor.execute(() -> results.add(2)); + executor.execute(() -> results.add(3)); + } finally { + executor.shutdown(); + executor.awaitTermination(5, SECONDS); + } + + assertThat(results).containsExactly(1, 2, 3); + } + + @Test + public void enforcesConcurrencyLimit() throws InterruptedException { + int limit = 2; + BoundedExecutorService executor = new BoundedExecutorService(limit); + CountDownLatch startLatch = new CountDownLatch(limit); + CountDownLatch releaseLatch = new CountDownLatch(1); + AtomicInteger concurrentCount = new AtomicInteger(0); + AtomicInteger maxConcurrent = new AtomicInteger(0); + AtomicInteger submittedCount = new AtomicInteger(0); + + Runnable task = () -> { + try { + int current = concurrentCount.incrementAndGet(); + maxConcurrent.updateAndGet(max -> Math.max(max, current)); + startLatch.countDown(); + releaseLatch.await(); + concurrentCount.decrementAndGet(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }; + + // Use a separate thread to sequentially submit tasks + Thread submitter = new Thread(() -> { + for (int i = 0; i < 4; i++) { + executor.execute(task); + submittedCount.incrementAndGet(); + } + }); + + try { + submitter.start(); + + // Wait for limit number of tasks to start + startLatch.await(1, SECONDS); + + // Should have exactly 'limit' tasks running + assertThat(concurrentCount.get()).isEqualTo(limit); + + // The submitter thread should be blocked trying to submit the 3rd task + // Only 2 tasks should have been submitted successfully + assertThat(submittedCount.get()).isEqualTo(2); + + // Release all tasks + releaseLatch.countDown(); + + // Wait for submitter to finish + submitter.join(5000); + } finally { + executor.shutdown(); + executor.awaitTermination(5, SECONDS); + } + + // Verify that concurrent execution never exceeded the limit + assertThat(maxConcurrent.get()).isAtMost(limit); + // All 4 tasks should have eventually been submitted + assertThat(submittedCount.get()).isEqualTo(4); + } +} diff --git a/src/test/java/com/launchableinc/ingest/commits/ConcurrentConsumerTest.java b/src/test/java/com/launchableinc/ingest/commits/ConcurrentConsumerTest.java new file mode 100644 index 000000000..a448ab97e --- /dev/null +++ b/src/test/java/com/launchableinc/ingest/commits/ConcurrentConsumerTest.java @@ -0,0 +1,124 @@ +package com.launchableinc.ingest.commits; + +import org.junit.Test; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; + +public class ConcurrentConsumerTest { + @Test + public void basicProcessing() throws IOException { + ExecutorService executor = Executors.newFixedThreadPool(2); + List processed = Collections.synchronizedList(new ArrayList<>()); + + try (ConcurrentConsumer consumer = new ConcurrentConsumer<>(processed::add, executor)) { + consumer.accept(1); + consumer.accept(2); + consumer.accept(3); + } finally { + executor.shutdown(); + } + + assertThat(processed.size()).isEqualTo(3); + assertThat(processed).contains(1); + assertThat(processed).contains(2); + assertThat(processed).contains(3); + } + + @Test + public void concurrentProcessing() throws IOException, InterruptedException { + ExecutorService executor = Executors.newFixedThreadPool(3); + CountDownLatch latch = new CountDownLatch(3); + AtomicInteger concurrentCount = new AtomicInteger(0); + AtomicInteger maxConcurrent = new AtomicInteger(0); + + IOConsumer slowConsumer = (i) -> { + try { + int current = concurrentCount.incrementAndGet(); + maxConcurrent.updateAndGet(max -> Math.max(max, current)); + latch.countDown(); + + // Wait for all three to be running concurrently + latch.await(); + Thread.sleep(50); + concurrentCount.decrementAndGet(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }; + + try (ConcurrentConsumer consumer = new ConcurrentConsumer<>(slowConsumer, executor)) { + consumer.accept(1); + consumer.accept(2); + consumer.accept(3); + } finally { + executor.shutdown(); + } + + assertThat(maxConcurrent.get()).isEqualTo(3); + } + + @Test + public void exceptionPropagation() { + ExecutorService executor = Executors.newFixedThreadPool(2); + + IOConsumer throwingConsumer = (i) -> { + if (i == 2) { + throw new IOException("Test exception"); + } + }; + + ConcurrentConsumer consumer = new ConcurrentConsumer<>(throwingConsumer, executor); + consumer.accept(1); + consumer.accept(2); + consumer.accept(3); + + // Expected - exception should be thrown on close + try { + consumer.close(); + fail("Expected IOException was not thrown"); + } catch (IOException e) { + assertThat(e.getMessage()).contains("Test exception"); + } finally { + executor.shutdown(); + } + } + + @Test + public void closeWaitsForCompletion() throws IOException { + ExecutorService executor = Executors.newFixedThreadPool(2); + AtomicInteger completed = new AtomicInteger(0); + + IOConsumer slowConsumer = (i) -> { + try { + Thread.sleep(100); + completed.incrementAndGet(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }; + + try (ConcurrentConsumer consumer = new ConcurrentConsumer<>(slowConsumer, executor)) { + consumer.accept(1); + consumer.accept(2); + consumer.accept(3); + + // Items should still be processing + assertThat(completed.get()).isLessThan(3); + } finally { + executor.shutdown(); + } + + // After close(), all items should be completed + assertThat(completed.get()).isEqualTo(3); + } +} From b08dcb70c3fb6f81dd9eb32c2153db0e0e6267c1 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Wed, 31 Dec 2025 15:20:26 -0800 Subject: [PATCH 09/15] Added short instruction --- CLAUDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index b818f3fb7..596b3eef1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,2 +1,2 @@ -- In Java code, use JUnit 4 & Google truth +- In Java code, use JUnit 4 & Google truth. Test needs to be added to AllTests.java or else it won't run. - See README.md & CONTRIBUTING.md From 8a3d0b6bfe562497fdd88782e8fb9c730ce2944c Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Fri, 2 Jan 2026 09:33:30 -0800 Subject: [PATCH 10/15] Wrong logic to let the sender own the list. In not only one but two separate ways! --- .../launchableinc/ingest/commits/ChunkStreamer.java | 13 +++++++------ .../ingest/commits/CommitGraphCollector.java | 4 ++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/launchableinc/ingest/commits/ChunkStreamer.java b/src/main/java/com/launchableinc/ingest/commits/ChunkStreamer.java index 29605ffc1..3c6bb08d2 100644 --- a/src/main/java/com/launchableinc/ingest/commits/ChunkStreamer.java +++ b/src/main/java/com/launchableinc/ingest/commits/ChunkStreamer.java @@ -1,5 +1,6 @@ package com.launchableinc.ingest.commits; +import com.google.common.collect.ImmutableList; import org.apache.http.entity.ContentProducer; import java.io.IOException; @@ -47,12 +48,12 @@ public void flush() throws IOException { return; } - try { - sender.accept(os -> writeTo(spool,os)); - // let sender own the list -- do not reuse - } finally { - spool.clear(); - } + // let sender own the list -- do not reuse + // for this to work, we need to resolve `this.spool` here, not in the lambda + List ref = this.spool; + this.spool = new ArrayList<>(); + + sender.accept(os -> writeTo(ref,os)); } protected abstract void writeTo(List spool, OutputStream os) throws IOException; diff --git a/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java b/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java index db40eb6a9..f136e0273 100644 --- a/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java +++ b/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java @@ -244,7 +244,7 @@ private void sendFiles(URL service, CloseableHttpClient client, ContentProducer throw new IOException(); } // TODO: utilize numFiles for progress report - HttpGet get = new HttpGet(url.toExternalForm()); + HttpGet get = new HttpGet(workUrl.toExternalForm()); JSAsyncFileCollectionProgress status = readResponse(handleError(workUrl,client.execute(get)), JSAsyncFileCollectionProgress.class); switch (status.status) { case IN_PROGRESS: @@ -253,7 +253,7 @@ private void sendFiles(URL service, CloseableHttpClient client, ContentProducer return; case FAILED: case ABANDONED: - throw new IOException("File collection failed: " + status.status); + throw new IOException("File collection (workId="+workId+") failed: " + status.status); } } } From 6083d8c0c38deff1c32e6fb915a15cd051deb311 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Fri, 2 Jan 2026 09:48:09 -0800 Subject: [PATCH 11/15] Moved out HTTP client usage idiom --- .../ingest/commits/ChunkStreamer.java | 1 - .../ingest/commits/CommitGraphCollector.java | 83 ++++++++----------- .../ingest/commits/FileChunkStreamer.java | 10 ++- .../launchableinc/ingest/commits/GitFile.java | 11 +-- .../ingest/commits/LaunchableHttpClient.java | 67 +++++++++++++++ .../commits/CommitGraphCollectorTest.java | 17 ++-- .../ingest/commits/FileChunkStreamerTest.java | 3 +- 7 files changed, 127 insertions(+), 65 deletions(-) create mode 100644 src/main/java/com/launchableinc/ingest/commits/LaunchableHttpClient.java diff --git a/src/main/java/com/launchableinc/ingest/commits/ChunkStreamer.java b/src/main/java/com/launchableinc/ingest/commits/ChunkStreamer.java index 3c6bb08d2..80062579e 100644 --- a/src/main/java/com/launchableinc/ingest/commits/ChunkStreamer.java +++ b/src/main/java/com/launchableinc/ingest/commits/ChunkStreamer.java @@ -1,6 +1,5 @@ package com.launchableinc.ingest.commits; -import com.google.common.collect.ImmutableList; import org.apache.http.entity.ContentProducer; import java.io.IOException; diff --git a/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java b/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java index f136e0273..6be7bd34b 100644 --- a/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java +++ b/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java @@ -7,18 +7,15 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.collect.ImmutableList; -import com.google.common.io.CharStreams; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentProducer; import org.apache.http.entity.EntityTemplate; -import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm; import org.eclipse.jgit.diff.DiffEntry; @@ -44,11 +41,9 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStreamReader; import java.io.OutputStream; import java.io.UncheckedIOException; import java.net.URL; -import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.ArrayList; import java.util.Collection; @@ -59,10 +54,7 @@ import java.util.Set; import java.util.Vector; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; -import java.util.concurrent.SynchronousQueue; -import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; @@ -71,7 +63,6 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static java.util.Arrays.stream; -import static java.util.concurrent.TimeUnit.MILLISECONDS; /** * Compares what commits the local repository and the remote repository have, then send delta over. @@ -156,14 +147,14 @@ public void transfer(URL service, Authenticator authenticator, boolean enableTim .setSocketTimeout(HTTP_TIMEOUT_MILLISECONDS).build(); builder.setDefaultRequestConfig(config); } - try (CloseableHttpClient client = builder.build()) { + try (LaunchableHttpClient client = new LaunchableHttpClient(builder.build())) { latestUrl = new URL(service, "latest"); if (outputAuditLog()) { System.err.printf( "AUDIT:launchable:%ssend request method:get path: %s%n", dryRunPrefix(), latestUrl); } - CloseableHttpResponse latestResponse = client.execute(new HttpGet(latestUrl.toExternalForm())); - ImmutableList advertised = getAdvertisedRefs(handleError(latestUrl, latestResponse)); + CloseableHttpResponse latestResponse = client.httpGet(latestUrl); + ImmutableList advertised = getAdvertisedRefs(latestResponse); honorControlHeaders(latestResponse); // every time a new stream is needed, supply ByteArrayOutputStream, and when the data is all @@ -177,7 +168,7 @@ public void transfer(URL service, Authenticator authenticator, boolean enableTim } } - private void sendCommits(URL service, CloseableHttpClient client, ContentProducer commits) throws IOException { + private void sendCommits(URL service, LaunchableHttpClient client, ContentProducer commits) throws IOException { URL url = new URL(service, "collect"); HttpPost request = new HttpPost(url.toExternalForm()); request.setHeader("Content-Type", "application/json"); @@ -195,10 +186,10 @@ private void sendCommits(URL service, CloseableHttpClient client, ContentProduce if (dryRun) { return; } - handleError(url, client.execute(request)).close(); + client.httpPost(request).close(); } - private void sendFiles(URL service, CloseableHttpClient client, ContentProducer files) throws IOException { + private void sendFiles(URL service, LaunchableHttpClient client, ContentProducer files) throws IOException { URL url = new URL(service, "collect/files"); HttpPost request = new HttpPost(url.toExternalForm()); request.setHeader("Content-Type", "application/octet-stream"); @@ -234,7 +225,7 @@ private void sendFiles(URL service, CloseableHttpClient client, ContentProducer return; } - int workId = readResponse(handleError(url, client.execute(request)), JSAsyncFileCollectionResponse.class).workId; + int workId = readResponse(client.httpPost(request), JSAsyncFileCollectionResponse.class).workId; URL workUrl = new URL(service, "collect/files/work/" + workId); while (true) { try { @@ -244,8 +235,7 @@ private void sendFiles(URL service, CloseableHttpClient client, ContentProducer throw new IOException(); } // TODO: utilize numFiles for progress report - HttpGet get = new HttpGet(workUrl.toExternalForm()); - JSAsyncFileCollectionProgress status = readResponse(handleError(workUrl,client.execute(get)), JSAsyncFileCollectionProgress.class); + JSAsyncFileCollectionProgress status = readResponse(client.httpGet(workUrl), JSAsyncFileCollectionProgress.class); switch (status.status) { case IN_PROGRESS: break; // keep polling @@ -350,7 +340,7 @@ - then record commits // ConcurrentConsumer parallelizes file sending within a repository. When it leads the try block // it ensures all the submissions have completed. try (ConcurrentConsumer parallel = new ConcurrentConsumer<>(fileSender, transferPool); - FileChunkStreamer fs = new FileChunkStreamer(r.buildHeader(), parallel, chunkSize); + FileChunkStreamer fs = new FileChunkStreamer(r::buildHeader, parallel, chunkSize); ProgressReporter.Consumer fsr = pr.newConsumer(fs)) { br.collectFiles(advertised, treeReceiver, fsr); } @@ -372,23 +362,6 @@ - then record commits } } - /** Pass through {@link CloseableHttpResponse} but checks and throws an error. */ - private CloseableHttpResponse handleError(URL url, CloseableHttpResponse response) - throws IOException { - int code = response.getStatusLine().getStatusCode(); - if (code >= 400) { - throw new IOException( - String.format( - "Failed to retrieve from %s: %s%n%s", - url, - response.getStatusLine(), - CharStreams.toString( - new InputStreamReader( - response.getEntity().getContent(), StandardCharsets.UTF_8)))); - } - return response; - } - public void collectCommitMessage(boolean commitMessage) { this.collectCommitMessage = commitMessage; } @@ -416,6 +389,7 @@ final class ByRepository implements AutoCloseable { private final Repository git; private final ObjectReader objectReader; + private final ThreadLocal readers; private final Set shallowCommits; private final ObjectId headId; @@ -425,6 +399,7 @@ final class ByRepository implements AutoCloseable { this.objectReader = git.newObjectReader(); this.shallowCommits = objectReader.getShallowCommits(); this.headId = git.resolve("HEAD"); + this.readers = ThreadLocal.withInitial(objectReader::newReader); } void forEachSubModule(ExecutorService threadPool, IOConsumer consumer) throws IOException { @@ -549,7 +524,7 @@ void collectFiles(Collection advertised, TreeReceiver treeReceiver, Fl treeWalk.enterSubtree(); } else { if ((treeWalk.getFileMode(0).getBits() & FileMode.TYPE_MASK) == FileMode.TYPE_FILE) { - GitFile f = new GitFile(name, treeWalk.getPathString(), head, objectReader); + GitFile f = new GitFile(name, treeWalk.getPathString(), head, readers::get); // to avoid excessive data transfer, skip files that are too big if (f.size() < 1024 * 1024 && f.isText() && !f.path.equals(HEADER_FILE)) { treeReceiver.accept(f); @@ -574,25 +549,33 @@ void collectFiles(Collection advertised, TreeReceiver treeReceiver, Fl * Creates a per repository "header" file as a {@link VirtualFile}. * Currently, this is just the list of files in the repository. */ - VirtualFile buildHeader() throws IOException { + VirtualFile buildHeader(List files) throws IOException { ByteArrayOutputStream os = new ByteArrayOutputStream(); try (JsonGenerator w = new JsonFactory().createGenerator(os)) { w.setCodec(objectMapper); w.writeStartObject(); - w.writeArrayFieldStart("tree"); - - try (TreeWalk tw = new TreeWalk(git)) { - tw.addTree(git.parseCommit(headId).getTree()); - tw.setRecursive(true); + { + w.writeArrayFieldStart("tree"); + try (TreeWalk tw = new TreeWalk(git)) { + tw.addTree(git.parseCommit(headId).getTree()); + tw.setRecursive(true); + + while (tw.next()) { + w.writeStartObject(); + w.writeStringField("path", tw.getPathString()); + w.writeEndObject(); + } + } + w.writeEndArray(); - while (tw.next()) { + w.writeArrayFieldStart("inThisChunk"); + for (VirtualFile f : files) { w.writeStartObject(); - w.writeStringField("path", tw.getPathString()); + w.writeStringField("path", f.path()); w.writeEndObject(); } + w.writeEndArray(); } - - w.writeEndArray(); w.writeEndObject(); } return VirtualFile.from(name, HEADER_FILE, ObjectId.zeroId(), os.toByteArray()); @@ -709,9 +692,9 @@ public void close() { private class TreeReceiverImpl implements TreeReceiver { private final List files = new ArrayList<>(); private final URL service; - private final CloseableHttpClient client; + private final LaunchableHttpClient client; - public TreeReceiverImpl(URL service, CloseableHttpClient client) { + public TreeReceiverImpl(URL service, LaunchableHttpClient client) { this.service = service; this.client = client; } @@ -758,7 +741,7 @@ public Collection response() { } // even in dry run, this method needs to execute in order to show what files we'll be collecting - return select(readResponse(handleError(url, client.execute(request)), String[].class)); + return select(readResponse(client.httpPost(request), String[].class)); } catch (IOException e) { throw new UncheckedIOException(e); } finally { diff --git a/src/main/java/com/launchableinc/ingest/commits/FileChunkStreamer.java b/src/main/java/com/launchableinc/ingest/commits/FileChunkStreamer.java index fad3dab02..f9bf1a9cd 100644 --- a/src/main/java/com/launchableinc/ingest/commits/FileChunkStreamer.java +++ b/src/main/java/com/launchableinc/ingest/commits/FileChunkStreamer.java @@ -14,9 +14,13 @@ * Receives {@link GitFile}, buffers them, and writes them out in a gzipped tar file. */ final class FileChunkStreamer extends ChunkStreamer { - private final VirtualFile header; + interface HeaderBuilder { + VirtualFile makeHeader(List chunk) throws IOException; + } + + private final HeaderBuilder header; - FileChunkStreamer(VirtualFile header, IOConsumer sender, int chunkSize) { + FileChunkStreamer(HeaderBuilder header, IOConsumer sender, int chunkSize) { super(sender, chunkSize); this.header = header; } @@ -27,7 +31,7 @@ protected void writeTo(List files, OutputStream os) throws IOExcept tar.setLongFileMode(LONGFILE_POSIX); if (header!=null) { - write(header, tar); + write(header.makeHeader(files), tar); } for (VirtualFile f : files) { diff --git a/src/main/java/com/launchableinc/ingest/commits/GitFile.java b/src/main/java/com/launchableinc/ingest/commits/GitFile.java index 332dab776..6a55f3597 100644 --- a/src/main/java/com/launchableinc/ingest/commits/GitFile.java +++ b/src/main/java/com/launchableinc/ingest/commits/GitFile.java @@ -9,10 +9,10 @@ import java.io.OutputStream; import java.io.Reader; import java.nio.charset.CharacterCodingException; -import java.nio.charset.StandardCharsets; +import java.util.function.Supplier; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.eclipse.jgit.lib.Constants.*; +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; /** * Represents a file in a Git repository, and encapsulates the read access for convenience. @@ -21,9 +21,10 @@ final class GitFile implements VirtualFile { final String repo; final String path; final ObjectId blob; - private final ObjectReader objectReader; + // ObjectReader is not thread-safe, so we use Supplier to get a new instance per thread. + private final Supplier objectReader; - public GitFile(String repo, String path, ObjectId blob, ObjectReader objectReader) { + public GitFile(String repo, String path, ObjectId blob, Supplier objectReader) { this.repo = repo; this.path = path; this.blob = blob; @@ -55,7 +56,7 @@ public void writeTo(OutputStream os) throws IOException { } private ObjectLoader open() throws IOException { - return objectReader.open(blob, OBJ_BLOB); + return objectReader.get().open(blob, OBJ_BLOB); } /** diff --git a/src/main/java/com/launchableinc/ingest/commits/LaunchableHttpClient.java b/src/main/java/com/launchableinc/ingest/commits/LaunchableHttpClient.java new file mode 100644 index 000000000..5274c0c02 --- /dev/null +++ b/src/main/java/com/launchableinc/ingest/commits/LaunchableHttpClient.java @@ -0,0 +1,67 @@ +package com.launchableinc.ingest.commits; + +import com.google.common.io.CharStreams; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.CloseableHttpClient; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.StandardCharsets; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Wrapper around Apache HTTP client for our idiomatic use. + * + *

The way {@link CommitGraphCollector} uses HTTP client is highly idiomatic, for example + * to raise an exception for every 4xx/5xx response. We'll wrap those idioms in this class + * to keep {@link CommitGraphCollector} DRY and apply consistent behavior. + */ +final class LaunchableHttpClient implements Closeable { + final CloseableHttpClient core; + + LaunchableHttpClient(CloseableHttpClient core) { + this.core = core; + } + + @Override + public void close() throws IOException { + core.close(); + } + + /** + * GET with error handling. + */ + public CloseableHttpResponse httpGet(URL url) throws IOException { + return handleError(url, core.execute(new HttpGet(url.toExternalForm()))); + } + + /** + * POST with error handling. + */ + public CloseableHttpResponse httpPost(HttpPost request) throws IOException { + return handleError(request.getURI().toURL(), core.execute(request)); + } + + /** Pass through {@link CloseableHttpResponse} but checks and throws an error. */ + private CloseableHttpResponse handleError(URL url, CloseableHttpResponse response) + throws IOException { + int code = response.getStatusLine().getStatusCode(); + if (code >= 400) { + throw new IOException( + String.format( + "Failed to retrieve from %s: %s%n%s", + url, + response.getStatusLine(), + CharStreams.toString( + new InputStreamReader( + response.getEntity().getContent(), UTF_8)))); + } + return response; + } +} diff --git a/src/test/java/com/launchableinc/ingest/commits/CommitGraphCollectorTest.java b/src/test/java/com/launchableinc/ingest/commits/CommitGraphCollectorTest.java index 117e6ab85..7e4a13a1c 100644 --- a/src/test/java/com/launchableinc/ingest/commits/CommitGraphCollectorTest.java +++ b/src/test/java/com/launchableinc/ingest/commits/CommitGraphCollectorTest.java @@ -33,6 +33,7 @@ import java.util.List; import static com.google.common.truth.Truth.*; +import static java.util.Collections.singletonList; @RunWith(JUnit4.class) public class CommitGraphCollectorTest { @@ -172,22 +173,28 @@ public void header() throws Exception { try (Git mainrepo = Git.open(mainrepoDir)) { addCommitInSubRepo(mainrepo); - List files = new ArrayList<>(); - CommitGraphCollector cgc = new CommitGraphCollector("test", mainrepo.getRepository()); cgc.collectFiles(true); - VirtualFile header = cgc.new ByRepository(mainrepo.getRepository(), "main").buildHeader(); + VirtualFile header = cgc.new ByRepository(mainrepo.getRepository(), "main") + .buildHeader(singletonList(VirtualFile.from("repo", "a.txt", ObjectId.zeroId(), new byte[1]))); assertThat(header.path()).isEqualTo(CommitGraphCollector.HEADER_FILE); - JsonNode tree = assertValidJson(header::writeTo).get("tree"); + JsonNode payload = assertValidJson(header::writeTo); + JsonNode tree = payload.get("tree"); assertThat(tree.isArray()).isTrue(); List paths = new ArrayList<>(); for (JsonNode i : tree) { paths.add(i.get("path").asText()); } - assertThat(paths).containsExactly(".gitmodules", "sub"); + + List inThisChunk = new ArrayList<>(); + for (JsonNode i : payload.get("inThisChunk")) { + inThisChunk.add(i.get("path").asText()); + } + + assertThat(inThisChunk).containsExactly("a.txt"); } } diff --git a/src/test/java/com/launchableinc/ingest/commits/FileChunkStreamerTest.java b/src/test/java/com/launchableinc/ingest/commits/FileChunkStreamerTest.java index 01b946558..09d775f7d 100644 --- a/src/test/java/com/launchableinc/ingest/commits/FileChunkStreamerTest.java +++ b/src/test/java/com/launchableinc/ingest/commits/FileChunkStreamerTest.java @@ -2,6 +2,7 @@ import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; +import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.NullOutputStream; import org.apache.http.entity.ContentProducer; @@ -28,7 +29,7 @@ public void no_op_if_no_content() throws Exception { @Test public void basics() throws Exception { int[] count = new int[1]; - try (FileChunkStreamer fs = new FileChunkStreamer(new VirtualFileImpl("header.txt"), content -> { + try (FileChunkStreamer fs = new FileChunkStreamer(unused -> new VirtualFileImpl("header.txt"), content -> { switch(count[0]++) { case 0: assertThat(readEntries(content)).containsExactly("header.txt", "foo.txt", "bar.txt").inOrder(); From f5af9a87525581a0edf1fd4a9fe17928d41042e2 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Fri, 2 Jan 2026 15:46:35 -0800 Subject: [PATCH 12/15] Adding debug probe --- .../ingest/commits/BoundedExecutorService.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/com/launchableinc/ingest/commits/BoundedExecutorService.java b/src/main/java/com/launchableinc/ingest/commits/BoundedExecutorService.java index d2d745d05..959ead963 100644 --- a/src/main/java/com/launchableinc/ingest/commits/BoundedExecutorService.java +++ b/src/main/java/com/launchableinc/ingest/commits/BoundedExecutorService.java @@ -7,6 +7,7 @@ import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; /** * {@link ExecutorService} decorator that limits the number of concurrent tasks, @@ -15,6 +16,8 @@ class BoundedExecutorService extends AbstractExecutorService { private final ExecutorService delegate; private final Semaphore semaphore; + /** # of threads that are blocked trying to {@link #execute(Runnable)}. Just for diagnostics. */ + private final AtomicInteger blockCount = new AtomicInteger(0); BoundedExecutorService(int limit) { this(Executors.newFixedThreadPool(limit), limit); @@ -28,10 +31,14 @@ class BoundedExecutorService extends AbstractExecutorService { @Override public void execute(Runnable command) { try { + blockCount.incrementAndGet(); semaphore.acquire(); } catch (InterruptedException e) { throw new RejectedExecutionException(e); + } finally { + blockCount.decrementAndGet(); } + try { delegate.execute(() -> { try { From a60a878a60d656c65ae0f852b38d03636ec8ff84 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Fri, 2 Jan 2026 15:51:53 -0800 Subject: [PATCH 13/15] Allow debug options to be passed to the JVM --- launchable/commands/record/commit.py | 3 ++ .../ingest/commits/CommitGraphCollector.java | 14 ++++-- .../ingest/commits/ProgressReporter.java | 46 +++++++++++-------- .../commits/CommitGraphCollectorTest.java | 3 +- .../ingest/commits/ProgressReporterTest.java | 10 ++-- 5 files changed, 47 insertions(+), 29 deletions(-) diff --git a/launchable/commands/record/commit.py b/launchable/commands/record/commit.py index 0c84ea7ef..f81464df3 100644 --- a/launchable/commands/record/commit.py +++ b/launchable/commands/record/commit.py @@ -108,6 +108,9 @@ def exec_jar(name: str, source: str, max_days: int, app: Application, is_collect # using subprocess.check_out with shell=False and a list of command to prevent vulnerability # https://knowledge-base.secureflag.com/vulnerabilities/code_injection/os_command_injection_python.html command = [java] + debug_opts = os.getenv("LAUNCHABLE_JAVA_DEBUG") + if debug_opts: + command.extend(debug_opts.split()) command.extend(_build_proxy_option(os.getenv("HTTPS_PROXY"))) command.extend([ "-jar", diff --git a/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java b/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java index 6be7bd34b..6fa3f27c0 100644 --- a/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java +++ b/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java @@ -100,6 +100,12 @@ public class CommitGraphCollector { private boolean warnMissingObject; + /** + * Reports the # of file transfers, which is the most time-consuming part. + */ + private final ProgressReporter fileTransferProgressReporter = new ProgressReporter(Duration.ofSeconds(3)); + + private String dryRunPrefix() { if (!dryRun) { return ""; @@ -227,6 +233,7 @@ private void sendFiles(URL service, LaunchableHttpClient client, ContentProducer int workId = readResponse(client.httpPost(request), JSAsyncFileCollectionResponse.class).workId; URL workUrl = new URL(service, "collect/files/work/" + workId); + int filesProcessed = 0; while (true) { try { Thread.sleep(5000); @@ -234,8 +241,10 @@ private void sendFiles(URL service, LaunchableHttpClient client, ContentProducer // not expecting this to happen, sufficient to fail throw new IOException(); } - // TODO: utilize numFiles for progress report JSAsyncFileCollectionProgress status = readResponse(client.httpGet(workUrl), JSAsyncFileCollectionProgress.class); + for (; filesProcessed pr = new ProgressReporter<>(VirtualFile::path, Duration.ofSeconds(3)); try { r.forEachSubModule(scanPool, br -> { if (collectFiles) { @@ -341,7 +349,7 @@ - then record commits // it ensures all the submissions have completed. try (ConcurrentConsumer parallel = new ConcurrentConsumer<>(fileSender, transferPool); FileChunkStreamer fs = new FileChunkStreamer(r::buildHeader, parallel, chunkSize); - ProgressReporter.Consumer fsr = pr.newConsumer(fs)) { + FlushableConsumer fsr = fileTransferProgressReporter.newProducer(fs)) { br.collectFiles(advertised, treeReceiver, fsr); } } diff --git a/src/main/java/com/launchableinc/ingest/commits/ProgressReporter.java b/src/main/java/com/launchableinc/ingest/commits/ProgressReporter.java index d5ba0e7de..635164efe 100644 --- a/src/main/java/com/launchableinc/ingest/commits/ProgressReporter.java +++ b/src/main/java/com/launchableinc/ingest/commits/ProgressReporter.java @@ -6,17 +6,16 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; +import java.util.function.Consumer; import static java.time.Instant.now; /** - * Given multiple concurrent slow {@link Consumer}s, each g oing over a large + * Given multiple concurrent slow {@link Producer}s, each g oing over a large * number of items in parallel, * provide a progress report to show that the work is still in progress. */ -class ProgressReporter { - private final Function printer; +class ProgressReporter { private final Duration reportInterval; private Instant nextReportTime; @@ -29,20 +28,24 @@ class ProgressReporter { */ private final AtomicInteger completed = new AtomicInteger(); - ProgressReporter(Function printer, Duration reportInterval) { - this.printer = printer; + ProgressReporter(Duration reportInterval) { this.reportInterval = reportInterval; this.nextReportTime = now().plus(reportInterval); } + public void incrementCompleted() { + completed.incrementAndGet(); + maybePrintStatus(); + } + /** * Deals with one serial stream of work. */ - class Consumer implements FlushableConsumer { + class Producer implements FlushableConsumer { private final FlushableConsumer base; private final List pool = new ArrayList<>(); - Consumer(FlushableConsumer base) { + Producer(FlushableConsumer base) { this.base = base; } @@ -55,14 +58,8 @@ public void accept(T t) { @Override public void flush() throws IOException { for (T x : pool) { - synchronized (ProgressReporter.this) { - if (now().isAfter(nextReportTime)) { - print(completed.get(), workload.get(), x); - nextReportTime = now().plus(reportInterval); - } - } + maybePrintStatus(); base.accept(x); - completed.incrementAndGet(); } pool.clear(); base.flush(); @@ -71,16 +68,27 @@ public void flush() throws IOException { @Override public void close() throws IOException { flush(); + base.close(); } } - Consumer newConsumer(FlushableConsumer base) { - return new Consumer(base); + private synchronized void maybePrintStatus() { + if (now().isAfter(nextReportTime)) { + print(completed.get(), workload.get()); + nextReportTime = now().plus(reportInterval); + } + } + + /** + * Decorates the {@link Consumer} on the producing end to count the total number of work to be completed. + */ + FlushableConsumer newProducer(FlushableConsumer base) { + return new Producer<>(base); } - protected void print(int c, int w, T x) { + protected void print(int c, int w) { int width = String.valueOf(w).length(); - System.err.printf("%s/%d: %s%n", pad(c, width), w, printer.apply(x)); + System.err.printf("%s/%d%n", pad(c, width), w); } static String pad(int i, int width) { diff --git a/src/test/java/com/launchableinc/ingest/commits/CommitGraphCollectorTest.java b/src/test/java/com/launchableinc/ingest/commits/CommitGraphCollectorTest.java index 7e4a13a1c..783399384 100644 --- a/src/test/java/com/launchableinc/ingest/commits/CommitGraphCollectorTest.java +++ b/src/test/java/com/launchableinc/ingest/commits/CommitGraphCollectorTest.java @@ -29,10 +29,9 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import static com.google.common.truth.Truth.*; +import static com.google.common.truth.Truth.assertThat; import static java.util.Collections.singletonList; @RunWith(JUnit4.class) diff --git a/src/test/java/com/launchableinc/ingest/commits/ProgressReporterTest.java b/src/test/java/com/launchableinc/ingest/commits/ProgressReporterTest.java index b518c73d9..ad3d9ae64 100644 --- a/src/test/java/com/launchableinc/ingest/commits/ProgressReporterTest.java +++ b/src/test/java/com/launchableinc/ingest/commits/ProgressReporterTest.java @@ -15,12 +15,12 @@ import static java.util.Collections.*; public class ProgressReporterTest { - ProgressReporter pr = new ProgressReporter(String::valueOf, Duration.ofMillis(100)) { + ProgressReporter pr = new ProgressReporter(Duration.ofMillis(100)) { int cc = 0; int ww = 0; @Override - protected void print(int c, int w, String x) { - super.print(c, w, x); + protected void print(int c, int w) { + super.print(c, w); // ensure numbers are monotonically increasing assertThat(c).isAtLeast(cc); @@ -36,7 +36,7 @@ protected void print(int c, int w, String x) { @Test public void serial() throws Exception { List done = new ArrayList<>(); - try (ProgressReporter.Consumer x = pr.newConsumer(FlushableConsumer.of(s -> { + try (FlushableConsumer x = pr.newProducer(FlushableConsumer.of(s -> { done.add(s); sleep(); }))) { @@ -59,7 +59,7 @@ public void parallel() throws Exception { for (int i=0; i<10; i++) { final int ii = i; all.add(es.submit(() -> { - try (ProgressReporter.Consumer x = pr.newConsumer(FlushableConsumer.of(s -> {done.add(s);sleep();}))) { + try (FlushableConsumer x = pr.newProducer(FlushableConsumer.of(s -> {done.add(s);sleep();}))) { for (int j = 0; j < 100; j++) { x.accept("item " + (ii*100+j)); } From 77225d46e5d8da7be023be58192432d6fe2c73fd Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Fri, 2 Jan 2026 16:56:34 -0800 Subject: [PATCH 14/15] Align this with the progress reporter frequency --- .../launchableinc/ingest/commits/CommitGraphCollector.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java b/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java index 6fa3f27c0..8c70a0a30 100644 --- a/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java +++ b/src/main/java/com/launchableinc/ingest/commits/CommitGraphCollector.java @@ -76,6 +76,7 @@ public class CommitGraphCollector { */ static final String HEADER_FILE = ".launchable"; private static final String APPLICATION_JSON = "application/json"; + private static final int PROGRESS_REPORT_INTERVAL = 3000; private final String rootName; @@ -103,7 +104,7 @@ public class CommitGraphCollector { /** * Reports the # of file transfers, which is the most time-consuming part. */ - private final ProgressReporter fileTransferProgressReporter = new ProgressReporter(Duration.ofSeconds(3)); + private final ProgressReporter fileTransferProgressReporter = new ProgressReporter(Duration.ofMillis(PROGRESS_REPORT_INTERVAL)); private String dryRunPrefix() { @@ -236,7 +237,7 @@ private void sendFiles(URL service, LaunchableHttpClient client, ContentProducer int filesProcessed = 0; while (true) { try { - Thread.sleep(5000); + Thread.sleep(PROGRESS_REPORT_INTERVAL); } catch (InterruptedException e) { // not expecting this to happen, sufficient to fail throw new IOException(); From 188fc2b451878bb4e9aebc730b81dfb6962fd26b Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Mon, 5 Jan 2026 08:46:21 -0800 Subject: [PATCH 15/15] Rebuilt the jar file --- launchable/jar/exe_deploy.jar | Bin 12597754 -> 12609217 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/launchable/jar/exe_deploy.jar b/launchable/jar/exe_deploy.jar index 5800a905525613d358aae46b4281dd4a8c809a39..a1c5eae5ce25cda01c0fddd9841803634eae6d90 100755 GIT binary patch delta 189132 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

dY%cbfp96j%NzsQosq8;AR>yY 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