Skip to content

Commit 7a2939f

Browse files
Use non-blocking I/O for Docker API
This commit changes the NamedPipeSocket used for communication with a local Docker daemon to use a non-blocking AsynchronousByteChannel instead of a blocking RandomAccessFile, modeled after a similar change to the docker-java project. This eliminates the potential for a blocking call to hang indefinitely. Fixes gh-21672
1 parent d347346 commit 7a2939f

File tree

1 file changed

+85
-36
lines changed
  • spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/socket

1 file changed

+85
-36
lines changed

spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/socket/NamedPipeSocket.java

Lines changed: 85 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,21 @@
1616

1717
package org.springframework.boot.buildpack.platform.socket;
1818

19-
import java.io.FileNotFoundException;
2019
import java.io.IOException;
2120
import java.io.InputStream;
2221
import java.io.OutputStream;
23-
import java.io.RandomAccessFile;
2422
import java.net.Socket;
23+
import java.nio.ByteBuffer;
24+
import java.nio.channels.AsynchronousByteChannel;
25+
import java.nio.channels.AsynchronousCloseException;
26+
import java.nio.channels.AsynchronousFileChannel;
27+
import java.nio.channels.Channels;
28+
import java.nio.channels.CompletionHandler;
29+
import java.nio.file.NoSuchFileException;
30+
import java.nio.file.Paths;
31+
import java.nio.file.StandardOpenOption;
32+
import java.util.concurrent.CompletableFuture;
33+
import java.util.concurrent.Future;
2534
import java.util.concurrent.TimeUnit;
2635
import java.util.function.Consumer;
2736

@@ -32,6 +41,7 @@
3241
* A {@link Socket} implementation for named pipes.
3342
*
3443
* @author Phillip Webb
44+
* @author Scott Frederick
3545
* @since 2.3.0
3646
*/
3747
public class NamedPipeSocket extends Socket {
@@ -40,27 +50,22 @@ public class NamedPipeSocket extends Socket {
4050

4151
private static final long TIMEOUT = TimeUnit.MILLISECONDS.toNanos(1000);
4252

43-
private final RandomAccessFile file;
44-
45-
private final InputStream inputStream;
46-
47-
private final OutputStream outputStream;
53+
private final AsynchronousFileByteChannel channel;
4854

4955
NamedPipeSocket(String path) throws IOException {
50-
this.file = open(path);
51-
this.inputStream = new NamedPipeInputStream();
52-
this.outputStream = new NamedPipeOutputStream();
56+
this.channel = open(path);
5357
}
5458

55-
private static RandomAccessFile open(String path) throws IOException {
59+
private AsynchronousFileByteChannel open(String path) throws IOException {
5660
Consumer<String> awaiter = Platform.isWindows() ? new WindowsAwaiter() : new SleepAwaiter();
5761
long startTime = System.nanoTime();
5862
while (true) {
5963
try {
60-
return new RandomAccessFile(path, "rw");
64+
return new AsynchronousFileByteChannel(AsynchronousFileChannel.open(Paths.get(path),
65+
StandardOpenOption.READ, StandardOpenOption.WRITE));
6166
}
62-
catch (FileNotFoundException ex) {
63-
if (System.nanoTime() - startTime > TIMEOUT) {
67+
catch (NoSuchFileException ex) {
68+
if (System.nanoTime() - startTime >= TIMEOUT) {
6469
throw ex;
6570
}
6671
awaiter.accept(path);
@@ -70,21 +75,19 @@ private static RandomAccessFile open(String path) throws IOException {
7075

7176
@Override
7277
public InputStream getInputStream() {
73-
return this.inputStream;
78+
return Channels.newInputStream(this.channel);
7479
}
7580

7681
@Override
7782
public OutputStream getOutputStream() {
78-
return this.outputStream;
83+
return Channels.newOutputStream(this.channel);
7984
}
8085

8186
@Override
8287
public void close() throws IOException {
83-
this.file.close();
84-
}
85-
86-
protected final RandomAccessFile getFile() {
87-
return this.file;
88+
if (this.channel != null) {
89+
this.channel.close();
90+
}
8891
}
8992

9093
/**
@@ -98,35 +101,81 @@ public static NamedPipeSocket get(String path) throws IOException {
98101
}
99102

100103
/**
101-
* {@link InputStream} returned from the {@link NamedPipeSocket}.
104+
* Adapt an {@code AsynchronousByteChannel} to an {@code AsynchronousFileChannel}.
102105
*/
103-
private class NamedPipeInputStream extends InputStream {
106+
private static class AsynchronousFileByteChannel implements AsynchronousByteChannel {
107+
108+
private final AsynchronousFileChannel fileChannel;
109+
110+
AsynchronousFileByteChannel(AsynchronousFileChannel fileChannel) {
111+
this.fileChannel = fileChannel;
112+
}
104113

105114
@Override
106-
public int read() throws IOException {
107-
return getFile().read();
115+
public <A> void read(ByteBuffer dst, A attachment, CompletionHandler<Integer, ? super A> handler) {
116+
this.fileChannel.read(dst, 0, attachment, new CompletionHandler<Integer, A>() {
117+
118+
@Override
119+
public void completed(Integer read, A attachment) {
120+
handler.completed((read > 0) ? read : -1, attachment);
121+
}
122+
123+
@Override
124+
public void failed(Throwable exc, A attachment) {
125+
if (exc instanceof AsynchronousCloseException) {
126+
handler.completed(-1, attachment);
127+
return;
128+
}
129+
handler.failed(exc, attachment);
130+
}
131+
});
132+
108133
}
109134

110135
@Override
111-
public int read(byte[] bytes, int off, int len) throws IOException {
112-
return getFile().read(bytes, off, len);
136+
public Future<Integer> read(ByteBuffer dst) {
137+
CompletableFutureHandler future = new CompletableFutureHandler();
138+
this.fileChannel.read(dst, 0, null, future);
139+
return future;
113140
}
114141

115-
}
142+
@Override
143+
public <A> void write(ByteBuffer src, A attachment, CompletionHandler<Integer, ? super A> handler) {
144+
this.fileChannel.write(src, 0, attachment, handler);
145+
}
116146

117-
/**
118-
* {@link InputStream} returned from the {@link NamedPipeSocket}.
119-
*/
120-
private class NamedPipeOutputStream extends OutputStream {
147+
@Override
148+
public Future<Integer> write(ByteBuffer src) {
149+
return this.fileChannel.write(src, 0);
150+
}
121151

122152
@Override
123-
public void write(int value) throws IOException {
124-
NamedPipeSocket.this.file.write(value);
153+
public void close() throws IOException {
154+
this.fileChannel.close();
125155
}
126156

127157
@Override
128-
public void write(byte[] bytes, int off, int len) throws IOException {
129-
NamedPipeSocket.this.file.write(bytes, off, len);
158+
public boolean isOpen() {
159+
return this.fileChannel.isOpen();
160+
}
161+
162+
private static class CompletableFutureHandler extends CompletableFuture<Integer>
163+
implements CompletionHandler<Integer, Object> {
164+
165+
@Override
166+
public void completed(Integer read, Object attachment) {
167+
complete((read > 0) ? read : -1);
168+
}
169+
170+
@Override
171+
public void failed(Throwable exc, Object attachment) {
172+
if (exc instanceof AsynchronousCloseException) {
173+
complete(-1);
174+
return;
175+
}
176+
completeExceptionally(exc);
177+
}
178+
130179
}
131180

132181
}

0 commit comments

Comments
 (0)