Skip to content

Commit c011fbc

Browse files
committed
Fixing test failures
1 parent a34b78d commit c011fbc

File tree

21 files changed

+269
-195
lines changed

21 files changed

+269
-195
lines changed

.github/workflows/reusable-build.yml

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ jobs:
1515
strategy:
1616
fail-fast: true
1717
matrix:
18-
runtime: [ linux-x64, linux-aarch64, osx-x64, win-x64 ]
18+
# WORKAROUND: https://github.com/docker/setup-docker-action/issues/166
19+
runtime: [ linux-x64, linux-aarch64, osx-x64 ] #, win-x64 ]
1920
include:
2021
- runtime: linux-x64
2122
os: ubuntu-latest
@@ -26,8 +27,8 @@ jobs:
2627
- runtime: osx-x64
2728
os: macOS-latest
2829

29-
- runtime: win-x64
30-
os: windows-latest
30+
# - runtime: win-x64
31+
# os: windows-latest
3132
runs-on: ${{ matrix.os }}
3233
steps:
3334
- name: Checkout source code
@@ -41,9 +42,9 @@ jobs:
4142
distribution: temurin
4243
java-version: 24
4344

44-
- name: Set up WSL
45-
if: ${{ matrix.os == 'windows-latest' }}
46-
uses: Vampire/setup-wsl@v5
45+
# - name: Set up WSL
46+
# if: ${{ matrix.os == 'windows-latest' }}
47+
# uses: Vampire/setup-wsl@v5
4748

4849
- name: Set up Docker
4950
uses: docker/setup-docker-action@v4
@@ -55,9 +56,6 @@ jobs:
5556
}
5657
}
5758
58-
- name: Docker version
59-
run: docker version
60-
6159
# Pull the images used by integration tests
6260
- name: Pull docker:dind
6361
run: docker pull docker:dind

buildx/src/test/java/io/github/cowwoc/anchor4j/buildx/test/resource/ImageIT.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010
import io.github.cowwoc.anchor4j.core.resource.CoreImageBuilder;
1111
import io.github.cowwoc.anchor4j.core.resource.CoreImageBuilder.Exporter;
1212
import io.github.cowwoc.anchor4j.core.resource.DefaultBuildListener;
13+
import io.github.cowwoc.anchor4j.core.resource.WaitFor;
1314
import io.github.cowwoc.anchor4j.core.test.TestBuildListener;
1415
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
1516
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
1617
import org.testng.annotations.Test;
1718

