Home > other >  Why isn't the queue updating on the main thread?
Why isn't the queue updating on the main thread?

Time:04-27

I'm opening a PowerShell instance and sending it commands via System.in, and I want to collect the output into a queue so I can print the result while not interfering with typing in commands. However, the queue is not filling on the main thread, and sleeping on the main only fixes the issue slightly. I imagine the issue is with not synchronizing the queues, but I'm not sure how.

package PWST;

import java.io.PrintWriter;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class PWST {
    public static void main(String[] args) {
        try {
            Scanner in = new Scanner(System.in);
            System.out.print("\033[H\033[2J > "); // clear console.
            String[] tokens = in.nextLine().split(",");

            Queue<String> stdout = new LinkedList<>();
            Queue<String> stderr = new LinkedList<>();

            Process process = Runtime.getRuntime().exec("powershell.exe");
            new Thread(new SPQ(process.getInputStream(), stdout)).start();
            new Thread(new SPQ(process.getErrorStream(), stderr)).start();
            PrintWriter out = new PrintWriter(process.getOutputStream());
                
            for (String token : tokens) // parse commands (will be expanded to continuously ask for input)
                out.println(token.strip());

            // print powershell output after:
            System.out.println(stdout);
            System.out.println(stderr);

            in.close();
            out.close();
            process.waitFor();
            System.out.println("Shell link closed");
            
        } catch (Exception e) { e.printStackTrace(); }
    }
}
package PWST;

import java.io.InputStream;
import java.util.Queue;

class SPQ implements Runnable {
    private Queue<String> queue;
    private InputStream istrm;

    public SPQ(InputStream istrm, Queue<String> queue) {
        this.istrm = istrm;
        this.queue = queue;
    }

    public void run() {
        try {
            final byte[] buffer = new byte[1024];
            for (int length = 0; (length = istrm.read(buffer)) != -1; )
                queue.add(new String(buffer, 0, length)); // store output in queue
                
        } catch (Exception e) { e.printStackTrace(); }
    }
}

CodePudding user response:

There are at least two issues.

Firstly, closing the Scanner and PrintWriter will also close their undying streams (in this case where Scanner is reading from the stdin). This is undesirable, as, first, you never want to close the stdin stream and second, the process may not have finished processing the streams, but more importantly, I'd be very careful about closing streams you did not create yourself, just saying.

The second issue is, you're trying to read the output (of the stream) before the SPQ has actually had time to process the output, for example, if I adapt your code and use...

import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;
import java.util.Queue;

public final class Main {
    public static void main(String[] args) throws IOException, InterruptedException {
        new Main();
    }

    public Main() throws IOException, InterruptedException {
        ProcessBuilder pb = new ProcessBuilder("bash", "-c", "ls");
        pb.redirectErrorStream(true);
        System.out.println(">> Start process");
        Process p = pb.start();
        Queue<String> stdout = new LinkedList<>();
        Thread thread = new Thread(new SPQ(p.getInputStream(), stdout));
        System.out.println(">> Start thread");
        thread.start();
        System.out.println(">> Waiting for process to exit");
        p.waitFor();
        System.out.println("<< Process has exited");
        System.out.println(stdout);
    }

    class SPQ implements Runnable {
        private Queue<String> queue;
        private InputStream istrm;

        public SPQ(InputStream istrm, Queue<String> queue) {
            this.istrm = istrm;
            this.queue = queue;
        }

        public void run() {
            System.out.println(">> Started reading stream");
            try {
                final byte[] buffer = new byte[1024];
                for (int length = 0; (length = istrm.read(buffer)) != -1;) {
                    queue.add(new String(buffer, 0, length)); // store output in queue
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("<< Done reading stream");
        }
    }
}

It will output...

>> Start process
>> Start thread
>> Waiting for process to exit
<< Process has exited
>> Started reading stream
[]
<< Done reading stream

As you can see, I've printed the contents of the Queue BEFORE the thread has finished processing the stream.

If I add thread.join(); after p.waitFor(); it will print...

>> Start process
>> Start thread
>> Waiting for process to exit
>> Started reading stream
<< Done reading stream
<< Process has exited
[contents
of
my
working
directory
which
is
not
very
interesting]

Runnable example...

import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;
import java.util.Queue;

public final class Main {
    public static void main(String[] args) throws IOException, InterruptedException {
        new Main();
    }

    public Main() throws IOException, InterruptedException {
        ProcessBuilder pb = new ProcessBuilder("bash", "-c", "ls");
        pb.redirectErrorStream(true);
        System.out.println(">> Start process");
        Process p = pb.start();
        Queue<String> stdout = new LinkedList<>();
        Thread thread = new Thread(new SPQ(p.getInputStream(), stdout));
        System.out.println(">> Start thread");
        thread.start();
        System.out.println(">> Waiting for process to exit");
        p.waitFor();
        thread.join();
        System.out.println("<< Process has exited");
        System.out.println(stdout);
    }

    class SPQ implements Runnable {
        private Queue<String> queue;
        private InputStream istrm;

        public SPQ(InputStream istrm, Queue<String> queue) {
            this.istrm = istrm;
            this.queue = queue;
        }

        public void run() {
            System.out.println(">> Started reading stream");
            try {
                final byte[] buffer = new byte[1024];
                for (int length = 0; (length = istrm.read(buffer)) != -1;) {
                    queue.add(new String(buffer, 0, length)); // store output in queue
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("<< Done reading stream");
        }
    }
}

Please note - I'm not running Windows so I can't test Powershell and I've done the minimal example I can think of, so I'm not writing content to the stream, but conceptually, this fixed the issue you seem to be having

  • Related