4

Edit: see here!

I have a thread with the Runnable shown below. There is a problem with it which I can't figure out: half of the times I call interrupt() on the thread (to stop it) it does not actually terminate (the InterruptedException is not catched).

private class DataRunnable implements Runnable {
    @Override
    public void run() {
        Log.d(TAG, "DataRunnable started");
        while (true) {
            try {
                final String currentTemperature = HeatingSystem.get("currentTemperature");
                mView.post(() -> showData(currentTemperature));
            } catch (ConnectException e) {
                mView.post(() -> showConnectionMessage());
                break;
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                break;
            }
        }
        Log.d(TAG, "DataRunnable terminated");
    }
}

The problem is the HeatingSystem.get(String) method which does the long network operation. I guess somewhere in that method the interrupted flag is reset but I can't find what statement would do that (I didn't find it mentioned in the references for all classes involved in the method, like HttpURLConnection). The method is below (it is not written by me).

/**
 * Retrieves all data except for weekProgram
 * @param attribute_name
 *            = { "day", "time", "currentTemperature", "dayTemperature",
 *            "nightTemperature", "weekProgramState" }; Note that
 *            "weekProgram" has not been included, because it has a more
 *            complex value than a single value. Therefore the funciton
 *            getWeekProgram() is implemented which return a WeekProgram
 *            object that can be easily altered.
 */
public static String get(String attribute_name) throws ConnectException,
        IllegalArgumentException {
    // If XML File does not contain the specified attribute, than
    // throw NotFound or NotFoundArgumentException
    // You can retrieve every attribute with a single value. But for the
    // WeekProgram you need to call getWeekProgram().
    String link = "";
    boolean match = false;
    String[] valid_names = {"day", "time", "currentTemperature",
            "dayTemperature", "nightTemperature", "weekProgramState"};
    String[] tag_names = {"current_day", "time", "current_temperature",
            "day_temperature", "night_temperature", "week_program_state"};
    int i;
    for (i = 0; i < valid_names.length; i++) {
        if (attribute_name.equalsIgnoreCase(valid_names[i])) {
            match = true;
            link = HeatingSystem.BASE_ADDRESS + "/" + valid_names[i];
            break;
        }
    }

    if (match) {
        InputStream in = null;
        try {
            HttpURLConnection connect = getHttpConnection(link, "GET");
            in = connect.getInputStream();

            /**
             * For Debugging Note that when the input stream is already used
             * with this BufferedReader, then after that the XmlPullParser
             * can no longer use it. This will cause an error/exception.
             *
             * BufferedReader inn = new BufferedReader(new
             * InputStreamReader(in)); String testLine = ""; while((testLine
             * = inn.readLine()) != null) { System.out.println("Line: " +
             * testLine); }
             */
            // Set up an XML parser.
            XmlPullParser parser = Xml.newPullParser();
            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES,
                    false);
            parser.setInput(in, "UTF-8"); // Enter the stream.
            parser.nextTag();
            parser.require(XmlPullParser.START_TAG, null, tag_names[i]);

            int eventType = parser.getEventType();

            // Find the single value.
            String value = "";
            while (eventType != XmlPullParser.END_DOCUMENT) {
                if (eventType == XmlPullParser.TEXT) {
                    value = parser.getText();
                    break;
                }
                eventType = parser.next();
            }

            return value;
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            System.out.println("FileNotFound Exception! " + e.getMessage());
            // e.printStackTrace();
        } catch (XmlPullParserException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (in != null)
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
    } else {
        // return null;
        throw new IllegalArgumentException("Invalid Input Argument: \""
                + attribute_name + "\".");
    }
    return null;
}

/**
 * Method for GET and PUT requests
 * @param link
 * @param type
 * @return
 * @throws IOException
 * @throws MalformedURLException
 * @throws UnknownHostException
 * @throws FileNotFoundException
 */
private static HttpURLConnection getHttpConnection(String link, String type)
        throws IOException, MalformedURLException, UnknownHostException,
        FileNotFoundException {
    URL url = new URL(link);
    HttpURLConnection connect = (HttpURLConnection) url.openConnection();
    connect.setReadTimeout(HeatingSystem.TIME_OUT);
    connect.setConnectTimeout(HeatingSystem.TIME_OUT);
    connect.setRequestProperty("Content-Type", "application/xml");
    connect.setRequestMethod(type);
    if (type.equalsIgnoreCase("GET")) {
        connect.setDoInput(true);
        connect.setDoOutput(false);
    } else if (type.equalsIgnoreCase("PUT")) {
        connect.setDoInput(false);
        connect.setDoOutput(true);
    }
    connect.connect();
    return connect;
}