19+
import java.io.BufferedReader;
1820
import java.io.EOFException;
1921
import java.io.File;
2022
import java.io.FileInputStream;
@@ -200,6 +202,13 @@ public void buildWithCacheFrom() throws IOException, InterruptedException
200202
AtomicBoolean cacheWasUsed = new AtomicBoolean(false);
201203
client.buildImage().cacheFrom(image.getId()).listener(new DefaultBuildListener()
202204
{
205+
@Override
206+
public void buildStarted(BufferedReader stdoutReader, BufferedReader stderrReader, WaitFor waitFor)
207+
{
208+
cacheWasUsed.set(false);
209+
super.buildStarted(stdoutReader, stderrReader, waitFor);
210+
}
211+
203212
@Override
204213
public void onStderrLine(String line)
205214
{

core/src/main/java/io/github/cowwoc/anchor4j/core/client/Client.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,16 @@ public interface Client
7474
/**
7575
* Blocks until the default builder is reachable and has a {@code RUNNING} state.
7676
*
77-
* @param until the latest time to attempt to poll the builder's state
77+
* @param deadline the absolute time by which the builder must be ready. The method will poll the builder's
78+
* state while the current time is before this value.
7879
* @return the builder
7980
* @throws IOException if an I/O error occurs. These errors are typically transient, and retrying
8081
* the request may resolve the issue.
8182
* @throws InterruptedException if the thread is interrupted before the operation completes. This can happen
8283
* due to shutdown signals.
83-
* @throws TimeoutException if a timeout occurs before the operation completes
84+
* @throws TimeoutException if the deadline expires before the operation succeeds
8485
*/
85-
BuilderState waitUntilBuilderIsReady(Instant until)
86+
BuilderState waitUntilBuilderIsReady(Instant deadline)
8687
throws IOException, InterruptedException, TimeoutException;
8788

8889
/**

core/src/main/java/io/github/cowwoc/anchor4j/core/internal/client/AbstractInternalClient.java

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
public abstract class AbstractInternalClient implements InternalClient
5151
{
5252
private final static ByteBuffer EMPTY_BYTE_BUFFER = ByteBuffer.allocate(0);
53+
private final static Duration SLEEP_DURATION = Duration.ofMillis(100);
5354
/**
5455
* The path of the command-line executable.
5556
*/
@@ -102,17 +103,25 @@ public Duration getRetryTimeout()
102103
@Override
103104
public <V> V retry(Operation<V> operation) throws IOException, InterruptedException
104105
{
105-
return retry(operation, Instant.now().plus(getRetryTimeout()));
106+
try
107+
{
108+
return retry(operation, Instant.now().plus(getRetryTimeout()));
109+
}
110+
catch (TimeoutException e)
111+
{
112+
throw new AssertionError("An operation without a timeout threw a TimeoutException", e);
113+
}
106114
}
107115

108116
@Override
109-
public <V> V retry(Operation<V> operation, Instant until) throws IOException, InterruptedException
117+
public <V> V retry(Operation<V> operation, Instant deadline)
118+
throws IOException, InterruptedException, TimeoutException
110119
{
111120
while (true)
112121
{
113122
try
114123
{
115-
return operation.run(until);
124+
return operation.run(deadline);
116125
}
117126
catch (FileNotFoundException e)
118127
{
@@ -122,46 +131,68 @@ public <V> V retry(Operation<V> operation, Instant until) throws IOException, In
122131
catch (IOException e)
123132
{
124133
// WORKAROUND: https://github.com/moby/moby/issues/50160
125-
if (timeoutOccurred(until, e))
134+
if (!sleepBeforeRetry(deadline, e))
126135
throw e;
127136
}
128137
catch (UnsupportedExporterException e)
129138
{
130139
// Surprisingly, the following error occurs intermittently under load:
131140
//
132141
// ERROR: failed to build: docker exporter does not currently support exporting manifest lists
133-
if (timeoutOccurred(until, e))
142+
if (!sleepBeforeRetry(deadline, e))
134143
throw e;
135144
}
136145
}
137146
}
138147

139148
/**
140-
* @param until the latest time to attempt to run the command. The method will retry failed operations while
141-
* the current time is before this value.
142-
* @param t the exception that was thrown
149+
* Checks if a timeout occurred.
150+
*
151+
* @param deadline the absolute time by which the operation must succeed. The method will retry failed
152+
* operations while the current time is before this value.
153+
* @param t the exception that was thrown
143154
* @return {@code true} if the operation may be retried
144155
* @throws InterruptedException if the thread is interrupted before the operation completes. This can happen
145156
* due to shutdown signals.
146157
*/
147-
private boolean timeoutOccurred(Instant until, Throwable t) throws InterruptedException
158+
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
159+
private boolean sleepBeforeRetry(Instant deadline, Throwable t) throws InterruptedException
148160
{
149-
Instant now = Instant.now();
150-
if (now.isAfter(until))
151-
return true;
152-
Thread.sleep(100);
161+
Instant nextRetry = Instant.now().plus(SLEEP_DURATION);
162+
if (nextRetry.isAfter(deadline))
163+
return false;
164+
Thread.sleep(SLEEP_DURATION);
153165
log.debug("Retrying after sleep", t);
154-
return false;
166+
return true;
167+
}
168+
169+
/**
170+
* Checks if a timeout occurred.
171+
*
172+
* @param deadline the absolute time by which the operation must succeed. The method will retry failed
173+
* operations while the current time is before this value.
174+
* @return {@code true} if the operation may be retried
175+
* @throws InterruptedException if the thread is interrupted before the operation completes. This can happen
176+
* due to shutdown signals.
177+
*/
178+
protected boolean sleepBeforeRetry(Instant deadline) throws InterruptedException
179+
{
180+
Instant nextRetry = Instant.now().plus(SLEEP_DURATION);
181+
if (nextRetry.isAfter(deadline))
182+
return false;
183+
Thread.sleep(SLEEP_DURATION);
184+
log.debug("Retrying after sleep");
185+
return true;
155186
}
156187

157188
@Override
158-
public CommandResult run(List<String> arguments, Instant until) throws IOException, InterruptedException
189+
public CommandResult run(List<String> arguments, Instant deadline) throws IOException, InterruptedException
159190
{
160-
return run(arguments, EMPTY_BYTE_BUFFER, until);
191+
return run(arguments, EMPTY_BYTE_BUFFER, deadline);
161192
}
162193

163194
@Override
164-
public CommandResult run(List<String> arguments, ByteBuffer stdin, Instant until)
195+
public CommandResult run(List<String> arguments, ByteBuffer stdin, Instant deadline)
165196
throws IOException, InterruptedException
166197
{
167198
ProcessBuilder processBuilder = getProcessBuilder(arguments);
@@ -304,7 +335,7 @@ public BuilderState getBuilderState(String name) throws IOException, Interrupted
304335

305336
@Override
306337
@SuppressWarnings("BusyWait")
307-
public BuilderState waitUntilBuilderIsReady(Instant until)
338+
public BuilderState waitUntilBuilderIsReady(Instant deadline)
308339
throws IOException, InterruptedException, TimeoutException
309340
{
310341
while (true)
@@ -316,13 +347,13 @@ public BuilderState waitUntilBuilderIsReady(Instant until)
316347
if (builder == null)
317348
{
318349
log.debug("builder == null");
319-
if (now.isAfter(until))
350+
if (now.isAfter(deadline))
320351
throw new TimeoutException("Default builder not found");
321352
}
322353
else
323354
{
324355
log.debug("builder.status: {}", builder.getStatus());
325-
if (now.isAfter(until))
356+
if (now.isAfter(deadline))
326357
{
327358
StringBuilder message = new StringBuilder(
328359
"Default builder " + builder.getName() + " has a state of " +

core/src/main/java/io/github/cowwoc/anchor4j/core/internal/client/InternalClient.java

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.time.Duration;
1111
import java.time.Instant;
1212
import java.util.List;
13+
import java.util.concurrent.TimeoutException;
1314

1415
/**
1516
* The internals shared by all clients.
@@ -35,28 +36,28 @@ public interface InternalClient extends Client
3536
* Runs a command and returns its output.
3637
*
3738
* @param arguments the command-line arguments to pass to the executable
38-
* @param until the latest time to attempt to run the command. The method will retry failed operations
39-
* while the current time is before this value.
39+
* @param deadline the absolute time by which the operation must succeed. The method will retry failed
40+
* operations while the current time is before this value.
4041
* @return the output of the command
4142
* @throws NullPointerException if any of the arguments are null
4243
* @throws IOException if the executable could not be found
4344
* @throws InterruptedException if the thread was interrupted before the operation completed
4445
*/
45-
CommandResult run(List<String> arguments, Instant until) throws IOException, InterruptedException;
46+
CommandResult run(List<String> arguments, Instant deadline) throws IOException, InterruptedException;
4647

4748
/**
4849
* Runs a command and returns its output.
4950
*
5051
* @param arguments the command-line arguments to pass to the executable
5152
* @param stdin the bytes to pass into the command's stdin stream
52-
* @param until the latest time to attempt to run the command. The method will retry failed operations
53-
* while the current time is before this value.
53+
* @param deadline the absolute time by which the operation must succeed. The method will retry failed
54+
* operations while the current time is before this value.
5455
* @return the output of the command
5556
* @throws NullPointerException if any of the arguments are null
5657
* @throws IOException if the executable could not be found
5758
* @throws InterruptedException if the thread was interrupted before the operation completed
5859
*/
59-
CommandResult run(List<String> arguments, ByteBuffer stdin, Instant until)
60+
CommandResult run(List<String> arguments, ByteBuffer stdin, Instant deadline)
6061
throws IOException, InterruptedException;
6162

6263
/**
@@ -90,14 +91,17 @@ CommandResult run(List<String> arguments, ByteBuffer stdin, Instant until)
9091
*
9192
* @param <V> the type of value returned by the operation
9293
* @param operation the operation
93-
* @param until the latest time to attempt to run the command. The method will retry failed operations
94-
* while the current time is before this value.
94+
* @param deadline the absolute time by which the operation must succeed. The method will retry failed
95+
* operations while the current time is before this value.
9596
* @return the value returned by the operation
96-
* @throws IOException if an I/O error persists beyond the {@code deadline}
97+
* @throws IOException if an I/O error persists beyond the {@code until}
9798
* @throws InterruptedException if the thread is interrupted before the operation completes. This can happen
9899
* due to shutdown signals.
100+
* @throws TimeoutException if the deadline expires before the operation completes successfully, and no
101+
* other exception was thrown to indicate the failure
99102
*/
100-
<V> V retry(Operation<V> operation, Instant until) throws IOException, InterruptedException;
103+
<V> V retry(Operation<V> operation, Instant deadline)
104+
throws IOException, InterruptedException, TimeoutException;
101105

102106
/**
103107
* @return a {@code BuildXParser}

core/src/main/java/io/github/cowwoc/anchor4j/core/internal/client/Operation.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.io.IOException;
44
import java.time.Instant;
5+
import java.util.concurrent.TimeoutException;
56

67
/**
78
* Represents an operation that may fail due to intermittent I/O errors.
@@ -13,14 +14,16 @@ public interface Operation<V>
1314
/**
1415
* Runs the operation.
1516
*
16-
* @param until the latest time to attempt to run the command. The method will retry failed operations while
17-
* the current time is before this value.
17+
* @param deadline the absolute time by which the operation must succeed. The method will retry failed
18+
* operations while the current time is before this value.
1819
* @return the value returned by the operation
19-
* @throws NullPointerException if {@code deadline} is null
20+
* @throws NullPointerException if {@code until} is null
2021
* @throws IOException if an I/O error occurs. These errors are typically transient, and retrying
2122
* the request may resolve the issue.
2223
* @throws InterruptedException if the thread is interrupted before the operation completes. This can happen
2324
* due to shutdown signals.
25+
* @throws TimeoutException if the deadline expires before the operation succeeds, and no other
26+
* exception was thrown to indicate the failure
2427
*/
25-
V run(Instant until) throws InterruptedException, IOException;
28+
V run(Instant deadline) throws InterruptedException, IOException, TimeoutException;
2629
}

core/src/main/java/io/github/cowwoc/anchor4j/core/resource/DefaultBuildListener.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.io.BufferedReader;
1313
import java.io.FileNotFoundException;
1414
import java.io.IOException;
15+
import java.util.List;
1516
import java.util.StringJoiner;
1617
import java.util.concurrent.BlockingQueue;
1718
import java.util.concurrent.LinkedBlockingQueue;
@@ -27,9 +28,12 @@ public class DefaultBuildListener implements BuildListener
2728
private static final Pattern ERROR_READING_PREFACE = Pattern.compile(".+? .+? http2: server: " +
2829
"error reading preface from client .+?: file has already been closed\n");
2930
// Known variants:
30-
// ERROR: failed to build: resolve : (?:CreateFile|lstat) (.+?): The system cannot find the file specified.
31-
private static final Pattern FILE_NOT_FOUND = Pattern.compile("ERROR: failed to build: resolve : " +
32-
"(?:CreateFile|lstat) (.+?): The system cannot find the file specified\\.");
31+
// ERROR: failed to build: resolve : CreateFile C:\Users\Gili\Documents\anchor4j\buildx\src\test\resources\missing: The system cannot find the file specified.
32+
// ERROR: resolve : lstat /home/runner/work/anchor4j/anchor4j/buildx/src/test/resources/missing: no such file or directory
33+
// ERROR: failed to build: resolve : lstat /home/runner/work/anchor4j/anchor4j/buildx/src/test/resources/missing: no such file or directory
34+
private static final List<Pattern> FILE_NOT_FOUND = List.of(Pattern.compile("ERROR: (?:.*?: )?" +
35+
"resolve : CreateFile (.+?): The system cannot find the file specified\\."),
36+
Pattern.compile("ERROR: (?:.*?: )?resolve : lstat (.+?): no such file or directory"));
3337
private static final String DOCKER_DRIVER_DOES_NOT_SUPPORT_MANIFEST_LIST =
3438
"ERROR: failed to build: docker exporter does not currently support exporting manifest lists";
3539
private static final String DOCKER_DRIVER_DOES_NOT_SUPPORT_OCI =
@@ -42,7 +46,7 @@ public class DefaultBuildListener implements BuildListener
4246
private static final Pattern RESOURCE_IN_USE = Pattern.compile("""
4347
ERROR: failed to build: no valid drivers found: open (.+?): The process cannot access the file because \
4448
it is being used by another process\\.""");
45-
49+
4650
/**
4751
* The lines returned by the build's standard output stream.
4852
*/
@@ -163,9 +167,12 @@ public void buildFailed(CommandResult result) throws IOException
163167
// "2025/06/11 15:53:20 http2: server: error reading preface from client //./pipe/dockerDesktopLinuxEngine: file has already been closed"
164168
stderr = stderr.substring(matcher.end());
165169
}
166-
matcher = FILE_NOT_FOUND.matcher(stderr);
167-
if (matcher.matches())
168-
throw new FileNotFoundException(matcher.group(1));
170+
for (Pattern pattern : FILE_NOT_FOUND)
171+
{
172+
matcher = pattern.matcher(stderr);
173+
if (matcher.matches())
174+
throw new FileNotFoundException(matcher.group(1));
175+
}
169176
matcher = BuildXParser.NOT_FOUND.matcher(stderr);
170177
if (matcher.matches())
171178
throw new BuilderNotFoundException(matcher.group(1));

0 commit comments

Comments
 (0)