The risk of InterruptedException appears very often in multi-threading environment. As a declared exception, it must be defined either in method signature or caught in try-catch block. However, it must be done in some correct way.
A virtual conference at the intersection of Data and AI. This is not a conference for the hype. Its real users talking about real experiences.
- 40+ speakers with the likes of Hannes from Duck DB, Sol Rashidi, Joe Reis, Sadie St. Lawrence, Ryan Wolf from nvidia, Rebecca from lidl
- 12th September 2024
- Three simultaneous tracks
- Panels, Lighting Talks, Keynotes, Booth crawls, Roundtables and Entertainment.
- Topics include (ingestion, finops for data, data for inference (feature platforms), data for ML observability
- 100% virtual and 100% free
👉 Register here
What is InterruptedException ?
Internally each thread has a flag called interrupt status, telling if it was interrupted. Throwing an InterruptedException means that some other process interrupted occupied (sleeping, waiting...) thread. The problem about this exception is that every time when it's thrown, it resets interrupt status to false. In consequence, any thread from higher level can't know about thread interruption.
This lack of knowledge can lead to problematic situations. For instance, when ThreadPoolExecutor launches thread workers, it checks every time if started worker is not interrupted. The verification is made on Thread's isInterrupted() and interrupted() methods. The first one is an instance method and returns true if given Thread has been interrupted. The second one is class static method and it's odder than isInterrupted(). Its oddity consists on the fact that 2 subsequent calls won't return the same result ! The first call will return true or false, depending if current thread was interrupted. But the second call will return false because every time interrupt status is reset.
Are you observed something else ? Yes, there are 2 methods to check if given thread has been interrupted and thus, 2 types of thread: current thread and currently executing thread. What's the difference between them ? The current thread is a thread represented by given instance of Thread class. The currently executing thread is a thread being executed in given context. You can see the difference in the 3rd section on test called should_see_the_difference_between_thread_and_currently_executing_thread.
Handle InterruptedException
That's all for parenthesis and let's go to the solution for interrupt status reset. The solution consists on implementing auto-management for InterruptedException in potentially interruptible thread. Here we can find some good and bad practices to deal with InterruptedException.
First of all, let's see what shouldn't be done:
- it shouldn't be catched silently - throwing an InterruptedException causes the cleanup of interrupt status flag. You can see that in the Javadoc of some methods, as for example sleep(long) of Thread class:
* @throws InterruptedException * if any thread has interrupted the current thread. The * interrupted status of the current thread is * cleared when this exception is thrown. */ public static native void sleep(long millis) throws InterruptedException;
Concretely speaking, code similar to this one should be avoided:try { Thread.sleep(5_000L); // ... } catch (InterruptedException ie) { // ... do nothing // ... LOGGER.error(ie); // ... ie.printStackTrace(); // -> all of them are bad ideas because // interrupt status is still false (even if // something interrupted this code) }
Instead, some of good practices are advised:
- rethrow InterruptedException - by rethrowing InterruptedException we can inform other API users that given method uses some blocking operations. An example of this use case is sleep(long) method of Thread class:
public static native void sleep(long millis) throws InterruptedException
- interrupt thread manually - since interrupt status was reset to false by InterruptedException, putting it back to true is quite natural choice. Thanks to that higher level of code can learn about interruption and adapts its behavior if needed (for example: stop a loop):
try { // ... } catch (InterruptedException ie) { Thread.currentThread().interrupt(); // now you can log, clean some data etc. }
A good example of handling InterruptedException is Uninterruptibles class of Google Guava which puts back interrupt status inside finally block (version 19.0):public static void awaitUninterruptibly(CountDownLatch latch) { boolean interrupted = false; try { while (true) { try { latch.await(); return; } catch (InterruptedException e) { interrupted = true; } } } finally { if (interrupted) { Thread.currentThread().interrupt(); } } }
InterruptedException in tests
Here we can see some examples illustrating the differences between interrupt-related methods and 2 types of threads we've seen in the first section:
@Test public void should_prove_that_interrupted_resets_thread_status() throws InterruptedException { Thread longRunningTask = new Thread(() -> {}, "test"); longRunningTask.start(); // interrupted() refers to current thread while interrupt(), as na instance // method, refers to Thread object that it is called on. // It's why here we call currentThread().interrupt() Thread.currentThread().interrupt(); assertThat(Thread.currentThread().interrupted()).isTrue(); // At the 2nd call, interrupted flag should be reset assertThat(Thread.currentThread().interrupted()).isFalse(); // The instance isInterrupted() method should be false assertThat(longRunningTask.isInterrupted()).isFalse(); } @Test public void should_have_inconsistent_interrupted_status_for_badly_handled_interrupted_exception() throws InterruptedException { CountDownLatch latch = new CountDownLatch(1); boolean[] interruptedThreadStatus = new boolean[] {false}; Thread longRunningTask = new Thread(() -> { try { Thread.sleep(5_000L); } catch (InterruptedException e) { // Do nothing } interruptedThreadStatus[0] = Thread.currentThread().isInterrupted(); latch.countDown(); }); longRunningTask.start(); longRunningTask.interrupt(); latch.await(2, TimeUnit.SECONDS); assertThat(interruptedThreadStatus[0]).isFalse(); } @Test public void should_have_consistent_interrupted_status_for_correctly_2() throws InterruptedException { CountDownLatch latch = new CountDownLatch(1); boolean[] interruptedThreadStatus = new boolean[] {false}; Thread longRunningTask = new Thread(() -> { try { Thread.sleep(5_000L); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } interruptedThreadStatus[0] = Thread.currentThread().isInterrupted(); latch.countDown(); }); longRunningTask.start(); longRunningTask.interrupt(); latch.await(2, TimeUnit.SECONDS); assertThat(interruptedThreadStatus[0]).isTrue(); } @Test public void should_see_the_difference_between_thread_and_currently_executing_thread() throws InterruptedException { Map<String, String> nameContexts = new HashMap<>(); CountDownLatch latch = new CountDownLatch(2); Thread longRunningTask1 = new Thread(() -> { Uninterruptibles.sleepUninterruptibly(5L, TimeUnit.SECONDS); nameContexts.put("T1-current", Thread.currentThread().getName()); Thread t = new Thread(() -> { nameContexts.put("T1_child-current", Thread.currentThread().getName()); }, "T1_child"); t.start(); latch.countDown(); }, "T1"); longRunningTask1.start(); latch.await(6L, TimeUnit.SECONDS); assertThat(nameContexts.get("T1-current")).isEqualTo("T1"); assertThat(nameContexts.get("T1_child-current")).isEqualTo("T1_child"); // currently executing thread from this point of view will be // probably called 'main' but we'll only check if it's not one of previously defined assertThat(Thread.currentThread().getName()).isNotEqualTo("T1"); assertThat(Thread.currentThread().getName()).isNotEqualTo("T1_child"); assertThat(longRunningTask1.getName()).isEqualTo("T1"); }
This post describes some aspects of interruptible world in Java. In the first section it introduces InterruptedException, produces when blocking event (sleep, wait...) is interrupted. It also explains why this exception should be handled differently than the others and what is the difference between current thread and currently executing thread. The second part shows bad and good practices to deal with InterruptedException. At the end some tests shows interrupt-related methods.