mirror of
https://github.com/apache/maven-mvnd.git
synced 2025-09-24 21:35:15 +00:00
Merge pull request #136 from ppalaga/i129
mvnd --status to display memory usage #129
This commit is contained in:
@@ -49,6 +49,17 @@
|
||||
<artifactId>svm</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
@@ -28,7 +28,6 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
@@ -50,6 +49,7 @@ import org.jboss.fuse.mvnd.common.DaemonStopEvent;
|
||||
import org.jboss.fuse.mvnd.common.Environment;
|
||||
import org.jboss.fuse.mvnd.common.MavenDaemon;
|
||||
import org.jboss.fuse.mvnd.common.Message;
|
||||
import org.jboss.fuse.mvnd.common.Os;
|
||||
import org.jboss.fuse.mvnd.common.Serializer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -69,8 +69,6 @@ public class DaemonConnector {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DaemonConnector.class);
|
||||
|
||||
private static final boolean IS_WINDOWS = System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("win");
|
||||
|
||||
private final DaemonRegistry registry;
|
||||
private final ClientLayout layout;
|
||||
private final Serializer<Message> serializer;
|
||||
@@ -264,7 +262,7 @@ public class DaemonConnector {
|
||||
p -> p.getFileName().toString().equals("mvnd-common-" + buildProperties.getVersion() + ".jar"),
|
||||
p -> p.getFileName().toString().startsWith("slf4j-api-"),
|
||||
p -> p.getFileName().toString().startsWith("logback-"));
|
||||
final String java = IS_WINDOWS ? "bin\\java.exe" : "bin/java";
|
||||
final String java = Os.current().isUnixLike() ? "bin/java" : "bin\\java.exe";
|
||||
List<String> args = new ArrayList<>();
|
||||
args.add(layout.javaHome().resolve(java).toString());
|
||||
args.add("-classpath");
|
||||
|
@@ -37,6 +37,7 @@ import org.jboss.fuse.mvnd.common.Message.BuildException;
|
||||
import org.jboss.fuse.mvnd.common.Message.BuildMessage;
|
||||
import org.jboss.fuse.mvnd.common.Message.KeepAliveMessage;
|
||||
import org.jboss.fuse.mvnd.common.Message.MessageSerializer;
|
||||
import org.jboss.fuse.mvnd.common.OsUtils;
|
||||
import org.jboss.fuse.mvnd.common.logging.ClientOutput;
|
||||
import org.jboss.fuse.mvnd.common.logging.TerminalOutput;
|
||||
import org.slf4j.Logger;
|
||||
@@ -141,10 +142,12 @@ public class DefaultClient implements Client {
|
||||
try (DaemonRegistry registry = new DaemonRegistry(layout.registry())) {
|
||||
boolean status = args.remove("--status");
|
||||
if (status) {
|
||||
output.accept(null, String.format(" %36s %7s %5s %7s %23s %s",
|
||||
"UUID", "PID", "Port", "Status", "Last activity", "Java home"));
|
||||
registry.getAll().forEach(d -> output.accept(null, String.format(" %36s %7s %5s %7s %23s %s",
|
||||
final String template = " %36s %7s %5s %7s %5s %23s %s";
|
||||
output.accept(null, String.format(template,
|
||||
"UUID", "PID", "Port", "Status", "RSS", "Last activity", "Java home"));
|
||||
registry.getAll().forEach(d -> output.accept(null, String.format(template,
|
||||
d.getUid(), d.getPid(), d.getAddress(), d.getState(),
|
||||
OsUtils.kbTohumanReadable(OsUtils.findProcessRssInKb(d.getPid())),
|
||||
LocalDateTime.ofInstant(
|
||||
Instant.ofEpochMilli(Math.max(d.getLastIdle(), d.getLastBusy())),
|
||||
ZoneId.systemDefault()),
|
||||
|
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jboss.fuse.mvnd.client;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.jboss.fuse.mvnd.common.OsUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class OsUtilsTest {
|
||||
|
||||
/**
|
||||
* This test needs to be in the client module as long as the common module is on Java 8
|
||||
*/
|
||||
@Test
|
||||
void findProcessRssInKb() {
|
||||
long rss = OsUtils.findProcessRssInKb(ProcessHandle.current().pid());
|
||||
Assertions.assertThat(rss).isGreaterThanOrEqualTo(0);
|
||||
}
|
||||
}
|
61
common/src/main/java/org/jboss/fuse/mvnd/common/Os.java
Normal file
61
common/src/main/java/org/jboss/fuse/mvnd/common/Os.java
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jboss.fuse.mvnd.common;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public enum Os {
|
||||
LINUX(true),
|
||||
MAC(true),
|
||||
WINDOWS(false),
|
||||
UNKNOWN(false) {
|
||||
|
||||
@Override
|
||||
public boolean isUnixLike() {
|
||||
throw new UnsupportedOperationException("Cannot tell isUnixLike() for an " + UNKNOWN.name() + " OS");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private static final Os CURRENT;
|
||||
static {
|
||||
final String osName = System.getProperty("os.name").toLowerCase(Locale.ROOT);
|
||||
if (osName.startsWith("osx") || osName.startsWith("mac os x")) {
|
||||
CURRENT = MAC;
|
||||
} else if (osName.contains("win")) {
|
||||
CURRENT = WINDOWS;
|
||||
} else if (osName.contains("linux")) {
|
||||
CURRENT = LINUX;
|
||||
} else {
|
||||
CURRENT = UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
private final boolean unixLike;
|
||||
|
||||
public static Os current() {
|
||||
return CURRENT;
|
||||
}
|
||||
|
||||
Os(boolean unixLike) {
|
||||
this.unixLike = unixLike;
|
||||
}
|
||||
|
||||
public boolean isUnixLike() {
|
||||
return unixLike;
|
||||
}
|
||||
|
||||
}
|
187
common/src/main/java/org/jboss/fuse/mvnd/common/OsUtils.java
Normal file
187
common/src/main/java/org/jboss/fuse/mvnd/common/OsUtils.java
Normal file
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jboss.fuse.mvnd.common;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class OsUtils {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(OsUtils.class);
|
||||
private static final long KB = 1024;
|
||||
private static final String UNITS = "kmgt";
|
||||
|
||||
private OsUtils() {
|
||||
|
||||
}
|
||||
|
||||
public static String kbTohumanReadable(long kb) {
|
||||
int unit = 0;
|
||||
while (kb >= KB && unit < UNITS.length() - 1) {
|
||||
kb /= KB;
|
||||
unit++;
|
||||
}
|
||||
String kbString = String.valueOf(kb);
|
||||
return new StringBuilder(kbString.length() + 1).append(kbString).append(UNITS.charAt(unit)).toString();
|
||||
}
|
||||
|
||||
public static long findProcessRssInKb(long pid) {
|
||||
final Os os = Os.current();
|
||||
if (os.isUnixLike()) {
|
||||
String[] cmd = { "ps", "-o", "rss=", "-p", String.valueOf(pid) };
|
||||
final List<String> output = new ArrayList<String>(1);
|
||||
exec(cmd, output);
|
||||
if (output.size() == 1) {
|
||||
return Long.parseLong(output.get(0));
|
||||
} else {
|
||||
LOGGER.warn("Unexpected output of " + Stream.of(cmd).collect(Collectors.joining(" ")) + ":\n"
|
||||
+ output.stream().collect(Collectors.joining("\n")));
|
||||
}
|
||||
return -1;
|
||||
} else if (os == Os.WINDOWS) {
|
||||
String[] cmd = { "wmic", "process", "where", "processid=" + pid, "get", "WorkingSetSize" };
|
||||
final List<String> output = new ArrayList<String>(1);
|
||||
exec(cmd, output);
|
||||
final List<String> nonEmptyLines = output.stream().filter(l -> !l.isEmpty()).collect(Collectors.toList());
|
||||
if (nonEmptyLines.size() >= 2) {
|
||||
try {
|
||||
return Long.parseLong(nonEmptyLines.get(1).trim());
|
||||
} catch (NumberFormatException e) {
|
||||
LOGGER.warn("Could not parse the second line of " + Stream.of(cmd).collect(Collectors.joining(" "))
|
||||
+ " output as a long:\n"
|
||||
+ nonEmptyLines.stream().collect(Collectors.joining("\n")));
|
||||
}
|
||||
} else {
|
||||
LOGGER.warn("Unexpected output of " + Stream.of(cmd).collect(Collectors.joining(" ")) + ":\n"
|
||||
+ output.stream().collect(Collectors.joining("\n")));
|
||||
}
|
||||
return -1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private static void exec(String[] cmd, final List<String> output) {
|
||||
final ProcessBuilder builder = new ProcessBuilder(cmd).redirectErrorStream(true);
|
||||
try (CommandProcess ps = new CommandProcess(builder.start(), output::add)) {
|
||||
final int exitCode = ps.waitFor(1000);
|
||||
if (exitCode != 0) {
|
||||
LOGGER.warn(Stream.of(cmd).collect(Collectors.joining(" ")) + " exited with " + exitCode + ":\n"
|
||||
+ output.stream().collect(Collectors.joining("\n")));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Could not execute " + Stream.of(cmd).collect(Collectors.joining(" ")));
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple wrapper over {@link Process} that manages its destroying and offers Java 8-like
|
||||
* {@link #waitFor(long, TimeUnit, String[])} with timeout.
|
||||
*/
|
||||
public static class CommandProcess implements AutoCloseable {
|
||||
public static final int TIMEOUT_EXIT_CODE = Integer.MIN_VALUE + 42;
|
||||
|
||||
/**
|
||||
* The usual friend of {@link Process#getInputStream()} / {@link Process#getErrorStream()}.
|
||||
*/
|
||||
static class StreamGobbler extends Thread {
|
||||
private volatile boolean cancelled;
|
||||
private IOException exception;
|
||||
private final InputStream in;
|
||||
private final Consumer<String> out;
|
||||
|
||||
private StreamGobbler(InputStream in, Consumer<String> out) {
|
||||
this.in = in;
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
public void assertSuccess() throws IOException {
|
||||
if (exception != null) {
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
this.cancelled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try (BufferedReader r = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
while (!cancelled && (line = r.readLine()) != null) {
|
||||
out.accept(line);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final Process process;
|
||||
private final Thread shutDownHook;
|
||||
private final StreamGobbler stdOut;
|
||||
|
||||
public CommandProcess(Process process, Consumer<String> outputConsumer) {
|
||||
super();
|
||||
this.process = process;
|
||||
this.stdOut = new StreamGobbler(process.getInputStream(), outputConsumer);
|
||||
stdOut.start();
|
||||
|
||||
this.shutDownHook = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
stdOut.cancel();
|
||||
CommandProcess.this.process.destroy();
|
||||
}
|
||||
});
|
||||
Runtime.getRuntime().addShutdownHook(shutDownHook);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
process.destroy();
|
||||
}
|
||||
|
||||
public int waitFor(long timeoutMs) throws InterruptedException, IOException {
|
||||
final long deadline = System.currentTimeMillis() + timeoutMs;
|
||||
final boolean timeouted = !process.waitFor(timeoutMs, TimeUnit.MILLISECONDS);
|
||||
timeoutMs = Math.max(0, deadline - System.currentTimeMillis());
|
||||
stdOut.join(timeoutMs);
|
||||
stdOut.assertSuccess();
|
||||
try {
|
||||
Runtime.getRuntime().removeShutdownHook(shutDownHook);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
final int exitCode = timeouted ? TIMEOUT_EXIT_CODE : process.exitValue();
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jboss.fuse.mvnd.common;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class OsUtilsTest {
|
||||
@Test
|
||||
void kbTohumanReadable() {
|
||||
Assertions.assertEquals("0k", OsUtils.kbTohumanReadable(0));
|
||||
Assertions.assertEquals("1001k", OsUtils.kbTohumanReadable(1001));
|
||||
Assertions.assertEquals("1m", OsUtils.kbTohumanReadable(1024));
|
||||
Assertions.assertEquals("1023m", OsUtils.kbTohumanReadable(1024 * 1024 - 1));
|
||||
Assertions.assertEquals("1g", OsUtils.kbTohumanReadable(1024 * 1024));
|
||||
Assertions.assertEquals("1t", OsUtils.kbTohumanReadable(1024 * 1024 * 1024));
|
||||
}
|
||||
|
||||
}
|
@@ -15,21 +15,17 @@
|
||||
*/
|
||||
package org.jboss.fuse.mvnd.junit;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import org.jboss.fuse.mvnd.client.Client;
|
||||
import org.jboss.fuse.mvnd.client.ClientLayout;
|
||||
import org.jboss.fuse.mvnd.client.ExecutionResult;
|
||||
import org.jboss.fuse.mvnd.common.Environment;
|
||||
import org.jboss.fuse.mvnd.common.OsUtils.CommandProcess;
|
||||
import org.jboss.fuse.mvnd.common.logging.ClientOutput;
|
||||
|
||||
/**
|
||||
@@ -37,8 +33,6 @@ import org.jboss.fuse.mvnd.common.logging.ClientOutput;
|
||||
*/
|
||||
public class NativeTestClient implements Client {
|
||||
|
||||
public static final int TIMEOUT_EXIT_CODE = Integer.MIN_VALUE + 42;
|
||||
|
||||
private final ClientLayout layout;
|
||||
|
||||
private final Path mvndNativeExecutablePath;
|
||||
@@ -86,8 +80,17 @@ public class NativeTestClient implements Client {
|
||||
}
|
||||
final String cmdString = String.join(" ", cmd);
|
||||
output.accept(null, "Executing " + cmdString);
|
||||
try (CommandProcess process = new CommandProcess(builder.start(), cmd, s -> output.accept(null, s))) {
|
||||
return process.waitFor(timeoutMs);
|
||||
|
||||
final List<String> log = new ArrayList<>();
|
||||
final Consumer<String> loggingConsumer = s -> {
|
||||
synchronized (log) {
|
||||
log.add(s);
|
||||
}
|
||||
};
|
||||
try (CommandProcess process = new CommandProcess(builder.start(),
|
||||
loggingConsumer.andThen(s -> output.accept(null, s)))) {
|
||||
final int exitCode = process.waitFor(timeoutMs);
|
||||
return new Result(args, exitCode, log);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Could not execute: " + cmdString, e);
|
||||
}
|
||||
@@ -125,7 +128,7 @@ public class NativeTestClient implements Client {
|
||||
public Result assertSuccess() {
|
||||
if (exitCode != 0) {
|
||||
final StringBuilder sb = appendCommand(new StringBuilder("mvnd returned ").append(exitCode));
|
||||
if (exitCode == TIMEOUT_EXIT_CODE) {
|
||||
if (exitCode == CommandProcess.TIMEOUT_EXIT_CODE) {
|
||||
sb.append(" (timeout)");
|
||||
}
|
||||
sb.append("\n--- stderr+stdout start ---");
|
||||
@@ -148,96 +151,4 @@ public class NativeTestClient implements Client {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple wrapper over {@link Process} that manages its destroying and offers Java 8-like
|
||||
* {@link #waitFor(long, TimeUnit, String[])} with timeout.
|
||||
*/
|
||||
static class CommandProcess implements AutoCloseable {
|
||||
|
||||
private final Process process;
|
||||
private final Thread shutDownHook;
|
||||
private final StreamGobbler stdOut;
|
||||
private final List<String> args;
|
||||
private final List<String> log = new ArrayList<>();
|
||||
|
||||
public CommandProcess(Process process, List<String> args, Consumer<String> outputConsumer) {
|
||||
super();
|
||||
this.process = process;
|
||||
this.args = args;
|
||||
final Consumer<String> loggingConsumer = s -> {
|
||||
synchronized (log) {
|
||||
log.add(s);
|
||||
}
|
||||
};
|
||||
this.stdOut = new StreamGobbler(process.getInputStream(), loggingConsumer.andThen(outputConsumer));
|
||||
stdOut.start();
|
||||
|
||||
this.shutDownHook = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
stdOut.cancel();
|
||||
CommandProcess.this.process.destroy();
|
||||
}
|
||||
});
|
||||
Runtime.getRuntime().addShutdownHook(shutDownHook);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
process.destroy();
|
||||
}
|
||||
|
||||
public ExecutionResult waitFor(long timeoutMs) throws InterruptedException, IOException {
|
||||
final long deadline = System.currentTimeMillis() + timeoutMs;
|
||||
final boolean timeouted = !process.waitFor(timeoutMs, TimeUnit.MILLISECONDS);
|
||||
timeoutMs = Math.max(0, deadline - System.currentTimeMillis());
|
||||
stdOut.join(timeoutMs);
|
||||
stdOut.assertSuccess();
|
||||
try {
|
||||
Runtime.getRuntime().removeShutdownHook(shutDownHook);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
final int exitCode = timeouted ? TIMEOUT_EXIT_CODE : process.exitValue();
|
||||
return new Result(args, exitCode, log);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The usual friend of {@link Process#getInputStream()} / {@link Process#getErrorStream()}.
|
||||
*/
|
||||
static class StreamGobbler extends Thread {
|
||||
private volatile boolean cancelled;
|
||||
private IOException exception;
|
||||
private final InputStream in;
|
||||
private final Consumer<String> out;
|
||||
|
||||
private StreamGobbler(InputStream in, Consumer<String> out) {
|
||||
this.in = in;
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
public void assertSuccess() throws IOException {
|
||||
if (exception != null) {
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
this.cancelled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try (BufferedReader r = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
while (!cancelled && (line = r.readLine()) != null) {
|
||||
out.accept(line);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user