Does someone know what in the method above could cause the problem?

The Thread.sleep() also throws InterruptException when interrupt() has been called before entering Thread.sleep(): Calling Thread.sleep() with *interrupted status* set? .

I checked if Thread.sleep() is reached after an interrupt, and it is reached.

This is how DataRunnable is started and interrupted (I do always get the "onPause called" log):

@Override
public void onResume() {
    connect();
    super.onResume();
}

@Override
public void onPause() {
    Log.d(TAG, "onPause called");
    mDataThread.interrupt();
    super.onPause();
}

private void connect() {
    if (mDataThread != null && mDataThread.isAlive()) {
        Log.e(TAG, "mDataThread is alive while it shouldn't!"); // TODO: remove this for production.
    }
    setVisibleView(mLoading);
    mDataThread = new Thread(new DataRunnable());
    mDataThread.start();
}
Community
  • 1
  • 1
mhvis
  • 125
  • 2
  • 11
  • An InterruptedException is only thrown when the Thread.sleep is in progress while you call Thread.interrupt() on it. You'll also need to poll the the status of the thread to check if it's interrupted. And if it is, stop further work. – Measurity Jun 17 '15 at 20:37
  • @Measuring I posted a link at the end of the question that says that it should also work when Thread.interrupt() is called before entering Thread.sleep(). Anyway, I tried it by doing `if (Thread.currentThread().isInterrupted()) break;` before the Thread.sleep() but the problem persists. – mhvis Jun 17 '15 at 20:47
  • 1
    Can you include the code where you create the DataRunnable and also the part where you interrupt it? – Measurity Jun 17 '15 at 20:50
  • 1
    @Measuring Included the parts, thanks for the feedback so far. – mhvis Jun 17 '15 at 20:57
  • 1
    Could connect have been called multiple times? Spawning multiple threads with the same Runnable? I was also wrong about Thread.sleep() not throwing InterruptedException if thread is already interrupted. Sorry for that. I also don't think I'll be able to see the problem without debugging the code. It might be worth trying to add System.out.println's in the Runnable code to get a good sense of what's taking long and what's being executed. – Measurity Jun 17 '15 at 21:16
  • @Measuring doesn't matter that you missed that. I'm going to do some more debugging. Already tried a lot (by disabling code and logging) and it seems to lead to a problem in the `HeatingSystem.get(String)` method. I didn't fully debug that method however, will do that. `connect()` is only called once by the way. – mhvis Jun 17 '15 at 22:04
  • 2
    Here is how I would debug it. I would have the thread interrupt itself by adding a call to `Thread.currentThread().interrupt();` at the very beginning of `DataRunnable`'s `run()` method. Then I would add a bunch of `System.out.println("line XX - " + Thread.currentThread().isInterrupted());` statements all over the place. That way, I'll narrow down the line where the interrupted status all of a sudden changes to false. Hope this helps. (or instead of prints, just debug through each line while watching the `isInterrupted()` value) – sstan Jun 17 '15 at 22:18
  • @sstan I didn't think of calling the interrupt at the start of the `run()` method but it seems that tip leads to the cause. I get a stacktrace right now with `java.io.InterruptedIOException` at the line `in = connect.getInputStream();`. I don't know yet why I haven't seen that one before, probably because the interrupt calls at random moments are mostly at the longest running statement at which moment apparently `printStackTrace()` is not called. I'll write more later. – mhvis Jun 17 '15 at 22:47

3 Answers3

3

This is not really an answer but I don't know where to put it. With further debugging I think I encountered odd behavior and I was able to reproduce it in a test class. Now I'm curious whether this actually is wrong behavior as I suspect, and whether other people could reproduce it. I hope that it works to write this as an answer.

(Could it be better to make this into a new question, or actually just file a bug report?)

Below is the test class, it has to be ran on Android since it's about the getInputStream() call which behaves differently on Android (don't know exactly why). On Android, getInputStream() will throw an InterruptedIOException when interrupted. The thread below loops and gets interrupted after a second. Thus when it gets interrupted, the exception should be thrown by getInputStream() and should be catched using the catch block. This works correctly sometimes, but most of the times the exception is not thrown! Instead only the interrupted flag is reset, thus changed from interrupted==true to interrupted==false and that is then caught by the if. The message in the if pops up for me. This seems to me to be wrong behavior.

