Java 终止从UNIX命名管道打开输入流时阻塞的线程
我使用JavaIO流从Unix命名管道读取命令。程序有一个线程,该线程使用Java 终止从UNIX命名管道打开输入流时阻塞的线程,java,unix,named-pipes,Java,Unix,Named Pipes,我使用JavaIO流从Unix命名管道读取命令。程序有一个线程,该线程使用mkfifo创建一个命名管道,然后侦听该管道,允许用户使用echo”命令“>pipe向其发送命令 它在大多数情况下都可以正常工作,除非我的程序必须在没有收到任何命令的情况下终止(例如,在不可恢复的异常情况下)。 如本文所述:FileInputStream构造函数阻塞,直到其他进程打开管道进行写入。这使得线程实现没有机会处理中断并正确终止 我制作了一个简单的程序来展示我的问题。它会侦听输入命令3秒钟,然后尝试(但失败)自行停
mkfifo
创建一个命名管道,然后侦听该管道,允许用户使用echo”命令“>pipe
向其发送命令 它在大多数情况下都可以正常工作,除非我的程序必须在没有收到任何命令的情况下终止(例如,在不可恢复的异常情况下)。
如本文所述:
FileInputStream
构造函数阻塞,直到其他进程打开管道进行写入。这使得线程实现没有机会处理中断并正确终止
我制作了一个简单的程序来展示我的问题。它会侦听输入命令3秒钟,然后尝试(但失败)自行停止
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.function.Consumer;
/**
* Read the input named FIFO to dispatch commands to a command processor.
*/
public final class InputReaderThread {
private static final long LOOP_PERIOD_MS = 100;
private final Path fifoPath;
private final Consumer<String[]> processor;
private final Thread thread;
/**
* Constructor.
*
* @param fifoPath path to the named FIFO
* @param processor the command processor
*/
public InputReaderThread(final Path fifoPath, final Consumer<String[]> processor) {
this.fifoPath = fifoPath;
this.processor = processor;
this.thread = new Thread(this::run);
this.thread.setName(InputReaderThread.class.getSimpleName());
}
/**
* Start the input reader thread.<br>
* It is never legal to start a thread more than once. In particular, a thread may not be restarted once it has completed execution.
*
* @see Thread#start()
*/
public final void start() {
System.out.println("Requesting input reader thread start");
this.thread.start();
}
/**
* Request the input reader thread to stop.<br>
* This method does not wait for the thread to terminate, see {@link #join(long)}.
*/
public final void stop() {
System.out.println("Requesting input reader thread stop");
this.thread.interrupt();
}
/**
* Wait for the internal reader thread to terminate.<br>
* Throws an {@link InterruptedException} on timeout.
*
* @param timeout the maximum time to wait, in milliseconds
* @return {@code true} if the input thread terminated within timeout
* @throws InterruptedException the current thread was interrupted while waiting, or the timeout was reached
* @see Thread#join(long)
*/
public final boolean join(final long timeout) throws InterruptedException {
System.out.println("Awaiting input reader thread stop");
this.thread.join(timeout);
return !this.thread.isAlive();
}
private final void run() {
System.out.println("Input reader thread started");
try (final FileInputStream is = new FileInputStream(createFifoPipe(this.fifoPath))) {
final StringBuilder commandBuilder = new StringBuilder();
System.out.println("Listening to input FIFO");
while (!Thread.interrupted()) {
// Avoid reading when there is no data available
if (is.available() > 0) {
final int b = is.read();
if (b == '\n') {
// The command is complete: process it
final String command = commandBuilder.toString().trim();
System.out.println("Received command: " + command);
this.processor.accept(command.split(" "));
// Reset the command builder
commandBuilder.setLength(0);
} else {
// Append the character to the command
commandBuilder.append((char) b);
}
} else {
// Poll the input FIFO periodically
Thread.sleep(LOOP_PERIOD_MS);
}
}
} catch (final IOException e) {
throw new RuntimeException("An IO exception occurred on agent FIFO", e);
} catch (@SuppressWarnings("unused") final InterruptedException e) {
// Handle interruption by terminating the thread
}
System.out.println("Input reader thread terminated");
}
/**
* Helper method to create a Unix named FIFO.
*
* @param fifoPath the FIFO path
* @return the File handler to the created FIFO
* @throws IOException an I/O error occurs
* @throws InterruptedException the thread was interrupted while waiting for the FIFO creation
*/
private static final File createFifoPipe(final Path fifoPath) throws IOException, InterruptedException {
System.out.println("Creating fifo: " + fifoPath);
final File fifo = fifoPath.toFile();
if (fifo.exists()) {
System.err.println("Deleting existing fifo");
Files.delete(fifoPath);
}
final String[] command = new String[] { "mkfifo", fifo.getAbsolutePath() };
final Process process = new ProcessBuilder(command).start();
final int returnStatus = process.waitFor();
if (returnStatus != 0) {
throw new IOException("Failed to create fifo: " + returnStatus);
} else {
System.out.println("Created fifo: " + fifoPath);
}
return fifo;
}
}
运行此程序在命名管道中写入“stop”将显示输入读取器线程在处理中断时正确终止(不确定为什么会记录两次线程终止,但这是另一个问题):
但是,在未写入“stop”命令的情况下运行会显示FileInputStream
在打开管道时无法处理中断:
$ java -jar NamedPipeTest.jar fifo &
Requesting input reader thread start
Input reader thread started
Creating fifo: fifo
Deleting existing fifo
Created fifo: fifo
Requesting input reader thread stop
Awaiting input reader thread stop
Failed to terminate input reader thread
我想到的事情:
- 线程在执行IO时不应被设为守护进程(将这两个线程混用似乎是个坏主意)
- Java NIO API依赖于seek(),这似乎不适合命名管道
我不知道如何才能解决这个问题,我是否错过了什么?谢谢你的帮助 你能解释一下你是如何知道中断不起作用的吗?这是停止阻塞线程的正常方法。起初我尝试了,然后搜索并找到了回答它的链接(由于某种原因,链接没有出现在我的问题中):基本上,FileInputStream构造函数不会处理来自Unix命名管道的IO阻塞时的中断。您必须发布一个显示问题的完整程序。@Armali问题使用完整复制程序更新抱歉,我无法编译此程序-
错误:找不到符号…ICommandProcessor
。
$ (java -jar NamedPipeTest.jar fifo &) && sleep 2s && echo "stop" > fifo
Requesting input reader thread start
Input reader thread started
Creating fifo: fifo
Deleting existing fifo
Created fifo: fifo
Listening to input FIFO
Received command: stop
Requesting input reader thread stop
Awaiting input reader thread stop
Input reader thread terminated
Stopped successfully
Requesting input reader thread stop
Awaiting input reader thread stop
Stopped successfully
$ java -jar NamedPipeTest.jar fifo &
Requesting input reader thread start
Input reader thread started
Creating fifo: fifo
Deleting existing fifo
Created fifo: fifo
Requesting input reader thread stop
Awaiting input reader thread stop
Failed to terminate input reader thread