001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.maven.resolver.examples;
020
021import java.time.Duration;
022import java.time.Instant;
023import java.time.temporal.ChronoUnit;
024import java.util.List;
025import java.util.concurrent.CountDownLatch;
026import java.util.concurrent.atomic.AtomicInteger;
027
028import org.apache.maven.resolver.examples.util.Booter;
029import org.eclipse.aether.DefaultRepositorySystemSession;
030import org.eclipse.aether.RepositorySystem;
031import org.eclipse.aether.RepositorySystemSession;
032import org.eclipse.aether.artifact.Artifact;
033import org.eclipse.aether.artifact.DefaultArtifact;
034import org.eclipse.aether.collection.CollectRequest;
035import org.eclipse.aether.graph.Dependency;
036import org.eclipse.aether.repository.RemoteRepository;
037import org.eclipse.aether.resolution.ArtifactResult;
038import org.eclipse.aether.resolution.DependencyRequest;
039import org.eclipse.aether.util.artifact.JavaScopes;
040
041/**
042 * Resolves the transitive (compile) LARGE dependencies of an imaginary artifact in parallel.
043 * This is the reproducer for locking issues: <a href="https://github.com/apache/maven-resolver/issues/1644">GH-1644</a>
044 * This code does NOT run as part of build/tests, it is meant to be ad-hoc run from IDE or alike.
045 */
046public class ResolveTransitiveDependenciesParallel {
047
048    /**
049     * Main.
050     * @param args
051     * @throws Exception
052     */
053    public static void main(String[] args) throws Exception {
054        System.out.println("------------------------------------------------------------");
055        System.out.println(ResolveTransitiveDependencies.class.getSimpleName());
056
057        RepositorySystem system = Booter.newRepositorySystem(Booter.selectFactory(args));
058
059        // note: these numbers below are not "universal", they stand for one given WS with one given network,
060        // and they may need change with time if anything changes in relation (even remote like Central!)
061        //
062        // cstamas (DK HW+net) 17. 11. 2025 (w/ empty local repo)
063        // One job is done in 20 sec
064        // BUT other must wait as they are serialized (time adds up)
065        // reproducer to succeed: 210 sec
066        // my run:
067        // DONE (21 sec): org.example:test:1: resolved 11; failed 0
068        // DONE (37 sec): org.example:test:6: resolved 11; failed 0
069        // DONE (60 sec): org.example:test:4: resolved 11; failed 0
070        // DONE (60 sec): org.example:test:5: resolved 11; failed 0
071        // DONE (170 sec): org.example:test:8: resolved 11; failed 0
072        // DONE (181 sec): org.example:test:3: resolved 11; failed 0
073        // DONE (181 sec): org.example:test:7: resolved 11; failed 0
074        // DONE (181 sec): org.example:test:2: resolved 11; failed 0
075        // =====
076        // TOTAL success=8; fail=0
077        //
078        // Pattern: as said above, one job does in 20 sec, BUT subsequent one will do it in cca 40 sec (waiting 20 sec)
079        // and so on, the times are adding up. Each subsequent job with start by waiting for previous jobs to finish
080        // due (intentional) artifact overlaps.
081
082        DefaultRepositorySystemSession session = Booter.newRepositorySystemSession(system);
083        session.setConfigProperty("aether.syncContext.named.time", "210");
084        session.setTransferListener(null);
085        session.setRepositoryListener(null);
086
087        Artifact bigArtifact1 = new DefaultArtifact("org.bytedeco:llvm:jar:linux-arm64:16.0.4-1.5.9");
088        Artifact bigArtifact2 = new DefaultArtifact("org.bytedeco:llvm:jar:linux-armhf:16.0.4-1.5.9");
089        Artifact bigArtifact3 = new DefaultArtifact("org.bytedeco:llvm:jar:linux-ppc64le:16.0.4-1.5.9");
090        Artifact bigArtifact4 = new DefaultArtifact("org.bytedeco:llvm:jar:linux-x86:16.0.4-1.5.9");
091        Artifact bigArtifact5 = new DefaultArtifact("org.bytedeco:llvm:jar:linux-x86_64:16.0.4-1.5.9");
092        Artifact bigArtifact6 = new DefaultArtifact("org.bytedeco:llvm:jar:macosx-arm64:16.0.4-1.5.9");
093        Artifact bigArtifact7 = new DefaultArtifact("org.bytedeco:llvm:jar:macosx-x86_64:16.0.4-1.5.9");
094        Artifact bigArtifact8 = new DefaultArtifact("org.bytedeco:llvm:jar:windows-x86:16.0.4-1.5.9");
095        Artifact bigArtifact9 = new DefaultArtifact("org.bytedeco:llvm:jar:windows-x86_64:16.0.4-1.5.9");
096
097        CountDownLatch latch = new CountDownLatch(8);
098        AtomicInteger success = new AtomicInteger(0);
099        AtomicInteger fail = new AtomicInteger(0);
100
101        Thread thread1 = new Thread(resolveWithDependencies(
102                latch,
103                success,
104                fail,
105                system,
106                session,
107                Booter.newRepositories(system, session),
108                "org.example:test:1",
109                bigArtifact1,
110                bigArtifact2));
111        Thread thread2 = new Thread(resolveWithDependencies(
112                latch,
113                success,
114                fail,
115                system,
116                session,
117                Booter.newRepositories(system, session),
118                "org.example:test:2",
119                bigArtifact2,
120                bigArtifact3));
121        Thread thread3 = new Thread(resolveWithDependencies(
122                latch,
123                success,
124                fail,
125                system,
126                session,
127                Booter.newRepositories(system, session),
128                "org.example:test:3",
129                bigArtifact3,
130                bigArtifact4));
131        Thread thread4 = new Thread(resolveWithDependencies(
132                latch,
133                success,
134                fail,
135                system,
136                session,
137                Booter.newRepositories(system, session),
138                "org.example:test:4",
139                bigArtifact4,
140                bigArtifact5));
141        Thread thread5 = new Thread(resolveWithDependencies(
142                latch,
143                success,
144                fail,
145                system,
146                session,
147                Booter.newRepositories(system, session),
148                "org.example:test:5",
149                bigArtifact5,
150                bigArtifact6));
151        Thread thread6 = new Thread(resolveWithDependencies(
152                latch,
153                success,
154                fail,
155                system,
156                session,
157                Booter.newRepositories(system, session),
158                "org.example:test:6",
159                bigArtifact6,
160                bigArtifact7));
161        Thread thread7 = new Thread(resolveWithDependencies(
162                latch,
163                success,
164                fail,
165                system,
166                session,
167                Booter.newRepositories(system, session),
168                "org.example:test:7",
169                bigArtifact7,
170                bigArtifact8));
171        Thread thread8 = new Thread(resolveWithDependencies(
172                latch,
173                success,
174                fail,
175                system,
176                session,
177                Booter.newRepositories(system, session),
178                "org.example:test:8",
179                bigArtifact8,
180                bigArtifact9));
181
182        thread1.start();
183        thread2.start();
184        thread3.start();
185        thread4.start();
186        thread5.start();
187        thread6.start();
188        thread7.start();
189        thread8.start();
190
191        latch.await();
192
193        System.out.println("=====");
194        System.out.println("TOTAL success=" + success.get() + "; fail=" + fail.get());
195    }
196
197    @SuppressWarnings("checkstyle:parameternumber")
198    private static Runnable resolveWithDependencies(
199            CountDownLatch latch,
200            AtomicInteger success,
201            AtomicInteger fail,
202            RepositorySystem system,
203            RepositorySystemSession session,
204            List<RemoteRepository> repositories,
205            String gav,
206            Artifact... deps) {
207        return () -> {
208            try {
209                Instant now = Instant.now();
210                CollectRequest collectRequest = new CollectRequest();
211                collectRequest.setRootArtifact(new DefaultArtifact(gav));
212                for (Artifact dep : deps) {
213                    collectRequest.addDependency(new Dependency(dep, JavaScopes.COMPILE));
214                }
215                collectRequest.setRepositories(repositories);
216                DependencyRequest dependencyRequest = new DependencyRequest(collectRequest, null);
217                List<ArtifactResult> artifactResults =
218                        system.resolveDependencies(session, dependencyRequest).getArtifactResults();
219                int resolved = 0;
220                int fails = 0;
221                for (ArtifactResult artifactResult : artifactResults) {
222                    if (artifactResult.isResolved()) {
223                        resolved++;
224                    } else {
225                        fails++;
226                    }
227                }
228                String dur = Duration.between(now, Instant.now()).get(ChronoUnit.SECONDS) + " sec";
229                System.out.println("DONE (" + dur + "): " + gav + ": resolved " + resolved + "; failed " + fails);
230                success.getAndIncrement();
231            } catch (Exception e) {
232                System.out.println("FAILED " + gav + ": " + e.getMessage());
233                fail.getAndIncrement();
234            } finally {
235                latch.countDown();
236            }
237        };
238    }
239}