Java ProcessBuilder: результирующие зависания процессов

Я пытался использовать Java ProcessBuilder для запуска приложения в Linux, которое должно работать "на длительный срок". Способ запуска этой программы - запустить команду (в данном случае я запускаю приложение воспроизведения мультимедиа), разрешить ее запуск и убедиться, что она не вылетела. Например, проверьте, активен ли PID, и перезапустите процесс, если он умер.

Проблема, которую я получаю сейчас, состоит в том, что PID остается в системе живым, но графический интерфейс приложения зависает. Я попытался перенести ProcessBuilder(cmd).start() в отдельный поток, но, похоже, это ничего не решает, как я и надеялся.

По сути, результатом является то, что для пользователя программа, по-видимому, аварийно завершает работу, но уничтожение процесса Java, который управляет процессом ProcessBuilder.start(), фактически позволяет созданному процессу возобновить свое нормальное поведение. Это означает, что что-то в Java-приложении мешает порожденному процессу, но я абсолютно не знаю, что на данный момент. (Поэтому я попытался разделить его на другой поток, который, похоже, ничего не решал)

Если у кого-то есть какие-либо замечания / мысли, пожалуйста, дайте мне знать, так как я не могу думать, как решить эту проблему.

Изменить: я не беспокоюсь о потоке ввода-вывода, созданного из процесса, и, следовательно, не предпринял никаких шагов для решения этой проблемы - может ли это вызвать зависание в самом процессе?

10 ответов

Решение

Если процесс пишет в stderr или stdout, а вы его не читаете - он просто "зависнет", блокируя при записи в stdout/err. Либо перенаправьте stdout / err в /dev/null с помощью оболочки, либо объедините stdout / err с redirectErrorStream(true) и создайте другой поток, который читает из stdout процесса

Вы хотите трюк?

Не запускайте свой процесс из ProcessBuilder.start(). Не пытайтесь связываться с перенаправлением / потреблением потоков из Java (особенно, если вы не обращаете на это внимания;)

Используйте ProcessBuilder.start() для запуска небольшого сценария оболочки, который поглощает все потоки ввода / вывода.

Что-то вроде того:

#!/bin/bash

nohup $1 >/dev/null 2>error.log &

То есть: если вы не заботитесь о stdout и по-прежнему хотите записать stderr (не так ли?) В файл (error.log здесь).

Если вы даже не заботитесь о stderr, просто перенаправьте его на stdout:

#!/bin/bash

nohup $1 >/dev/null 2>1 &

И вы вызываете этот крошечный скрипт из Java, давая в качестве аргумента имя процесса, который вы хотите запустить.

Если процесс, работающий в Linux, который перенаправляет как stdout, так и stderr в / dev / null, все равно производит что- то, значит, у вас испорчена несовместимая установка Linux;)

Другими словами: вышеупомянутый Just Works [TM] и избавление от проблематичного "вам нужно использовать потоки в том и другом порядке, бла-бла-бла, специфичные для Java бессмысленные".

Поток, выполняющий процесс, может блокироваться, если он не обрабатывает выходные данные. Это можно сделать, создав новый поток, который читает выходные данные процесса.

    final ProcessBuilder builder = new ProcessBuilder("script")
                    .redirectErrorStream(true)
                    .directory(workDirectory);

    final Process process = builder.start();
    final StringWriter writer = new StringWriter();

    new Thread(new Runnable() {
        public void run() {
            IOUtils.copy(process.getInputStream(), writer);
        }
    }).start();

    final int exitValue = process.waitFor();
    final String processOutput = writer.toString();

Просто наткнулся на это после того, как у меня возникла похожая проблема. Соглашаясь с nos, вам нужно обработать вывод. У меня было что-то вроде этого:

ProcessBuilder myProc2 = new ProcessBuilder(command);
final Process process = myProc2.start();

и это работало отлично. Порожденный процесс даже выводил некоторый вывод, но не очень. Когда я начал выводить намного больше, оказалось, что мой процесс больше не запускается. Я обновился к этому:

ProcessBuilder myProc2 = new ProcessBuilder(command);
myProc2.redirectErrorStream(true);        
final Process process = myProc2.start();
InputStream myIS = process.getInputStream();
String tempOut = convertStreamToStr(myIS);

и это снова заработало. (обратитесь к этой ссылке для получения кода convertStreamToStr(): http://singztechmusings.wordpress.com/2011/06/21/getting-started-with-javas-processbuilder-a-sample-utility-class-to-interact-with-linux-from-java-program/)

Изменить: я не беспокоюсь о потоке ввода-вывода, созданного из процесса, и, следовательно, не предпринял никаких шагов для решения этой проблемы - может ли это вызвать зависание в самом процессе?

Если вы не читаете выходные потоки, созданные процессом, возможно, что приложение заблокируется, как только буферы приложения будут заполнены. Я никогда не видел, чтобы это происходило в Linux (хотя я не говорю, что это не так), но я видел именно эту проблему в Windows. Я думаю, что это, вероятно, связано.

JDK7 будет иметь встроенную поддержку перенаправления ввода / вывода подпроцесса:

http://download.oracle.com/javase/7/docs/api/java/lang/ProcessBuilder.html

Тем временем, если вы действительно хотите отказаться от stdout/stderr, лучше всего (в Linux) вызывать ProcessBuilder по команде, которая выглядит следующим образом:

["/bin/bash", "-c", "exec YOUR_COMMAND_HERE >/dev/null 2>&1"]

Другое решение - начать процесс с Redirect.PIPE и закрыть InputStream нравится:

ProcessBuilder builder = new ProcessBuilder(cmd);
builder.redirectOutput(Redirect.PIPE);
builder.redirectErrorStream(true); // redirect the SysErr to SysOut
Process proc = builder.start();
proc.getInputStream().close(); // this will close the pipe and the output will "flow"
procp.waitFor();

Я тестировал это в Windows и Linux, и работает!

Если вам нужно захватить stdout и stderr и отслеживать процесс, то использование Apache Commons Exec мне очень помогло.

Я полагаю, что проблема заключается в буферизации трубы от самого Linux.

Попробуй использовать stdbuf с вашим исполняемым файлом

new ProcessBuilder().command("/usr/bin/stdbuf","-o0","*executable*","*arguments*");**

-o0 говорит не буферизировать вывод. То же самое относится и к -i0 а также -e0 если вы хотите снять буфер ввода и канал ошибок.

вам необходимо прочитать вывод, прежде чем ждать завершения цикла. Вы не будете уведомлены, если вывод не заполняет буфер. Если это так, он будет ждать, пока вы не прочитаете вывод.

Предположим, у вас есть ошибки или ответы относительно вашей команды, которые вы не читаете. Это заставит приложение останавливаться и ждать вечного ожидания. Простой способ - перенаправить ошибки на обычный вывод.

На этот вопрос я потратил 2 дня.

      public static void exeCuteCommand(String command) {
    try {
        boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows");
        ProcessBuilder builder = new ProcessBuilder();
        if (isWindows) {
            builder.command("cmd.exe", "/c", command);
        } else {
            builder.command("sh", "-c", command);
        }
        Process process = builder.start();
        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        String line;
        while ((line = reader.readLine()) != null)
            System.out.println("Cmd Response: " + line);
        process.waitFor();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
Другие вопросы по тегам