import java.net.HttpURLConnection;
import java.net.URL;

class InterruptTest {

InterruptTest() {
    Thread thread = new Thread(new ConnectionRunnable());
    thread.start();
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {}
    thread.interrupt();
}

private class ConnectionRunnable implements Runnable {
    @Override
    public void run() {
        while (true) {
            try {
                URL url = new URL("http://www.google.com");
                HttpURLConnection connect = (HttpURLConnection) url.openConnection();

                boolean wasInterruptedBefore = Thread.currentThread().isInterrupted();
                connect.getInputStream(); // This call seems to behave odd(ly?)
                boolean wasInterruptedAfter = Thread.currentThread().isInterrupted();

                if (wasInterruptedBefore == true && wasInterruptedAfter == false) {
                    System.out.println("Wut! Interrupted changed from true to false while no InterruptedIOException or InterruptedException was thrown");
                    break;
                }
            } catch (Exception e) {
                System.out.println(e.getClass().getName() + ": " + e.getMessage());
                break;
            }
            for (int i = 0; i < 100000; i += 1) { // Crunching
                System.out.print("");
            }
        }
        System.out.println("ConnectionThread is stopped");
    }
}
}
mhvis
  • 125
  • 2
  • 11
  • 1
    well, it may not be an answer, but I'm glad you posted it. I've been very curious about what you would find. I hope you file a bug report. And, as for getting this to work for you, I think `edr`'s suggestion of using your own flag is the right way to go. Good luck! – sstan Jun 18 '15 at 23:50
  • Did you post a bug-report? Also, did you ever figure out the explanation? I am unable to tell whether I am facing the same problem (the bug is rare and sensitive to code changes), and would like to know more about whether Thread.interrupt() can sometimes fail. – user21820 May 30 '22 at 20:37
  • @user21820 I have not, I can't help you with it. Good luck anyway – mhvis Jun 12 '22 at 15:45
  • @mhvis: Oh ok thanks for the reply! In my case, I think it's not a bug in the JVM after all, because I managed to find the (very rare) race-condition that was due to my algorithm. Incidentally I might even be able to suggest a possible explanation for what you observed; if `connect.getInputStream();` internally does a `try { ... } catch(InterruptedException e) { ... }` but does not restore the flag, then it can clear your interrupt status contrary to your expectation. – user21820 Jun 12 '22 at 16:34
2

I have been struggling with similar problems in the past, and I never found a satisfying solution to this. The only workaround that always worked for me was introducing a volatile boolean "stopRequested" member variable that is set to true in order to interrupt the Runnable and checked in the while condition of the Runnable instead of the Thread's interrupted status.

The unfortunate detail about my experiences with this is that I have never been able to extract a small compilable example that reproduces this problem. Are you able to do that? In case you are not able to do that, I'd be interested to know if you are positive that you are working with the right instance of mDataThread? You could verify this by logging its hashcode...

edr
  • 436
  • 3
  • 5
  • 15
  • Out of curiosity, did you experience this on Android as well, like OP? – sstan Jun 17 '15 at 22:02
  • @sstan No I was experiencing this in standard Java, and mostly when working with Executors and Futures. – edr Jun 18 '15 at 09:17
  • Accepted since I use the workaround, also [see this](http://stackoverflow.com/a/30923619/2373688) – mhvis Jun 19 '15 at 09:59
0

You'll need to change your while loop to:

// You'll need to change your while loop check to this for it to reliably stop when interrupting.
while(!Thread.currentThread().isInterrupted()) {
    try {
        final String currentTemperature = HeatingSystem.get("currentTemperature");
        mView.post(() -> showData(currentTemperature));
    } catch (ConnectException e) {
        mView.post(() -> showConnectionMessage());
        break;
    }

    try {
        Thread.sleep(10);
    } catch (InterruptedException e) {
        break;
    }
}

Instead of while(true).

Note that Thread.interrupted() and Thread.currentThread().isInterrupted() do different things. The first one resets the interrupted status after the check. The latter leaves the status unchanged.

Measurity
  • 1,316
  • 1
  • 12
  • 24
  • 1
    I have tried this but it still only terminates half of the time when calling `interrupt()` on the thread. This check is already done in the while loop at the end. – mhvis Jun 17 '15 at 20:39