Edit:
To test this problem outside of Android environment I've created a Java application that creates an ExecutorService
, provides a task of AttackScript
(identical class) and then terminates.
This works 100% as expected, the thread is interrupted and the task is stopped.
You don't even have to cancel a task by its Future.cancel(true)
. ExecutorService.shutdownNow()
does the job. Is there something in the Android's Service
that somehow messes with the thread pool?
Code that works as expcepted:
public static void main(String[] args) {
AttackScript script = new AttackScript("http://ninjaflex.com/");
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.submit(script);
executor.submit(script);
executor.submit(script);
executor.submit(script);
sleep(1300);
// Automatically interrupts threads in the pool.
executor.shutdownNow();
}
private static void sleep(long timeMilli){
try {
Thread.sleep(timeMilli);
} catch(Exception e) {
System.out.println("Error sleep()");
}
}
Original post:
I have an Android Service
where it includes an ExecutorService
field, responsible to run some tasks.
The tasks are objects of the AttackScript
class. I cache the Future
references in a Map<String,Future>
, called tasks, so that I will be able to cancel them later.
Future future = executor.submit(new AttackScript(attack.getWebsite()));
tasks.put(attack.getPushId(), future);
In Service
'sonDestroy()
(called when user presses a notification button) I am cancelling all tasks
private void cancelAllTasks() {
for (Map.Entry<String, Future> futureEntry : tasks.entrySet()) {
futureEntry.getValue().cancel(true);
}
}
and then shutdown the executor:
private void shutdownThreadPool() {
// https://www.baeldung.com/java-executor-service-tutorial
executor.shutdown();
try {
if (executor.awaitTermination(800, TimeUnit.MILLISECONDS))
executor.shutdownNow();
} catch (InterruptedException e) {
executor.shutdownNow();
}
}
Finally here is the AttackScript class:
public class AttackScript implements Runnable {
private static final String TAG = "AttackScript";
private URL url;
public AttackScript(String website) {
initializeUrl(website);
}
private void initializeUrl(String website) {
try {
url = new URL(website);
} catch (MalformedURLException e) {
Log.e(TAG, "Wrong url?", e);
}
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
readUrl();
}
Log.d(TAG, "Stopped requesting from " + url + " server.");
}
private void readUrl() {
InputStream in = null;
try {
in = url.openStream();
} catch (IOException e) {
Log.e(TAG, "openStream() error.", e);
} finally {
closeInputStream(in);
}
}
private void closeInputStream(InputStream in) {
try {
in.close();
Log.d(TAG, "InputStream closed for " + url);
} catch (IOException e) {
Log.e(TAG, "Error while closing the input stream.", e);
}
}
}
The weird part is that rarely, like 1 out of 10, tasks are interrupted and AttackScript
's execution stops. But the other 9 the tasks were not interrupted, continuing to openStreams() on URL
s.
Forced to find an alternative solution I completely removed the use of a thread pool and now implementing single Thread
s, stored in a Map
.
The interruption was, again, never happening so an AtomicBoolean
is now controlling the Thread's execution.
private AtomicBoolean stopped = new AtomicBoolean(false);
@Override
public void run() {
while (!stopped.get()) {
readUrl();
}
}
public void stopExecution() {
stopped.set(true);
}
It's a desperate move, but the only one that works so far.
You answered already with a valid workaround to avoid this issue but I will explain the cause. The fault is not with the ExecutorService
but with the thread's interrupt status being cleared silently by the network library.
As you and another commenter have found this could very well depend on the particular device you are using and its Android version.
As of Android 4.4 OkHttp
is used as the HttpUrlConnection
. There is a race condition between when each thread is interrupted and whether or not the InputStream
has been closed in older versions.
As a part of the close()
call this code is eventually executed:
public void throwIfReached() throws IOException {
if (Thread.interrupted()) {
throw new InterruptedIOException("thread interrupted");
}
if (hasDeadline && deadlineNanoTime - System.nanoTime() <= 0) {
throw new InterruptedIOException("deadline reached");
}
}
You can see based on the Thread.interrupted()
call it clears the interrupt status of the thread and never sets it again.
To make matters worse it looks like you can instead rely on the InterruptedIOException
but that is silently handled internally when closing the stream so you have no chance to handle it.
Your code sample worked for me when using a more recent version of OkHttp
. In later versions it looks like more care was taken to keep the interrupt status and it actually works as expected.
However, based on some searching it looks like historically interrupts do not play nicely with OkHttp
and to stop a request they recommend Call.cancel()
where possible instead.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With