Merge "JarFile: Add tests for ambiguous chains"
diff --git a/harmony-tests/src/test/java/org/apache/harmony/tests/java/io/SerializationStressTest4.java b/harmony-tests/src/test/java/org/apache/harmony/tests/java/io/SerializationStressTest4.java
index 1e569e8..c5dd4f0 100644
--- a/harmony-tests/src/test/java/org/apache/harmony/tests/java/io/SerializationStressTest4.java
+++ b/harmony-tests/src/test/java/org/apache/harmony/tests/java/io/SerializationStressTest4.java
@@ -721,35 +721,6 @@
}
- public void test_writeObject_Collections_UnmodifiableMap_UnmodifiableEntrySet() throws Exception {
- // Test for method void
- // java.io.ObjectOutputStream.writeObject(java.util.Collections.UnmodifiableMap.UnmodifiableEntrySet)
-
- Object objToSave = null;
- Object objLoaded = null;
-
-
- objToSave = java.util.Collections.unmodifiableMap(MAP).entrySet();
- if (DEBUG)
- System.out.println("Obj = " + objToSave);
- objLoaded = dumpAndReload(objToSave);
-
- // Has to have worked
- boolean equals;
- equals = ((java.util.Collection) objToSave).size() == ((java.util.Collection) objLoaded)
- .size();
- if (equals) {
- java.util.Iterator iter1 = ((java.util.Collection) objToSave)
- .iterator(), iter2 = ((java.util.Collection) objLoaded)
- .iterator();
- while (iter1.hasNext())
- equals = equals && iter1.next().equals(iter2.next());
- }
- assertTrue(MSG_TEST_FAILED + objToSave, equals);
-
-
- }
-
public void test_writeObject_NumberFormat() {
// Test for method void
// java.io.ObjectOutputStream.writeObject(java.text.NumberFormat)
diff --git a/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/ThreadGroupTest.java b/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/ThreadGroupTest.java
index dbf2e22..a437939 100644
--- a/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/ThreadGroupTest.java
+++ b/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/ThreadGroupTest.java
@@ -21,62 +21,41 @@
public class ThreadGroupTest extends junit.framework.TestCase {
- class MyThread extends Thread {
- public volatile int heartBeat = 0;
+ private TestThreadDefaultUncaughtExceptionHandler testThreadDefaultUncaughtExceptionHandler;
+ private ThreadGroup rootThreadGroup;
+ private ThreadGroup initialThreadGroup;
+ private Thread.UncaughtExceptionHandler originalThreadDefaultUncaughtExceptionHandler;
- public MyThread(ThreadGroup group, String name)
- throws SecurityException, IllegalThreadStateException {
- super(group, name);
+ @Override
+ protected void setUp() {
+ initialThreadGroup = Thread.currentThread().getThreadGroup();
+ rootThreadGroup = initialThreadGroup;
+ while (rootThreadGroup.getParent() != null) {
+ rootThreadGroup = rootThreadGroup.getParent();
}
- @Override
- public void run() {
- while (true) {
- heartBeat++;
- try {
- Thread.sleep(50);
- } catch (InterruptedException e) {
- }
- }
- }
-
- public boolean isActivelyRunning() {
- long MAX_WAIT = 100;
- return isActivelyRunning(MAX_WAIT);
- }
-
- public boolean isActivelyRunning(long maxWait) {
- int beat = heartBeat;
- long start = System.currentTimeMillis();
- do {
- Thread.yield();
- int beat2 = heartBeat;
- if (beat != beat2) {
- return true;
- }
- } while (System.currentTimeMillis() - start < maxWait);
- return false;
- }
-
+ // When running as a CTS test Android will by default treat an uncaught exception as a
+ // fatal application error and kill the test. To avoid this the default
+ // UncaughtExceptionHandler is replaced for the duration of the test (if one exists). It
+ // also allows us to test that ultimately the default handler is called if a ThreadGroup's
+ // UncaughtExceptionHandler doesn't handle an exception.
+ originalThreadDefaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
+ testThreadDefaultUncaughtExceptionHandler = new TestThreadDefaultUncaughtExceptionHandler();
+ Thread.setDefaultUncaughtExceptionHandler(testThreadDefaultUncaughtExceptionHandler);
}
- private ThreadGroup rootThreadGroup = null;
+ @Override
+ protected void tearDown() {
+ // Reset the uncaughtExceptionHandler to what it was when the test began.
+ Thread.setDefaultUncaughtExceptionHandler(originalThreadDefaultUncaughtExceptionHandler);
+ }
- private ThreadGroup initialThreadGroup = null;
-
- /**
- * java.lang.ThreadGroup#ThreadGroup(java.lang.String)
- */
+ // Test for method java.lang.ThreadGroup(java.lang.String)
public void test_ConstructorLjava_lang_String() {
- // Test for method java.lang.ThreadGroup(java.lang.String)
-
- // Unfortunately we have to use other APIs as well as we test the
- // constructor
-
- ThreadGroup newGroup = null;
- ThreadGroup initial = getInitialThreadGroup();
+ // Unfortunately we have to use other APIs as well as we test the constructor
+ ThreadGroup initial = initialThreadGroup;
final String name = "Test name";
- newGroup = new ThreadGroup(name);
+ ThreadGroup newGroup = new ThreadGroup(name);
assertTrue(
"Has to be possible to create a subgroup of current group using simple constructor",
newGroup.getParent() == initial);
@@ -84,39 +63,28 @@
// cleanup
newGroup.destroy();
-
}
- /**
- * java.lang.ThreadGroup#ThreadGroup(java.lang.ThreadGroup,
- *java.lang.String)
- */
+ // Test for method java.lang.ThreadGroup(java.lang.ThreadGroup, java.lang.String)
public void test_ConstructorLjava_lang_ThreadGroupLjava_lang_String() {
- // Test for method java.lang.ThreadGroup(java.lang.ThreadGroup,
- // java.lang.String)
-
- // Unfortunately we have to use other APIs as well as we test the
- // constructor
-
+ // Unfortunately we have to use other APIs as well as we test the constructor
ThreadGroup newGroup = null;
-
try {
newGroup = new ThreadGroup(null, null);
} catch (NullPointerException e) {
}
- assertNull("Can't create a ThreadGroup with a null parent",
- newGroup);
+ assertNull("Can't create a ThreadGroup with a null parent", newGroup);
- newGroup = new ThreadGroup(getInitialThreadGroup(), null);
+ newGroup = new ThreadGroup(initialThreadGroup, null);
assertTrue("Has to be possible to create a subgroup of current group",
newGroup.getParent() == Thread.currentThread().getThreadGroup());
// Lets start all over
newGroup.destroy();
- newGroup = new ThreadGroup(getRootThreadGroup(), "a name here");
+ newGroup = new ThreadGroup(rootThreadGroup, "a name here");
assertTrue("Has to be possible to create a subgroup of root group",
- newGroup.getParent() == getRootThreadGroup());
+ newGroup.getParent() == rootThreadGroup);
// Lets start all over
newGroup.destroy();
@@ -126,16 +94,11 @@
} catch (IllegalThreadStateException e) {
newGroup = null;
}
- ;
- assertNull("Can't create a subgroup of a destroyed group",
- newGroup);
+ assertNull("Can't create a subgroup of a destroyed group", newGroup);
}
- /**
- * java.lang.ThreadGroup#activeCount()
- */
+ // Test for method int java.lang.ThreadGroup.activeCount()
public void test_activeCount() {
- // Test for method int java.lang.ThreadGroup.activeCount()
ThreadGroup tg = new ThreadGroup("activeCount");
Thread t1 = new Thread(tg, new Runnable() {
public void run() {
@@ -159,13 +122,9 @@
tg.destroy();
}
- /**
- * java.lang.ThreadGroup#destroy()
- */
+ // Test for method void java.lang.ThreadGroup.destroy()
public void test_destroy() {
- // Test for method void java.lang.ThreadGroup.destroy()
-
- final ThreadGroup originalCurrent = getInitialThreadGroup();
+ final ThreadGroup originalCurrent = initialThreadGroup;
ThreadGroup testRoot = new ThreadGroup(originalCurrent, "Test group");
final int DEPTH = 4;
final Vector<ThreadGroup> subgroups = buildRandomTreeUnder(testRoot, DEPTH);
@@ -175,15 +134,13 @@
for (int i = 0; i < subgroups.size(); i++) {
ThreadGroup child = subgroups.elementAt(i);
- assertEquals("Destroyed child can't have children", 0, child
- .activeCount());
+ assertEquals("Destroyed child can't have children", 0, child.activeCount());
boolean passed = false;
try {
child.destroy();
} catch (IllegalThreadStateException e) {
passed = true;
}
- ;
assertTrue("Destroyed child can't be destroyed again", passed);
}
@@ -202,7 +159,6 @@
} catch (IllegalThreadStateException e) {
passed = true;
}
- ;
assertTrue("Daemon should have been destroyed already", passed);
passed = false;
@@ -211,7 +167,6 @@
} catch (IllegalThreadStateException e) {
passed = true;
}
- ;
assertTrue("Daemon parent should have been destroyed automatically",
passed);
@@ -231,12 +186,7 @@
noOp.start();
// Wait for the no-op thread to run inside daemon ThreadGroup
- try {
- noOp.join();
- } catch (InterruptedException ie) {
- fail("Should not be interrupted");
- }
- ;
+ waitForThreadToDieUninterrupted(noOp);
passed = false;
try {
@@ -244,10 +194,7 @@
} catch (IllegalThreadStateException e) {
passed = true;
}
- ;
- assertTrue(
- "Daemon group should have been destroyed already when last thread died",
- passed);
+ assertTrue("Daemon group should have been destroyed already when last thread died", passed);
testRoot = new ThreadGroup(originalCurrent, "Test group (daemon)");
noOp = new Thread(testRoot, null, "no-op thread") {
@@ -261,8 +208,7 @@
}
};
- // Has to execute the next lines in an interval < the sleep interval of
- // the no-op thread
+ // Has to execute the next lines in an interval < the sleep interval of the no-op thread
noOp.start();
passed = false;
try {
@@ -272,29 +218,18 @@
}
assertTrue("Can't destroy a ThreadGroup that has threads", passed);
- // But after the thread dies, we have to be able to destroy the thread
- // group
- try {
- noOp.join();
- } catch (InterruptedException ie) {
- fail("Should not be interrupted");
- }
- ;
+ // But after the thread dies, we have to be able to destroy the thread group
+ waitForThreadToDieUninterrupted(noOp);
passed = true;
try {
testRoot.destroy();
} catch (IllegalThreadStateException its) {
passed = false;
}
- assertTrue(
- "Should be able to destroy a ThreadGroup that has no threads",
- passed);
-
+ assertTrue("Should be able to destroy a ThreadGroup that has no threads", passed);
}
- /**
- * java.lang.ThreadGroup#destroy()
- */
+ // Test for method java.lang.ThreadGroup.destroy()
public void test_destroy_subtest0() {
ThreadGroup group1 = new ThreadGroup("test_destroy_subtest0");
group1.destroy();
@@ -305,13 +240,9 @@
}
}
- /**
- * java.lang.ThreadGroup#getMaxPriority()
- */
+ // Test for method int java.lang.ThreadGroup.getMaxPriority()
public void test_getMaxPriority() {
- // Test for method int java.lang.ThreadGroup.getMaxPriority()
-
- final ThreadGroup originalCurrent = getInitialThreadGroup();
+ final ThreadGroup originalCurrent = initialThreadGroup;
ThreadGroup testRoot = new ThreadGroup(originalCurrent, "Test group");
boolean passed = true;
@@ -322,38 +253,26 @@
}
assertTrue("Should be able to set priority", passed);
- assertTrue("New value should be the same as we set", testRoot
- .getMaxPriority() == Thread.MIN_PRIORITY);
+ assertTrue("New value should be the same as we set",
+ testRoot.getMaxPriority() == Thread.MIN_PRIORITY);
testRoot.destroy();
-
}
- /**
- * java.lang.ThreadGroup#getName()
- */
+ // Test for method java.lang.String java.lang.ThreadGroup.getName()
public void test_getName() {
- // Test for method java.lang.String java.lang.ThreadGroup.getName()
-
- final ThreadGroup originalCurrent = getInitialThreadGroup();
+ final ThreadGroup originalCurrent = initialThreadGroup;
final String name = "Test group";
final ThreadGroup testRoot = new ThreadGroup(originalCurrent, name);
- assertTrue("Setting a name&getting does not work", testRoot.getName()
- .equals(name));
+ assertTrue("Setting a name&getting does not work", testRoot.getName().equals(name));
testRoot.destroy();
-
}
- /**
- * java.lang.ThreadGroup#getParent()
- */
+ // Test for method java.lang.ThreadGroup java.lang.ThreadGroup.getParent()
public void test_getParent() {
- // Test for method java.lang.ThreadGroup
- // java.lang.ThreadGroup.getParent()
-
- final ThreadGroup originalCurrent = getInitialThreadGroup();
+ final ThreadGroup originalCurrent = initialThreadGroup;
ThreadGroup testRoot = new ThreadGroup(originalCurrent, "Test group");
assertTrue("Parent is wrong", testRoot.getParent() == originalCurrent);
@@ -381,38 +300,16 @@
testRoot.destroy();
}
- /**
- * java.lang.ThreadGroup#isDaemon()
- */
- public void test_isDaemon() {
- // Test for method boolean java.lang.ThreadGroup.isDaemon()
-
- daemonTests();
-
- }
-
- /**
- * java.lang.ThreadGroup#list()
- */
+ // Test for method void java.lang.ThreadGroup.list()
public void test_list() {
- // Test for method void java.lang.ThreadGroup.list()
-
- final ThreadGroup originalCurrent = getInitialThreadGroup();
- // wipeSideEffectThreads destroy all side effect of threads created in
- // java.lang.Thread
- boolean result = wipeSideEffectThreads(originalCurrent);
- if (result == false) {
- fail("wipe threads in test_list() not successful");
- }
- final ThreadGroup testRoot = new ThreadGroup(originalCurrent,
- "Test group");
+ final ThreadGroup originalCurrent = initialThreadGroup;
+ final ThreadGroup testRoot = new ThreadGroup(originalCurrent, "Test group");
// First save the original System.out
java.io.PrintStream originalOut = System.out;
try {
- java.io.ByteArrayOutputStream contentsStream = new java.io.ByteArrayOutputStream(
- 100);
+ java.io.ByteArrayOutputStream contentsStream = new java.io.ByteArrayOutputStream(100);
java.io.PrintStream newOut = new java.io.PrintStream(contentsStream);
// We have to "redirect" System.out to test the method 'list'
@@ -421,13 +318,11 @@
originalCurrent.list();
/*
- * The output has to look like this
- *
- * java.lang.ThreadGroup[name=main,maxpri=10] Thread[main,5,main]
- * java.lang.ThreadGroup[name=Test group,maxpri=10]
- *
- */
-
+ * The output has to look like this:
+ *
+ * java.lang.ThreadGroup[name=main,maxpri=10] Thread[main,5,main]
+ * java.lang.ThreadGroup[name=Test group,maxpri=10]
+ */
String contents = new String(contentsStream.toByteArray());
boolean passed = (contents.indexOf("ThreadGroup[name=main") != -1) &&
(contents.indexOf("Thread[") != -1) &&
@@ -442,17 +337,11 @@
// No matter what, we need to restore the original System.out
System.setOut(originalOut);
}
-
}
- /**
- * java.lang.ThreadGroup#parentOf(java.lang.ThreadGroup)
- */
+ // Test for method boolean java.lang.ThreadGroup.parentOf(java.lang.ThreadGroup)
public void test_parentOfLjava_lang_ThreadGroup() {
- // Test for method boolean
- // java.lang.ThreadGroup.parentOf(java.lang.ThreadGroup)
-
- final ThreadGroup originalCurrent = getInitialThreadGroup();
+ final ThreadGroup originalCurrent = initialThreadGroup;
final ThreadGroup testRoot = new ThreadGroup(originalCurrent,
"Test group");
final int DEPTH = 4;
@@ -460,8 +349,7 @@
final ThreadGroup[] allChildren = allGroups(testRoot);
for (ThreadGroup element : allChildren) {
- assertTrue("Have to be parentOf all children", testRoot
- .parentOf(element));
+ assertTrue("Have to be parentOf all children", testRoot.parentOf(element));
}
assertTrue("Have to be parentOf itself", testRoot.parentOf(testRoot));
@@ -471,14 +359,20 @@
!arrayIncludes(groups(testRoot.getParent()), testRoot));
}
- /**
- * java.lang.ThreadGroup#setDaemon(boolean)
- */
- public void test_setDaemonZ() {
- // Test for method void java.lang.ThreadGroup.setDaemon(boolean)
+ // Test for method boolean java.lang.ThreadGroup.isDaemon() and
+ // void java.lang.ThreadGroup.setDaemon(boolean)
+ public void test_setDaemon_isDaemon() {
+ final ThreadGroup originalCurrent = initialThreadGroup;
+ final ThreadGroup testRoot = new ThreadGroup(originalCurrent,
+ "Test group");
- daemonTests();
+ testRoot.setDaemon(true);
+ assertTrue("Setting daemon&getting does not work", testRoot.isDaemon());
+ testRoot.setDaemon(false);
+ assertTrue("Setting daemon&getting does not work", !testRoot.isDaemon());
+
+ testRoot.destroy();
}
/*
@@ -501,13 +395,9 @@
assertFalse(ctg.isDaemon());
}
- /**
- * java.lang.ThreadGroup#setMaxPriority(int)
- */
+ // Test for method void java.lang.ThreadGroup.setMaxPriority(int)
public void test_setMaxPriorityI() {
- // Test for method void java.lang.ThreadGroup.setMaxPriority(int)
-
- final ThreadGroup originalCurrent = getInitialThreadGroup();
+ final ThreadGroup originalCurrent = initialThreadGroup;
ThreadGroup testRoot = new ThreadGroup(originalCurrent, "Test group");
boolean passed;
@@ -529,8 +419,7 @@
passed = testRoot.getMaxPriority() == Thread.MIN_PRIORITY;
assertTrue(
"setMaxPriority: Any value smaller than MIN_PRIORITY is adjusted to MIN_PRIORITY. Before: "
- + currentMax + " , after: " + testRoot.getMaxPriority(),
- passed);
+ + currentMax + " , after: " + testRoot.getMaxPriority(), passed);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -579,8 +468,7 @@
assertTrue(
"Priority of leaf child group has to be much smaller than original root group",
- current.getMaxPriority() == testRoot.getMaxPriority()
- - TOTAL_DEPTH);
+ current.getMaxPriority() == testRoot.getMaxPriority() - TOTAL_DEPTH);
testRoot.destroy();
@@ -599,351 +487,184 @@
testRoot.destroy();
}
- /**
- * java.lang.ThreadGroup#uncaughtException(java.lang.Thread,
- *java.lang.Throwable)
+ /*
+ * Test for method void java.lang.ThreadGroup.uncaughtException(java.lang.Thread,
+ * java.lang.Throwable)
+ * Tests if a Thread tells its ThreadGroup about ThreadDeath.
*/
- @SuppressWarnings("deprecation")
- public void test_uncaughtExceptionLjava_lang_ThreadLjava_lang_Throwable() {
- // Test for method void
- // java.lang.ThreadGroup.uncaughtException(java.lang.Thread,
- // java.lang.Throwable)
+ public void test_uncaughtException_threadDeath() {
+ final boolean[] passed = new boolean[1];
- final ThreadGroup originalCurrent = getInitialThreadGroup();
+ ThreadGroup testRoot = new ThreadGroup(rootThreadGroup,
+ "Test Forcing a throw of ThreadDeath") {
+ @Override
+ public void uncaughtException(Thread t, Throwable e) {
+ if (e instanceof ThreadDeath) {
+ passed[0] = true;
+ }
+ // always forward, any exception
+ super.uncaughtException(t, e);
+ }
+ };
- // indices for the array defined below
- final int TEST_DEATH = 0;
- final int TEST_OTHER = 1;
- final int TEST_EXCEPTION_IN_UNCAUGHT = 2;
- final int TEST_OTHER_THEN_DEATH = 3;
- final int TEST_FORCING_THROW_THREAD_DEATH = 4;
- final int TEST_KILLING = 5;
- final int TEST_DEATH_AFTER_UNCAUGHT = 6;
+ final ThreadDeath threadDeath = new ThreadDeath();
+ Thread thread = new Thread(testRoot, null, "suicidal thread") {
+ @Override
+ public void run() {
+ throw threadDeath;
+ }
+ };
+ thread.start();
+ waitForThreadToDieUninterrupted(thread);
+ testThreadDefaultUncaughtExceptionHandler.assertWasCalled(thread, threadDeath);
- final boolean[] passed = new boolean[] { false, false, false, false,
- false, false, false };
+ testRoot.destroy();
+ assertTrue(
+ "Any thread should notify its ThreadGroup about its own death, even if suicide:"
+ + testRoot, passed[0]);
+ }
- ThreadGroup testRoot;
- Thread thread;
+ /*
+ * Test for method void java.lang.ThreadGroup.uncaughtException(java.lang.Thread,
+ * java.lang.Throwable)
+ * Test if a Thread tells its ThreadGroup about a natural (non-exception) death.
+ */
+ public void test_uncaughtException_naturalDeath() {
+ final boolean[] failed = new boolean[1];
+ ThreadGroup testRoot = new ThreadGroup(initialThreadGroup, "Test ThreadDeath") {
+ @Override
+ public void uncaughtException(Thread t, Throwable e) {
+ failed[0] = true;
+
+ // always forward any exception
+ super.uncaughtException(t, e);
+ }
+ };
+
+ Thread thread = new Thread(testRoot, null, "no-op thread");
+ thread.start();
+ waitForThreadToDieUninterrupted(thread);
+ testThreadDefaultUncaughtExceptionHandler.assertWasNotCalled();
+ testRoot.destroy();
+ assertFalse("A thread should not call uncaughtException when it dies:"
+ + testRoot, failed[0]);
+ }
+
+ /*
+ * Test for method void java.lang.ThreadGroup.uncaughtException(java.lang.Thread,
+ * java.lang.Throwable)
+ * Test if a Thread tells its ThreadGroup about an Exception
+ */
+ public void test_uncaughtException_runtimeException() {
// Our own exception class
class TestException extends RuntimeException {
private static final long serialVersionUID = 1L;
}
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- // - - - - - - -
- testRoot = new ThreadGroup(originalCurrent,
- "Test killing a Thread, forcing it to throw ThreadDeath") {
- @Override
- public void uncaughtException(Thread t, Throwable e) {
- if (e instanceof ThreadDeath) {
- passed[TEST_KILLING] = true;
- }
- // always forward, any exception
- super.uncaughtException(t, e);
- }
- };
+ final boolean[] passed = new boolean[1];
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- // - - - - - - -
- testRoot = new ThreadGroup(originalCurrent,
- "Test Forcing a throw of ThreadDeath") {
- @Override
- public void uncaughtException(Thread t, Throwable e) {
- if (e instanceof ThreadDeath) {
- passed[TEST_FORCING_THROW_THREAD_DEATH] = true;
- }
- // always forward, any exception
- super.uncaughtException(t, e);
- }
- };
-
- // Test if a Thread tells its ThreadGroup about ThreadDeath
- thread = new Thread(testRoot, null, "suicidal thread") {
- @Override
- public void run() {
- throw new ThreadDeath();
- }
- };
- thread.start();
- try {
- thread.join();
- } catch (InterruptedException ie) {
- fail("Should not have been interrupted");
- }
- testRoot.destroy();
- assertTrue(
- "Any thread should notify its ThreadGroup about its own death, even if suicide:"
- + testRoot, passed[TEST_FORCING_THROW_THREAD_DEATH]);
-
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- // - - - - - - -
-
- testRoot = new ThreadGroup(originalCurrent, "Test ThreadDeath") {
- @Override
- public void uncaughtException(Thread t, Throwable e) {
- passed[TEST_DEATH] = false;
- // always forward, any exception
- super.uncaughtException(t, e);
- }
- };
-
- // Test if a Thread tells its ThreadGroup about ThreadDeath
- passed[TEST_DEATH] = true;
- thread = new Thread(testRoot, null, "no-op thread");
- thread.start();
- try {
- thread.join();
- } catch (InterruptedException ie) {
- fail("Should not have been interrupted");
- }
- testRoot.destroy();
- assertTrue("A thread should not call uncaughtException when it dies:"
- + testRoot, passed[TEST_DEATH]);
-
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- // - - - - - - -
-
- testRoot = new ThreadGroup(originalCurrent, "Test other Exception") {
+ ThreadGroup testRoot = new ThreadGroup(initialThreadGroup, "Test other Exception") {
@Override
public void uncaughtException(Thread t, Throwable e) {
if (e instanceof TestException) {
- passed[TEST_OTHER] = true;
- } else {
- // only forward exceptions other than our test
- super.uncaughtException(t, e);
+ passed[0] = true;
}
+ // always forward any exception
+ super.uncaughtException(t, e);
}
};
- // Test if a Thread tells its ThreadGroup about an Exception
- thread = new Thread(testRoot, null, "no-op thread") {
+ final TestException testException = new TestException();
+ Thread thread = new Thread(testRoot, null, "RuntimeException thread") {
@Override
public void run() {
- throw new TestException();
+ throw testException;
}
};
thread.start();
- try {
- thread.join();
- } catch (InterruptedException ie) {
- fail("Should not have been interrupted");
- }
+ waitForThreadToDieUninterrupted(thread);
+ testThreadDefaultUncaughtExceptionHandler.assertWasCalled(thread, testException);
testRoot.destroy();
assertTrue(
"Any thread should notify its ThreadGroup about an uncaught exception:"
- + testRoot, passed[TEST_OTHER]);
+ + testRoot, passed[0]);
+ }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- // - - - - - - -
-
- // Our own uncaught exception class
- class UncaughtException extends TestException {
+ /*
+ * Test for method void java.lang.ThreadGroup.uncaughtException(java.lang.Thread,
+ * java.lang.Throwable)
+ * Test if a handler doesn't pass on the exception to super.uncaughtException that's ok.
+ */
+ public void test_uncaughtException_exceptionHandledByHandler() {
+ // Our own exception class
+ class TestException extends RuntimeException {
private static final long serialVersionUID = 1L;
}
- testRoot = new ThreadGroup(originalCurrent,
- "Test Exception in uncaught exception") {
+ ThreadGroup testRoot = new ThreadGroup(initialThreadGroup, "Test other Exception") {
@Override
public void uncaughtException(Thread t, Throwable e) {
- if (e instanceof TestException) {
- passed[TEST_EXCEPTION_IN_UNCAUGHT] = true;
- // Let's simulate an error inside our uncaughtException
- // method.
- // This should be no-op according to the spec
- throw new UncaughtException();
- }
- // only forward exceptions other than our test
- super.uncaughtException(t, e);
- }
- };
-
- // Test if an Exception in uncaughtException is really a no-op
- thread = new Thread(testRoot, null, "no-op thread") {
- @Override
- public void run() {
- try {
- throw new TestException();
- } catch (UncaughtException ue) {
- // any exception in my ThreadGroup's uncaughtException must
- // not be propagated.
- // If it gets propagated and we detected that, the test failed
- passed[TEST_EXCEPTION_IN_UNCAUGHT] = false;
- }
- }
- };
- thread.start();
- try {
- thread.join();
- } catch (InterruptedException ie) {
- fail("Should not have been interrupted");
- }
- testRoot.destroy();
- assertTrue(
- "Any uncaughtException in uncaughtException should be no-op:"
- + testRoot, passed[TEST_EXCEPTION_IN_UNCAUGHT]);
-
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- // - - - - - - -
-
- // This is a mix of 2 of the tests above. It is assumed that ThreadDeath
- // and any random exception do work , tested separately. Now we test
- // if after an uncaughtException is forwarded to the ThreadGroup and
- // the Thread dies, if ThreadDeath is also forwarded. It should be
- // (so that a ThreadGroup can know its Thread died)
- testRoot = new ThreadGroup(originalCurrent,
- "Test Uncaught followed by ThreadDeath") {
- @Override
- public void uncaughtException(Thread t, Throwable e) {
- if (e instanceof ThreadDeath) {
- passed[TEST_DEATH_AFTER_UNCAUGHT] = true;
- }
- if (e instanceof TestException) {
- passed[TEST_OTHER_THEN_DEATH] = true;
- } else {
- // only forward exceptions other than our test
+ // Swallow TestException and always forward any other exception
+ if (!(e instanceof TestException)) {
super.uncaughtException(t, e);
}
}
};
- // Test if a Thread tells its ThreadGroup about an Exception and also
- // ThreadDeath
- thread = new Thread(testRoot, null, "no-op thread") {
+ final TestException testException = new TestException();
+ Thread thread = new Thread(testRoot, null, "RuntimeException thread") {
@Override
public void run() {
- throw new TestException();
+ throw testException;
}
};
thread.start();
- try {
- thread.join();
- } catch (InterruptedException ie) {
- fail("Should not have been interrupted");
- }
+ waitForThreadToDieUninterrupted(thread);
+ testThreadDefaultUncaughtExceptionHandler.assertWasNotCalled();
testRoot.destroy();
}
- @Override
- protected void setUp() {
- initialThreadGroup = Thread.currentThread().getThreadGroup();
- rootThreadGroup = initialThreadGroup;
- while (rootThreadGroup.getParent() != null) {
- rootThreadGroup = rootThreadGroup.getParent();
- }
- }
-
- @Override
- protected void tearDown() {
- try {
- // Give the threads a chance to die.
- Thread.sleep(50);
- } catch (InterruptedException e) {
- }
- }
-
- private Thread[] threads(ThreadGroup parent) {
- // No API to get the count of immediate children only ?
- int count = parent.activeCount();
- Thread[] all = new Thread[count];
- int actualSize = parent.enumerate(all, false);
- Thread[] result;
- if (actualSize == all.length) {
- result = all;
- } else {
- result = new Thread[actualSize];
- System.arraycopy(all, 0, result, 0, actualSize);
+ /*
+ * Test for method void java.lang.ThreadGroup.uncaughtException(java.lang.Thread,
+ * java.lang.Throwable)
+ * Tests an exception thrown by the handler itself.
+ */
+ public void test_uncaughtException_exceptionInUncaughtException() {
+ // Our own uncaught exception classes
+ class UncaughtException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
}
- return result;
+ ThreadGroup testRoot = new ThreadGroup(initialThreadGroup,
+ "Test Exception in uncaught exception") {
+ @Override
+ public void uncaughtException(Thread t, Throwable e) {
+ // This should be no-op according to the spec
+ throw new UncaughtException();
+ }
+ };
+ Thread thread = new Thread(testRoot, null, "no-op thread") {
+ @Override
+ public void run() {
+ throw new RuntimeException();
+ }
+ };
+ thread.start();
+ waitForThreadToDieUninterrupted(thread);
+ testThreadDefaultUncaughtExceptionHandler.assertWasNotCalled();
+ testRoot.destroy();
}
- private ThreadGroup getInitialThreadGroup() {
- return initialThreadGroup;
- }
-
- private ThreadGroup[] allGroups(ThreadGroup parent) {
+ private static ThreadGroup[] allGroups(ThreadGroup parent) {
int count = parent.activeGroupCount();
ThreadGroup[] all = new ThreadGroup[count];
parent.enumerate(all, true);
return all;
}
- private void daemonTests() {
- // Test for method void java.lang.ThreadGroup.setDaemon(boolean)
-
- final ThreadGroup originalCurrent = getInitialThreadGroup();
- final ThreadGroup testRoot = new ThreadGroup(originalCurrent,
- "Test group");
-
- testRoot.setDaemon(true);
- assertTrue("Setting daemon&getting does not work", testRoot.isDaemon());
-
- testRoot.setDaemon(false);
- assertTrue("Setting daemon&getting does not work", !testRoot.isDaemon());
-
- testRoot.destroy();
-
- }
-
- private boolean wipeAllThreads(final ThreadGroup aGroup) {
- boolean ok = true;
- Thread[] threads = threads(aGroup);
- for (Thread t : threads) {
- ok = ok && wipeThread(t);
- }
-
- // Recursively for subgroups (if any)
- ThreadGroup[] children = groups(aGroup);
- for (ThreadGroup element : children) {
- ok = ok && wipeAllThreads(element);
- }
-
- return ok;
-
- }
-
- private boolean wipeSideEffectThreads(ThreadGroup aGroup) {
- boolean ok = true;
- Thread[] threads = threads(aGroup);
- for (Thread t : threads) {
- if (t.getName().equals("SimpleThread")
- || t.getName().equals("Bogus Name")
- || t.getName().equals("Testing")
- || t.getName().equals("foo")
- || t.getName().equals("Test Group")
- || t.getName().equals("Squawk")
- || t.getName().equals("Thread-1")
- || t.getName().equals("firstOne")
- || t.getName().equals("secondOne")
- || t.getName().equals("Thread-16")
- || t.getName().equals("Thread-14")) {
- ok = ok && wipeThread(t);
- }
- }
-
- // Recursively for subgroups (if any)
- ThreadGroup[] children = groups(aGroup);
-
- for (ThreadGroup element : children) {
- ok = ok && wipeSideEffectThreads(element);
- if (element.getName().equals("Test Group")
- || element.getName().equals("foo")
- || element.getName().equals("jp")) {
- element.destroy();
- }
- }
- try {
- // Give the threads a chance to die.
- Thread.sleep(50);
- } catch (InterruptedException e) {
- }
- return ok;
- }
-
- private void asyncBuildRandomTreeUnder(final ThreadGroup aGroup,
+ private static void asyncBuildRandomTreeUnder(final ThreadGroup aGroup,
final int depth, final Vector<ThreadGroup> allCreated) {
if (depth <= 0) {
return;
@@ -969,7 +690,7 @@
}
- private Vector<ThreadGroup> asyncBuildRandomTreeUnder(final ThreadGroup aGroup,
+ private static Vector<ThreadGroup> asyncBuildRandomTreeUnder(final ThreadGroup aGroup,
final int depth) {
Vector<ThreadGroup> result = new Vector<ThreadGroup>();
asyncBuildRandomTreeUnder(aGroup, depth, result);
@@ -977,19 +698,7 @@
}
- private boolean allSuspended(Vector<MyThread> threads) {
- for (int i = 0; i < threads.size(); i++) {
- MyThread t = threads.elementAt(i);
- if (t.isActivelyRunning()) {
- return false;
- }
- }
-
- return true;
-
- }
-
- private ThreadGroup[] groups(ThreadGroup parent) {
+ private static ThreadGroup[] groups(ThreadGroup parent) {
// No API to get the count of immediate children only ?
int count = parent.activeGroupCount();
ThreadGroup[] all = new ThreadGroup[count];
@@ -1013,57 +722,11 @@
}
- private Vector<MyThread> populateGroupsWithThreads(final ThreadGroup aGroup,
- final int threadCount) {
- Vector<MyThread> result = new Vector<MyThread>();
- populateGroupsWithThreads(aGroup, threadCount, result);
- return result;
-
- }
-
- private void populateGroupsWithThreads(final ThreadGroup aGroup,
- final int threadCount, final Vector<MyThread> allCreated) {
- for (int i = 0; i < threadCount; i++) {
- final int iClone = i;
- final String name = "(MyThread)N =" + iClone + "/" + threadCount
- + " ,Vector size at creation: " + allCreated.size();
-
- MyThread t = new MyThread(aGroup, name);
- allCreated.addElement(t);
- }
-
- // Recursively for subgroups (if any)
- ThreadGroup[] children = groups(aGroup);
- for (ThreadGroup element : children) {
- populateGroupsWithThreads(element, threadCount, allCreated);
- }
-
- }
-
- private int random(int max) {
-
+ private static int random(int max) {
return 1 + ((new Object()).hashCode() % max);
-
}
- @SuppressWarnings("deprecation")
- private boolean wipeThread(Thread t) {
- t.stop();
- try {
- t.join(1000);
- } catch (InterruptedException ie) {
- fail("Should not have been interrupted");
- }
- // The thread had plenty (subjective) of time to die so there
- // is a problem.
- if (t.isAlive()) {
- return false;
- }
-
- return true;
- }
-
- private Vector<ThreadGroup> buildRandomTreeUnder(ThreadGroup aGroup, int depth) {
+ private static Vector<ThreadGroup> buildRandomTreeUnder(ThreadGroup aGroup, int depth) {
Vector<ThreadGroup> result = asyncBuildRandomTreeUnder(aGroup, depth);
while (true) {
int sizeBefore = result.size();
@@ -1087,24 +750,46 @@
}
- private boolean arrayIncludes(Object[] array, Object toTest) {
+ private static boolean arrayIncludes(Object[] array, Object toTest) {
for (Object element : array) {
if (element == toTest) {
return true;
}
}
-
return false;
}
- protected void myassertTrue(String msg, boolean b) {
- // This method is defined here just to solve a visibility problem
- // of protected methods with inner types
- assertTrue(msg, b);
+ private static void waitForThreadToDieUninterrupted(Thread thread) {
+ try {
+ thread.join();
+ } catch (InterruptedException ie) {
+ fail("Should not have been interrupted");
+ }
}
- private ThreadGroup getRootThreadGroup() {
- return rootThreadGroup;
+ private static class TestThreadDefaultUncaughtExceptionHandler
+ implements Thread.UncaughtExceptionHandler {
+ private boolean called;
+ private Throwable ex;
+ private Thread thread;
+
+ @Override
+ public void uncaughtException(Thread thread, Throwable ex) {
+ this.called = true;
+ this.thread = thread;
+ this.ex = ex;
+ }
+
+ public void assertWasCalled(Thread thread, Throwable ex) {
+ assertTrue(called);
+ assertSame(this.thread, thread);
+ assertSame(this.ex, ex);
+ }
+
+ public void assertWasNotCalled() {
+ assertFalse(called);
+ }
}
+
}
diff --git a/luni/src/main/java/java/io/FileInputStream.java b/luni/src/main/java/java/io/FileInputStream.java
index 644f749..5debe64 100644
--- a/luni/src/main/java/java/io/FileInputStream.java
+++ b/luni/src/main/java/java/io/FileInputStream.java
@@ -21,10 +21,8 @@
import java.nio.NioUtils;
import java.nio.channels.FileChannel;
-import java.util.Arrays;
import libcore.io.ErrnoException;
import libcore.io.IoBridge;
-import libcore.io.IoUtils;
import libcore.io.Libcore;
import libcore.io.Streams;
import static libcore.io.OsConstants.*;
@@ -118,7 +116,7 @@
channel.close();
}
if (shouldClose) {
- IoUtils.close(fd);
+ IoBridge.closeAndSignalBlockedThreads(fd);
} else {
// An owned fd has been invalidated by IoUtils.close, but
// we need to explicitly stop using an unowned fd (http://b/4361076).
diff --git a/luni/src/main/java/java/io/FileOutputStream.java b/luni/src/main/java/java/io/FileOutputStream.java
index f5ba11e..e796e80 100644
--- a/luni/src/main/java/java/io/FileOutputStream.java
+++ b/luni/src/main/java/java/io/FileOutputStream.java
@@ -20,9 +20,8 @@
import dalvik.system.CloseGuard;
import java.nio.NioUtils;
import java.nio.channels.FileChannel;
-import java.util.Arrays;
import libcore.io.IoBridge;
-import libcore.io.IoUtils;
+
import static libcore.io.OsConstants.*;
/**
@@ -136,7 +135,7 @@
channel.close();
}
if (shouldClose) {
- IoUtils.close(fd);
+ IoBridge.closeAndSignalBlockedThreads(fd);
} else {
// An owned fd has been invalidated by IoUtils.close, but
// we need to explicitly stop using an unowned fd (http://b/4361076).
diff --git a/luni/src/main/java/java/io/RandomAccessFile.java b/luni/src/main/java/java/io/RandomAccessFile.java
index 6e88fe0..eac7641 100644
--- a/luni/src/main/java/java/io/RandomAccessFile.java
+++ b/luni/src/main/java/java/io/RandomAccessFile.java
@@ -25,7 +25,6 @@
import java.util.Arrays;
import libcore.io.ErrnoException;
import libcore.io.IoBridge;
-import libcore.io.IoUtils;
import libcore.io.Libcore;
import libcore.io.Memory;
import libcore.io.SizeOf;
@@ -163,7 +162,7 @@
channel.close();
channel = null;
}
- IoUtils.close(fd);
+ IoBridge.closeAndSignalBlockedThreads(fd);
}
}
diff --git a/luni/src/main/java/java/net/InetAddress.java b/luni/src/main/java/java/net/InetAddress.java
index 98ad098..885b472 100644
--- a/luni/src/main/java/java/net/InetAddress.java
+++ b/luni/src/main/java/java/net/InetAddress.java
@@ -28,8 +28,6 @@
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.Collections;
-import java.util.Comparator;
-import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -734,7 +732,7 @@
}
}
- IoBridge.closeSocket(fd);
+ IoBridge.closeAndSignalBlockedThreads(fd);
return reached;
}
diff --git a/luni/src/main/java/java/net/PlainDatagramSocketImpl.java b/luni/src/main/java/java/net/PlainDatagramSocketImpl.java
index edf7024..a9ade28 100644
--- a/luni/src/main/java/java/net/PlainDatagramSocketImpl.java
+++ b/luni/src/main/java/java/net/PlainDatagramSocketImpl.java
@@ -78,7 +78,7 @@
public synchronized void close() {
guard.close();
try {
- IoBridge.closeSocket(fd);
+ IoBridge.closeAndSignalBlockedThreads(fd);
} catch (IOException ignored) {
}
}
diff --git a/luni/src/main/java/java/net/PlainSocketImpl.java b/luni/src/main/java/java/net/PlainSocketImpl.java
index d81303f..e3988ed 100644
--- a/luni/src/main/java/java/net/PlainSocketImpl.java
+++ b/luni/src/main/java/java/net/PlainSocketImpl.java
@@ -145,7 +145,7 @@
@Override
protected synchronized void close() throws IOException {
guard.close();
- IoBridge.closeSocket(fd);
+ IoBridge.closeAndSignalBlockedThreads(fd);
}
@Override
diff --git a/luni/src/main/java/java/nio/DatagramChannelImpl.java b/luni/src/main/java/java/nio/DatagramChannelImpl.java
index 7c92d83..a303903 100644
--- a/luni/src/main/java/java/nio/DatagramChannelImpl.java
+++ b/luni/src/main/java/java/nio/DatagramChannelImpl.java
@@ -519,7 +519,7 @@
@Override protected synchronized void implCloseSelectableChannel() throws IOException {
// A closed channel is not connected.
onDisconnect(true /* updateSocketState */);
- IoBridge.closeSocket(fd);
+ IoBridge.closeAndSignalBlockedThreads(fd);
multicastMembershipHandler = null;
if (socket != null && !socket.isClosed()) {
diff --git a/luni/src/main/java/java/nio/FileChannelImpl.java b/luni/src/main/java/java/nio/FileChannelImpl.java
index 6206d1b..9a47706 100644
--- a/luni/src/main/java/java/nio/FileChannelImpl.java
+++ b/luni/src/main/java/java/nio/FileChannelImpl.java
@@ -50,7 +50,7 @@
}
};
- private final Object stream;
+ private final Closeable ioObject;
private final FileDescriptor fd;
private final int mode;
@@ -61,9 +61,9 @@
* Create a new file channel implementation class that wraps the given
* fd and operates in the specified mode.
*/
- public FileChannelImpl(Object stream, FileDescriptor fd, int mode) {
+ public FileChannelImpl(Closeable ioObject, FileDescriptor fd, int mode) {
this.fd = fd;
- this.stream = stream;
+ this.ioObject = ioObject;
this.mode = mode;
}
@@ -86,9 +86,7 @@
}
protected void implCloseChannel() throws IOException {
- if (stream instanceof Closeable) {
- ((Closeable) stream).close();
- }
+ ioObject.close();
}
private FileLock basicLock(long position, long size, boolean shared, boolean wait) throws IOException {
diff --git a/luni/src/main/java/java/nio/NioUtils.java b/luni/src/main/java/java/nio/NioUtils.java
index 34af76b..51adddb 100644
--- a/luni/src/main/java/java/nio/NioUtils.java
+++ b/luni/src/main/java/java/nio/NioUtils.java
@@ -16,6 +16,7 @@
package java.nio;
+import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.IOException;
import java.net.SocketOption;
@@ -48,8 +49,8 @@
/**
* Helps bridge between io and nio.
*/
- public static FileChannel newFileChannel(Object stream, FileDescriptor fd, int mode) {
- return new FileChannelImpl(stream, fd, mode);
+ public static FileChannel newFileChannel(Closeable ioObject, FileDescriptor fd, int mode) {
+ return new FileChannelImpl(ioObject, fd, mode);
}
/**
diff --git a/luni/src/main/java/java/nio/SelectorImpl.java b/luni/src/main/java/java/nio/SelectorImpl.java
index d63fa63..3495523 100644
--- a/luni/src/main/java/java/nio/SelectorImpl.java
+++ b/luni/src/main/java/java/nio/SelectorImpl.java
@@ -17,18 +17,15 @@
import java.io.FileDescriptor;
import java.io.IOException;
+import java.io.InterruptedIOException;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.IllegalSelectorException;
-import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
-import static java.nio.channels.SelectionKey.*;
import java.nio.channels.Selector;
-import java.nio.channels.SocketChannel;
import java.nio.channels.spi.AbstractSelectableChannel;
import java.nio.channels.spi.AbstractSelectionKey;
import java.nio.channels.spi.AbstractSelector;
import java.nio.channels.spi.SelectorProvider;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
@@ -40,8 +37,15 @@
import libcore.io.IoUtils;
import libcore.io.Libcore;
import libcore.io.StructPollfd;
-import libcore.util.EmptyArray;
-import static libcore.io.OsConstants.*;
+
+import static java.nio.channels.SelectionKey.OP_ACCEPT;
+import static java.nio.channels.SelectionKey.OP_CONNECT;
+import static java.nio.channels.SelectionKey.OP_READ;
+import static java.nio.channels.SelectionKey.OP_WRITE;
+import static libcore.io.OsConstants.EINTR;
+import static libcore.io.OsConstants.POLLHUP;
+import static libcore.io.OsConstants.POLLIN;
+import static libcore.io.OsConstants.POLLOUT;
/*
* Default implementation of java.nio.channels.Selector
@@ -321,6 +325,7 @@
try {
Libcore.os.write(wakeupOut, new byte[] { 1 }, 0, 1);
} catch (ErrnoException ignored) {
+ } catch (InterruptedIOException ignored) {
}
return this;
}
diff --git a/luni/src/main/java/java/nio/SocketChannelImpl.java b/luni/src/main/java/java/nio/SocketChannelImpl.java
index d7cd562..e4e92f3 100644
--- a/luni/src/main/java/java/nio/SocketChannelImpl.java
+++ b/luni/src/main/java/java/nio/SocketChannelImpl.java
@@ -520,9 +520,9 @@
protected synchronized void implCloseSelectableChannel() throws IOException {
if (status != SOCKET_STATUS_CLOSED) {
status = SOCKET_STATUS_CLOSED;
- // IoBridge.closeSocket(fd) is idempotent: It is safe to call on an already-closed file
- // descriptor.
- IoBridge.closeSocket(fd);
+ // IoBridge.closeAndSignalBlockedThreads(fd) is idempotent: It is safe to call on an
+ // already-closed file descriptor.
+ IoBridge.closeAndSignalBlockedThreads(fd);
if (socket != null && !socket.isClosed()) {
socket.onClose();
}
diff --git a/luni/src/main/java/libcore/io/BlockGuardOs.java b/luni/src/main/java/libcore/io/BlockGuardOs.java
index 05a756f..eaa7f0c 100644
--- a/luni/src/main/java/libcore/io/BlockGuardOs.java
+++ b/luni/src/main/java/libcore/io/BlockGuardOs.java
@@ -19,6 +19,7 @@
import dalvik.system.BlockGuard;
import dalvik.system.SocketTagger;
import java.io.FileDescriptor;
+import java.io.InterruptedIOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
@@ -158,6 +159,11 @@
os.mkdir(path, mode);
}
+ @Override public void mkfifo(String path, int mode) throws ErrnoException {
+ BlockGuard.getThreadPolicy().onWriteToDisk();
+ os.mkfifo(path, mode);
+ }
+
@Override public FileDescriptor open(String path, int flags, int mode) throws ErrnoException {
BlockGuard.getThreadPolicy().onReadFromDisk();
if ((mode & O_ACCMODE) != O_RDONLY) {
@@ -180,32 +186,32 @@
os.posix_fallocate(fd, offset, length);
}
- @Override public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException {
+ @Override public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException {
BlockGuard.getThreadPolicy().onReadFromDisk();
return os.pread(fd, buffer, offset);
}
- @Override public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException {
+ @Override public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException {
BlockGuard.getThreadPolicy().onReadFromDisk();
return os.pread(fd, bytes, byteOffset, byteCount, offset);
}
- @Override public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException {
+ @Override public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException {
BlockGuard.getThreadPolicy().onWriteToDisk();
return os.pwrite(fd, buffer, offset);
}
- @Override public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException {
+ @Override public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException {
BlockGuard.getThreadPolicy().onWriteToDisk();
return os.pwrite(fd, bytes, byteOffset, byteCount, offset);
}
- @Override public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException {
+ @Override public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException {
BlockGuard.getThreadPolicy().onReadFromDisk();
return os.read(fd, buffer);
}
- @Override public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException {
+ @Override public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException {
BlockGuard.getThreadPolicy().onReadFromDisk();
return os.read(fd, bytes, byteOffset, byteCount);
}
@@ -215,7 +221,7 @@
return os.readlink(path);
}
- @Override public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException {
+ @Override public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException {
BlockGuard.getThreadPolicy().onReadFromDisk();
return os.readv(fd, buffers, offsets, byteCounts);
}
@@ -283,17 +289,17 @@
os.symlink(oldPath, newPath);
}
- @Override public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException {
+ @Override public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException {
BlockGuard.getThreadPolicy().onWriteToDisk();
return os.write(fd, buffer);
}
- @Override public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException {
+ @Override public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException {
BlockGuard.getThreadPolicy().onWriteToDisk();
return os.write(fd, bytes, byteOffset, byteCount);
}
- @Override public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException {
+ @Override public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException {
BlockGuard.getThreadPolicy().onWriteToDisk();
return os.writev(fd, buffers, offsets, byteCounts);
}
diff --git a/luni/src/main/java/libcore/io/ForwardingOs.java b/luni/src/main/java/libcore/io/ForwardingOs.java
index 2de35e9..0b49071 100644
--- a/luni/src/main/java/libcore/io/ForwardingOs.java
+++ b/luni/src/main/java/libcore/io/ForwardingOs.java
@@ -17,6 +17,7 @@
package libcore.io;
import java.io.FileDescriptor;
+import java.io.InterruptedIOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
@@ -90,6 +91,7 @@
public StructStat lstat(String path) throws ErrnoException { return os.lstat(path); }
public void mincore(long address, long byteCount, byte[] vector) throws ErrnoException { os.mincore(address, byteCount, vector); }
public void mkdir(String path, int mode) throws ErrnoException { os.mkdir(path, mode); }
+ public void mkfifo(String path, int mode) throws ErrnoException { os.mkfifo(path, mode); }
public void mlock(long address, long byteCount) throws ErrnoException { os.mlock(address, byteCount); }
public long mmap(long address, long byteCount, int prot, int flags, FileDescriptor fd, long offset) throws ErrnoException { return os.mmap(address, byteCount, prot, flags, fd, offset); }
public void msync(long address, long byteCount, int flags) throws ErrnoException { os.msync(address, byteCount, flags); }
@@ -99,14 +101,14 @@
public FileDescriptor[] pipe() throws ErrnoException { return os.pipe(); }
public int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException { return os.poll(fds, timeoutMs); }
public void posix_fallocate(FileDescriptor fd, long offset, long length) throws ErrnoException { os.posix_fallocate(fd, offset, length); }
- public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException { return os.pread(fd, buffer, offset); }
- public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException { return os.pread(fd, bytes, byteOffset, byteCount, offset); }
- public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException { return os.pwrite(fd, buffer, offset); }
- public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException { return os.pwrite(fd, bytes, byteOffset, byteCount, offset); }
- public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException { return os.read(fd, buffer); }
- public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException { return os.read(fd, bytes, byteOffset, byteCount); }
+ public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { return os.pread(fd, buffer, offset); }
+ public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { return os.pread(fd, bytes, byteOffset, byteCount, offset); }
+ public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { return os.pwrite(fd, buffer, offset); }
+ public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { return os.pwrite(fd, bytes, byteOffset, byteCount, offset); }
+ public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { return os.read(fd, buffer); }
+ public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { return os.read(fd, bytes, byteOffset, byteCount); }
public String readlink(String path) throws ErrnoException { return os.readlink(path); }
- public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException { return os.readv(fd, buffers, offsets, byteCounts); }
+ public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException { return os.readv(fd, buffers, offsets, byteCounts); }
public int recvfrom(FileDescriptor fd, ByteBuffer buffer, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException { return os.recvfrom(fd, buffer, flags, srcAddress); }
public int recvfrom(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException { return os.recvfrom(fd, bytes, byteOffset, byteCount, flags, srcAddress); }
public void remove(String path) throws ErrnoException { os.remove(path); }
@@ -143,7 +145,7 @@
public StructUtsname uname() { return os.uname(); }
public void unsetenv(String name) throws ErrnoException { os.unsetenv(name); }
public int waitpid(int pid, MutableInt status, int options) throws ErrnoException { return os.waitpid(pid, status, options); }
- public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException { return os.write(fd, buffer); }
- public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException { return os.write(fd, bytes, byteOffset, byteCount); }
- public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException { return os.writev(fd, buffers, offsets, byteCounts); }
+ public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { return os.write(fd, buffer); }
+ public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { return os.write(fd, bytes, byteOffset, byteCount); }
+ public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException { return os.writev(fd, buffers, offsets, byteCounts); }
}
diff --git a/luni/src/main/java/libcore/io/IoBridge.java b/luni/src/main/java/libcore/io/IoBridge.java
index 08d4837..89070cd 100644
--- a/luni/src/main/java/libcore/io/IoBridge.java
+++ b/luni/src/main/java/libcore/io/IoBridge.java
@@ -177,9 +177,15 @@
return detail;
}
- public static void closeSocket(FileDescriptor fd) throws IOException {
- if (!fd.valid()) {
- // Socket.close doesn't throw if you try to close an already-closed socket.
+ /**
+ * Closes the supplied file descriptor and sends a signal to any threads are currently blocking.
+ * In order for the signal to be sent the blocked threads must have registered with
+ * the AsynchronousCloseMonitor before they entered the blocking operation.
+ *
+ * <p>This method is a no-op if passed a {@code null} or already-closed file descriptor.
+ */
+ public static void closeAndSignalBlockedThreads(FileDescriptor fd) throws IOException {
+ if (fd == null || !fd.valid()) {
return;
}
int intFd = fd.getInt$();
diff --git a/luni/src/main/java/libcore/io/Os.java b/luni/src/main/java/libcore/io/Os.java
index a4541a7..2065c70 100644
--- a/luni/src/main/java/libcore/io/Os.java
+++ b/luni/src/main/java/libcore/io/Os.java
@@ -17,6 +17,7 @@
package libcore.io;
import java.io.FileDescriptor;
+import java.io.InterruptedIOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
@@ -82,6 +83,7 @@
public StructStat lstat(String path) throws ErrnoException;
public void mincore(long address, long byteCount, byte[] vector) throws ErrnoException;
public void mkdir(String path, int mode) throws ErrnoException;
+ public void mkfifo(String path, int mode) throws ErrnoException;
public void mlock(long address, long byteCount) throws ErrnoException;
public long mmap(long address, long byteCount, int prot, int flags, FileDescriptor fd, long offset) throws ErrnoException;
public void msync(long address, long byteCount, int flags) throws ErrnoException;
@@ -92,14 +94,14 @@
/* TODO: if we used the non-standard ppoll(2) behind the scenes, we could take a long timeout. */
public int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException;
public void posix_fallocate(FileDescriptor fd, long offset, long length) throws ErrnoException;
- public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException;
- public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException;
- public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException;
- public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException;
- public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException;
- public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException;
+ public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException;
+ public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException;
+ public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException;
+ public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException;
+ public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException;
+ public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException;
public String readlink(String path) throws ErrnoException;
- public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException;
+ public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException;
public int recvfrom(FileDescriptor fd, ByteBuffer buffer, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException;
public int recvfrom(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException;
public void remove(String path) throws ErrnoException;
@@ -136,7 +138,7 @@
public StructUtsname uname();
public void unsetenv(String name) throws ErrnoException;
public int waitpid(int pid, MutableInt status, int options) throws ErrnoException;
- public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException;
- public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException;
- public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException;
+ public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException;
+ public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException;
+ public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException;
}
diff --git a/luni/src/main/java/libcore/io/Posix.java b/luni/src/main/java/libcore/io/Posix.java
index c6f13ea..a5c3eb0 100644
--- a/luni/src/main/java/libcore/io/Posix.java
+++ b/luni/src/main/java/libcore/io/Posix.java
@@ -17,6 +17,7 @@
package libcore.io;
import java.io.FileDescriptor;
+import java.io.InterruptedIOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
@@ -84,6 +85,7 @@
public native StructStat lstat(String path) throws ErrnoException;
public native void mincore(long address, long byteCount, byte[] vector) throws ErrnoException;
public native void mkdir(String path, int mode) throws ErrnoException;
+ public native void mkfifo(String path, int mode) throws ErrnoException;
public native void mlock(long address, long byteCount) throws ErrnoException;
public native long mmap(long address, long byteCount, int prot, int flags, FileDescriptor fd, long offset) throws ErrnoException;
public native void msync(long address, long byteCount, int flags) throws ErrnoException;
@@ -93,44 +95,44 @@
public native FileDescriptor[] pipe() throws ErrnoException;
public native int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException;
public native void posix_fallocate(FileDescriptor fd, long offset, long length) throws ErrnoException;
- public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException {
+ public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException {
if (buffer.isDirect()) {
return preadBytes(fd, buffer, buffer.position(), buffer.remaining(), offset);
} else {
return preadBytes(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining(), offset);
}
}
- public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException {
+ public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException {
// This indirection isn't strictly necessary, but ensures that our public interface is type safe.
return preadBytes(fd, bytes, byteOffset, byteCount, offset);
}
- private native int preadBytes(FileDescriptor fd, Object buffer, int bufferOffset, int byteCount, long offset) throws ErrnoException;
- public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException {
+ private native int preadBytes(FileDescriptor fd, Object buffer, int bufferOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException;
+ public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException {
if (buffer.isDirect()) {
return pwriteBytes(fd, buffer, buffer.position(), buffer.remaining(), offset);
} else {
return pwriteBytes(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining(), offset);
}
}
- public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException {
+ public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException {
// This indirection isn't strictly necessary, but ensures that our public interface is type safe.
return pwriteBytes(fd, bytes, byteOffset, byteCount, offset);
}
- private native int pwriteBytes(FileDescriptor fd, Object buffer, int bufferOffset, int byteCount, long offset) throws ErrnoException;
- public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException {
+ private native int pwriteBytes(FileDescriptor fd, Object buffer, int bufferOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException;
+ public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException {
if (buffer.isDirect()) {
return readBytes(fd, buffer, buffer.position(), buffer.remaining());
} else {
return readBytes(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining());
}
}
- public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException {
+ public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException {
// This indirection isn't strictly necessary, but ensures that our public interface is type safe.
return readBytes(fd, bytes, byteOffset, byteCount);
}
- private native int readBytes(FileDescriptor fd, Object buffer, int offset, int byteCount) throws ErrnoException;
+ private native int readBytes(FileDescriptor fd, Object buffer, int offset, int byteCount) throws ErrnoException, InterruptedIOException;
public native String readlink(String path) throws ErrnoException;
- public native int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException;
+ public native int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException;
public int recvfrom(FileDescriptor fd, ByteBuffer buffer, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException {
if (buffer.isDirect()) {
return recvfromBytes(fd, buffer, buffer.position(), buffer.remaining(), flags, srcAddress);
@@ -193,17 +195,17 @@
public native StructUtsname uname();
public native void unsetenv(String name) throws ErrnoException;
public native int waitpid(int pid, MutableInt status, int options) throws ErrnoException;
- public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException {
+ public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException {
if (buffer.isDirect()) {
return writeBytes(fd, buffer, buffer.position(), buffer.remaining());
} else {
return writeBytes(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining());
}
}
- public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException {
+ public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException {
// This indirection isn't strictly necessary, but ensures that our public interface is type safe.
return writeBytes(fd, bytes, byteOffset, byteCount);
}
- private native int writeBytes(FileDescriptor fd, Object buffer, int offset, int byteCount) throws ErrnoException;
- public native int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException;
+ private native int writeBytes(FileDescriptor fd, Object buffer, int offset, int byteCount) throws ErrnoException, InterruptedIOException;
+ public native int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException;
}
diff --git a/luni/src/main/native/AsynchronousSocketCloseMonitor.cpp b/luni/src/main/native/AsynchronousCloseMonitor.cpp
similarity index 76%
rename from luni/src/main/native/AsynchronousSocketCloseMonitor.cpp
rename to luni/src/main/native/AsynchronousCloseMonitor.cpp
index 9617e9d..92d5260 100644
--- a/luni/src/main/native/AsynchronousSocketCloseMonitor.cpp
+++ b/luni/src/main/native/AsynchronousCloseMonitor.cpp
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-#define LOG_TAG "AsynchronousSocketCloseMonitor"
+#define LOG_TAG "AsynchronousCloseMonitor"
-#include "AsynchronousSocketCloseMonitor.h"
+#include "AsynchronousCloseMonitor.h"
#include "cutils/log.h"
#include <errno.h>
@@ -27,12 +27,12 @@
* We use an intrusive doubly-linked list to keep track of blocked threads.
* This gives us O(1) insertion and removal, and means we don't need to do any allocation.
* (The objects themselves are stack-allocated.)
- * Waking potentially-blocked threads when a socket is closed is O(n) in the total number of
- * blocked threads (not the number of threads actually blocked on the socket in question).
- * For now at least, this seems like a good compromise for Android.
+ * Waking potentially-blocked threads when a file descriptor is closed is O(n) in the total number
+ * of blocked threads (not the number of threads actually blocked on the file descriptor in
+ * question). For now at least, this seems like a good compromise for Android.
*/
static pthread_mutex_t blockedThreadListMutex = PTHREAD_MUTEX_INITIALIZER;
-static AsynchronousSocketCloseMonitor* blockedThreadList = NULL;
+static AsynchronousCloseMonitor* blockedThreadList = NULL;
/**
* The specific signal chosen here is arbitrary.
@@ -47,7 +47,7 @@
// Do nothing. We only sent this signal for its side-effect of interrupting syscalls.
}
-void AsynchronousSocketCloseMonitor::init() {
+void AsynchronousCloseMonitor::init() {
// Ensure that the signal we send interrupts system calls but doesn't kill threads.
// Using sigaction(2) lets us ensure that the SA_RESTART flag is not set.
// (The whole reason we're sending this signal is to unblock system calls!)
@@ -61,21 +61,27 @@
}
}
-void AsynchronousSocketCloseMonitor::signalBlockedThreads(int fd) {
+void AsynchronousCloseMonitor::signalBlockedThreads(int fd) {
ScopedPthreadMutexLock lock(&blockedThreadListMutex);
- for (AsynchronousSocketCloseMonitor* it = blockedThreadList; it != NULL; it = it->mNext) {
+ for (AsynchronousCloseMonitor* it = blockedThreadList; it != NULL; it = it->mNext) {
if (it->mFd == fd) {
+ it->mSignaled = true;
pthread_kill(it->mThread, BLOCKED_THREAD_SIGNAL);
// Keep going, because there may be more than one thread...
}
}
}
-AsynchronousSocketCloseMonitor::AsynchronousSocketCloseMonitor(int fd) {
+bool AsynchronousCloseMonitor::wasSignaled() const {
+ return mSignaled;
+}
+
+AsynchronousCloseMonitor::AsynchronousCloseMonitor(int fd) {
ScopedPthreadMutexLock lock(&blockedThreadListMutex);
// Who are we, and what are we waiting for?
mThread = pthread_self();
mFd = fd;
+ mSignaled = false;
// Insert ourselves at the head of the intrusive doubly-linked list...
mPrev = NULL;
mNext = blockedThreadList;
@@ -85,7 +91,7 @@
blockedThreadList = this;
}
-AsynchronousSocketCloseMonitor::~AsynchronousSocketCloseMonitor() {
+AsynchronousCloseMonitor::~AsynchronousCloseMonitor() {
ScopedPthreadMutexLock lock(&blockedThreadListMutex);
// Unlink ourselves from the intrusive doubly-linked list...
if (mNext != NULL) {
diff --git a/luni/src/main/native/AsynchronousCloseMonitor.h b/luni/src/main/native/AsynchronousCloseMonitor.h
new file mode 100644
index 0000000..eefbbdf
--- /dev/null
+++ b/luni/src/main/native/AsynchronousCloseMonitor.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ASYNCHRONOUS_CLOSE_MONITOR_H_included
+#define ASYNCHRONOUS_CLOSE_MONITOR_H_included
+
+#include "ScopedPthreadMutexLock.h"
+#include <pthread.h>
+
+/**
+ * AsynchronousCloseMonitor helps implement Java's asynchronous close semantics.
+ *
+ * AsynchronousCloseMonitor::init must be called before anything else.
+ *
+ * Every blocking I/O operation must be surrounded by an AsynchronousCloseMonitor
+ * instance. For example:
+ *
+ * {
+ * AsynchronousCloseMonitor monitor(fd);
+ * byteCount = ::read(fd, buf, sizeof(buf));
+ * }
+ *
+ * To interrupt all threads currently blocked on file descriptor 'fd', call signalBlockedThreads:
+ *
+ * AsynchronousCloseMonitor::signalBlockedThreads(fd);
+ *
+ * To test to see if the interruption was due to the signalBlockedThreads call:
+ *
+ * monitor.wasSignaled();
+ */
+class AsynchronousCloseMonitor {
+public:
+ AsynchronousCloseMonitor(int fd);
+ ~AsynchronousCloseMonitor();
+ bool wasSignaled() const;
+
+ static void init();
+
+ static void signalBlockedThreads(int fd);
+
+private:
+ AsynchronousCloseMonitor* mPrev;
+ AsynchronousCloseMonitor* mNext;
+ pthread_t mThread;
+ int mFd;
+ bool mSignaled;
+
+ // Disallow copy and assignment.
+ AsynchronousCloseMonitor(const AsynchronousCloseMonitor&);
+ void operator=(const AsynchronousCloseMonitor&);
+};
+
+#endif // ASYNCHRONOUS_CLOSE_MONITOR_H_included
diff --git a/luni/src/main/native/AsynchronousSocketCloseMonitor.h b/luni/src/main/native/AsynchronousSocketCloseMonitor.h
deleted file mode 100644
index 3370e22..0000000
--- a/luni/src/main/native/AsynchronousSocketCloseMonitor.h
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ASYNCHRONOUS_SOCKET_CLOSE_MONITOR_H_included
-#define ASYNCHRONOUS_SOCKET_CLOSE_MONITOR_H_included
-
-#include "ScopedPthreadMutexLock.h"
-#include <pthread.h>
-
-/**
- * AsynchronousSocketCloseMonitor helps implement Java's asynchronous Socket.close semantics.
- *
- * AsynchronousSocketCloseMonitor::init must be called before anything else.
- *
- * Every blocking network I/O operation must be surrounded by an AsynchronousSocketCloseMonitor
- * instance. For example:
- *
- * {
- * AsynchronousSocketCloseMonitor monitor(fd);
- * byteCount = ::read(fd, buf, sizeof(buf));
- * }
- *
- * To interrupt all threads currently blocked on file descriptor 'fd', call signalBlockedThreads:
- *
- * AsynchronousSocketCloseMonitor::signalBlockedThreads(fd);
- */
-class AsynchronousSocketCloseMonitor {
-public:
- AsynchronousSocketCloseMonitor(int fd);
- ~AsynchronousSocketCloseMonitor();
-
- static void init();
-
- static void signalBlockedThreads(int fd);
-
-private:
- AsynchronousSocketCloseMonitor* mPrev;
- AsynchronousSocketCloseMonitor* mNext;
- pthread_t mThread;
- int mFd;
-
- // Disallow copy and assignment.
- AsynchronousSocketCloseMonitor(const AsynchronousSocketCloseMonitor&);
- void operator=(const AsynchronousSocketCloseMonitor&);
-};
-
-#endif // ASYNCHRONOUS_SOCKET_CLOSE_MONITOR_H_included
diff --git a/luni/src/main/native/libcore_io_AsynchronousCloseMonitor.cpp b/luni/src/main/native/libcore_io_AsynchronousCloseMonitor.cpp
index 4f50ce5..a27e7b8 100644
--- a/luni/src/main/native/libcore_io_AsynchronousCloseMonitor.cpp
+++ b/luni/src/main/native/libcore_io_AsynchronousCloseMonitor.cpp
@@ -16,20 +16,20 @@
#define LOG_TAG "AsynchronousCloseMonitor"
-#include "AsynchronousSocketCloseMonitor.h"
+#include "AsynchronousCloseMonitor.h"
#include "JNIHelp.h"
#include "JniConstants.h"
#include "jni.h"
static void AsynchronousCloseMonitor_signalBlockedThreads(JNIEnv* env, jclass, jobject javaFd) {
int fd = jniGetFDFromFileDescriptor(env, javaFd);
- AsynchronousSocketCloseMonitor::signalBlockedThreads(fd);
+ AsynchronousCloseMonitor::signalBlockedThreads(fd);
}
static JNINativeMethod gMethods[] = {
NATIVE_METHOD(AsynchronousCloseMonitor, signalBlockedThreads, "(Ljava/io/FileDescriptor;)V"),
};
void register_libcore_io_AsynchronousCloseMonitor(JNIEnv* env) {
- AsynchronousSocketCloseMonitor::init();
+ AsynchronousCloseMonitor::init();
jniRegisterNativeMethods(env, "libcore/io/AsynchronousCloseMonitor", gMethods, NELEM(gMethods));
}
diff --git a/luni/src/main/native/libcore_io_Posix.cpp b/luni/src/main/native/libcore_io_Posix.cpp
index b9f16a7..65ee36f 100644
--- a/luni/src/main/native/libcore_io_Posix.cpp
+++ b/luni/src/main/native/libcore_io_Posix.cpp
@@ -16,7 +16,7 @@
#define LOG_TAG "Posix"
-#include "AsynchronousSocketCloseMonitor.h"
+#include "AsynchronousCloseMonitor.h"
#include "cutils/log.h"
#include "ExecStrings.h"
#include "JNIHelp.h"
@@ -68,31 +68,77 @@
};
/**
- * Used to retry networking system calls that can return EINTR. Unlike TEMP_FAILURE_RETRY,
- * this also handles the case where the reason for failure is that another thread called
- * Socket.close. This macro also throws exceptions on failure.
+ * Used to retry networking system calls that can be interrupted with a signal. Unlike
+ * TEMP_FAILURE_RETRY, this also handles the case where
+ * AsynchronousCloseMonitor::signalBlockedThreads(fd) is used to signal a close() or
+ * Thread.interrupt(). Other signals that result in an EINTR result are ignored and the system call
+ * is retried.
*
- * Returns the result of 'exp', though a Java exception will be pending if the result is -1.
+ * Returns the result of the system call though a Java exception will be pending if the result is
+ * -1: a SocketException if signaled via AsynchronousCloseMonitor, or ErrnoException for other
+ * failures.
*/
#define NET_FAILURE_RETRY(jni_env, return_type, syscall_name, java_fd, ...) ({ \
return_type _rc = -1; \
do { \
+ bool _wasSignaled; \
+ int _syscallErrno; \
{ \
int _fd = jniGetFDFromFileDescriptor(jni_env, java_fd); \
- AsynchronousSocketCloseMonitor _monitor(_fd); \
+ AsynchronousCloseMonitor _monitor(_fd); \
_rc = syscall_name(_fd, __VA_ARGS__); \
+ _syscallErrno = errno; \
+ _wasSignaled = _monitor.wasSignaled(); \
} \
- if (_rc == -1) { \
- if (jniGetFDFromFileDescriptor(jni_env, java_fd) == -1) { \
- jniThrowException(jni_env, "java/net/SocketException", "Socket closed"); \
+ if (_wasSignaled) { \
+ jniThrowException(jni_env, "java/net/SocketException", "Socket closed"); \
+ break; \
+ } \
+ if (_rc == -1 && _syscallErrno != EINTR) { \
+ /* TODO: with a format string we could show the arguments too, like strace(1). */ \
+ throwErrnoException(jni_env, # syscall_name); \
+ break; \
+ } \
+ } while (_rc == -1); /* _syscallErrno == EINTR && !_wasSignaled */ \
+ _rc; })
+
+/**
+ * Used to retry system calls that can be interrupted with a signal. Unlike TEMP_FAILURE_RETRY, this
+ * also handles the case where AsynchronousCloseMonitor::signalBlockedThreads(fd) is used to signal
+ * a close() or Thread.interrupt(). Other signals that result in an EINTR result are ignored and the
+ * system call is retried.
+ *
+ * Returns the result of the system call though a Java exception will be pending if the result is
+ * -1: an IOException if the file descriptor is already closed, a InterruptedIOException if signaled
+ * via AsynchronousCloseMonitor, or ErrnoException for other failures.
+ */
+#define IO_FAILURE_RETRY(jni_env, return_type, syscall_name, java_fd, ...) ({ \
+ return_type _rc = -1; \
+ int _fd = jniGetFDFromFileDescriptor(jni_env, java_fd); \
+ if (_fd == -1) { \
+ jniThrowException(jni_env, "java/io/IOException", "File descriptor closed"); \
+ } else { \
+ do { \
+ bool _wasSignaled; \
+ int _syscallErrno; \
+ { \
+ int _fd = jniGetFDFromFileDescriptor(jni_env, java_fd); \
+ AsynchronousCloseMonitor _monitor(_fd); \
+ _rc = syscall_name(_fd, __VA_ARGS__); \
+ _syscallErrno = errno; \
+ _wasSignaled = _monitor.wasSignaled(); \
+ } \
+ if (_wasSignaled) { \
+ jniThrowException(jni_env, "java/io/InterruptedIOException", # syscall_name " interrupted"); \
break; \
- } else if (errno != EINTR) { \
+ } \
+ if (_rc == -1 && _syscallErrno != EINTR) { \
/* TODO: with a format string we could show the arguments too, like strace(1). */ \
throwErrnoException(jni_env, # syscall_name); \
break; \
} \
- } \
- } while (_rc == -1); \
+ } while (_rc == -1); /* && _syscallErrno == EINTR && !_wasSignaled */ \
+ } \
_rc; })
static void throwException(JNIEnv* env, jclass exceptionClass, jmethodID ctor3, jmethodID ctor2,
@@ -907,6 +953,14 @@
throwIfMinusOne(env, "mkdir", TEMP_FAILURE_RETRY(mkdir(path.c_str(), mode)));
}
+static void Posix_mkfifo(JNIEnv* env, jobject, jstring javaPath, jint mode) {
+ ScopedUtfChars path(env, javaPath);
+ if (path.c_str() == NULL) {
+ return;
+ }
+ throwIfMinusOne(env, "mkfifo", TEMP_FAILURE_RETRY(mkfifo(path.c_str(), mode)));
+}
+
static void Posix_mlock(JNIEnv* env, jobject, jlong address, jlong byteCount) {
void* ptr = reinterpret_cast<void*>(static_cast<uintptr_t>(address));
throwIfMinusOne(env, "mlock", TEMP_FAILURE_RETRY(mlock(ptr, byteCount)));
@@ -990,11 +1044,9 @@
++count;
}
- // Since we don't know which fds -- if any -- are sockets, be conservative and register
- // all fds for asynchronous socket close monitoring.
- std::vector<AsynchronousSocketCloseMonitor*> monitors;
+ std::vector<AsynchronousCloseMonitor*> monitors;
for (size_t i = 0; i < count; ++i) {
- monitors.push_back(new AsynchronousSocketCloseMonitor(fds[i].fd));
+ monitors.push_back(new AsynchronousCloseMonitor(fds[i].fd));
}
int rc = poll(fds.get(), count, timeoutMs);
for (size_t i = 0; i < monitors.size(); ++i) {
@@ -1029,8 +1081,7 @@
if (bytes.get() == NULL) {
return -1;
}
- int fd = jniGetFDFromFileDescriptor(env, javaFd);
- return throwIfMinusOne(env, "pread", TEMP_FAILURE_RETRY(pread64(fd, bytes.get() + byteOffset, byteCount, offset)));
+ return IO_FAILURE_RETRY(env, ssize_t, pread64, javaFd, bytes.get() + byteOffset, byteCount, offset);
}
static jint Posix_pwriteBytes(JNIEnv* env, jobject, jobject javaFd, jbyteArray javaBytes, jint byteOffset, jint byteCount, jlong offset) {
@@ -1038,8 +1089,7 @@
if (bytes.get() == NULL) {
return -1;
}
- int fd = jniGetFDFromFileDescriptor(env, javaFd);
- return throwIfMinusOne(env, "pwrite", TEMP_FAILURE_RETRY(pwrite64(fd, bytes.get() + byteOffset, byteCount, offset)));
+ return IO_FAILURE_RETRY(env, ssize_t, pwrite64, javaFd, bytes.get() + byteOffset, byteCount, offset);
}
static jint Posix_readBytes(JNIEnv* env, jobject, jobject javaFd, jobject javaBytes, jint byteOffset, jint byteCount) {
@@ -1047,8 +1097,7 @@
if (bytes.get() == NULL) {
return -1;
}
- int fd = jniGetFDFromFileDescriptor(env, javaFd);
- return throwIfMinusOne(env, "read", TEMP_FAILURE_RETRY(read(fd, bytes.get() + byteOffset, byteCount)));
+ return IO_FAILURE_RETRY(env, ssize_t, read, javaFd, bytes.get() + byteOffset, byteCount);
}
static jstring Posix_readlink(JNIEnv* env, jobject, jstring javaPath) {
@@ -1070,8 +1119,7 @@
if (!ioVec.init(buffers, offsets, byteCounts)) {
return -1;
}
- int fd = jniGetFDFromFileDescriptor(env, javaFd);
- return throwIfMinusOne(env, "readv", TEMP_FAILURE_RETRY(readv(fd, ioVec.get(), ioVec.size())));
+ return IO_FAILURE_RETRY(env, ssize_t, readv, javaFd, ioVec.get(), ioVec.size());
}
static jint Posix_recvfromBytes(JNIEnv* env, jobject, jobject javaFd, jobject javaBytes, jint byteOffset, jint byteCount, jint flags, jobject javaInetSocketAddress) {
@@ -1417,8 +1465,7 @@
if (bytes.get() == NULL) {
return -1;
}
- int fd = jniGetFDFromFileDescriptor(env, javaFd);
- return throwIfMinusOne(env, "write", TEMP_FAILURE_RETRY(write(fd, bytes.get() + byteOffset, byteCount)));
+ return IO_FAILURE_RETRY(env, ssize_t, write, javaFd, bytes.get() + byteOffset, byteCount);
}
static jint Posix_writev(JNIEnv* env, jobject, jobject javaFd, jobjectArray buffers, jintArray offsets, jintArray byteCounts) {
@@ -1426,8 +1473,7 @@
if (!ioVec.init(buffers, offsets, byteCounts)) {
return -1;
}
- int fd = jniGetFDFromFileDescriptor(env, javaFd);
- return throwIfMinusOne(env, "writev", TEMP_FAILURE_RETRY(writev(fd, ioVec.get(), ioVec.size())));
+ return IO_FAILURE_RETRY(env, ssize_t, writev, javaFd, ioVec.get(), ioVec.size());
}
static JNINativeMethod gMethods[] = {
@@ -1486,6 +1532,7 @@
NATIVE_METHOD(Posix, lstat, "(Ljava/lang/String;)Llibcore/io/StructStat;"),
NATIVE_METHOD(Posix, mincore, "(JJ[B)V"),
NATIVE_METHOD(Posix, mkdir, "(Ljava/lang/String;I)V"),
+ NATIVE_METHOD(Posix, mkfifo, "(Ljava/lang/String;I)V"),
NATIVE_METHOD(Posix, mlock, "(JJ)V"),
NATIVE_METHOD(Posix, mmap, "(JJIILjava/io/FileDescriptor;J)J"),
NATIVE_METHOD(Posix, msync, "(JJI)V"),
diff --git a/luni/src/main/native/sub.mk b/luni/src/main/native/sub.mk
index e8b6e4a..6bec778 100644
--- a/luni/src/main/native/sub.mk
+++ b/luni/src/main/native/sub.mk
@@ -4,7 +4,7 @@
# or BUILD_*_LIBRARY.
LOCAL_SRC_FILES := \
- AsynchronousSocketCloseMonitor.cpp \
+ AsynchronousCloseMonitor.cpp \
ExecStrings.cpp \
IcuUtilities.cpp \
JniException.cpp \
diff --git a/luni/src/test/java/libcore/java/io/InterruptedStreamTest.java b/luni/src/test/java/libcore/java/io/InterruptedStreamTest.java
index e5fd39f..30ae7eb 100755
--- a/luni/src/test/java/libcore/java/io/InterruptedStreamTest.java
+++ b/luni/src/test/java/libcore/java/io/InterruptedStreamTest.java
@@ -207,7 +207,10 @@
private static void confirmInterrupted(Thread thread) throws InterruptedException {
// validate and clear interrupted bit before join
- assertTrue(Thread.interrupted());
- thread.join();
+ try {
+ assertTrue(Thread.interrupted());
+ } finally {
+ thread.join();
+ }
}
}
diff --git a/luni/src/test/java/libcore/java/nio/channels/FileIOInterruptTest.java b/luni/src/test/java/libcore/java/nio/channels/FileIOInterruptTest.java
new file mode 100644
index 0000000..2ac8827
--- /dev/null
+++ b/luni/src/test/java/libcore/java/nio/channels/FileIOInterruptTest.java
@@ -0,0 +1,628 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package libcore.java.nio.channels;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousCloseException;
+import java.nio.channels.ClosedByInterruptException;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.FileChannel;
+import libcore.io.ErrnoException;
+import libcore.io.Libcore;
+import libcore.io.OsConstants;
+
+import static libcore.io.IoUtils.closeQuietly;
+
+/**
+ * A test for file interrupt behavior. Because forcing a real file to block on read or write is
+ * difficult this test uses Unix FIFO / Named Pipes. FIFOs appear to Java as files but the test
+ * has more control over the available data. Reader will block until the other end writes, and
+ * writers can also be made to block.
+ *
+ * <p>Using FIFOs has a few drawbacks:
+ * <ol>
+ * <li>FIFOs are not supported from Java or the command-line on Android, so this test includes
+ * native code to create the FIFO.
+ * <li>FIFOs will not open() until there is both a reader and a writer of the FIFO; each test must
+ * always attach both ends or experience a blocked test.
+ * <li>FIFOs are not supported on some file systems. e.g. VFAT, so the test has to be particular
+ * about the temporary directory it uses to hold the FIFO.
+ * <li>Writes to FIFOs are buffered by the OS which makes blocking behavior more difficult to
+ * induce. See {@link ChannelWriter} and {@link StreamWriter}.
+ * </ol>
+ */
+public class FileIOInterruptTest extends TestCase {
+
+ private static File VOGAR_DEVICE_TEMP_DIR = new File("/data/data/file_io_interrupt_test");
+
+ private File fifoFile;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ // This test relies on a FIFO file. The file system must support FIFOs, so we check the path.
+ String tmpDirName = System.getProperty("java.io.tmpdir");
+ File tmpDir;
+ if (tmpDirName.startsWith("/sdcard")) {
+ // Vogar execution on device runs in /sdcard. Unfortunately the file system used does not
+ // support FIFOs so the test must use one that is more likely to work.
+ if (!VOGAR_DEVICE_TEMP_DIR.exists()) {
+ assertTrue(VOGAR_DEVICE_TEMP_DIR.mkdir());
+ }
+ VOGAR_DEVICE_TEMP_DIR.deleteOnExit();
+ tmpDir = VOGAR_DEVICE_TEMP_DIR;
+ } else {
+ tmpDir = new File(tmpDirName);
+ }
+ fifoFile = new File(tmpDir, "fifo_file.tmp");
+ if (fifoFile.exists()) {
+ fifoFile.delete();
+ }
+ fifoFile.deleteOnExit();
+
+ // Create the fifo. This will throw an exception if the file system does not support it.
+ Libcore.os.mkfifo(fifoFile.getAbsolutePath(), OsConstants.S_IRWXU);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ fifoFile.delete();
+ VOGAR_DEVICE_TEMP_DIR.delete();
+ }
+
+ public void testStreamRead_exceptionWhenAlreadyClosed() throws Exception {
+ FifoWriter fifoWriter = new FifoWriter(fifoFile);
+ fifoWriter.start();
+
+ FileInputStream fis = new FileInputStream(fifoFile);
+ fis.close();
+
+ byte[] buffer = new byte[10];
+ try {
+ fis.read(buffer);
+ fail();
+ } catch (IOException expected) {
+ assertSame(IOException.class, expected.getClass());
+ }
+
+ fifoWriter.tidyUp();
+ }
+
+ // This test fails on the RI: close() does not wake up a blocking FileInputStream.read() call.
+ public void testStreamRead_exceptionOnCloseWhenBlocked() throws Exception {
+ FifoWriter fifoWriter = new FifoWriter(fifoFile);
+ fifoWriter.start();
+
+ FileInputStream fis = new FileInputStream(fifoFile);
+ StreamReader streamReader = new StreamReader(fis);
+ Thread streamReaderThread = createAndStartThread("StreamReader", streamReader);
+
+ // Delay until we can be fairly sure the reader thread is blocking.
+ streamReader.waitForThreadToBlock();
+
+ // Now close the OutputStream to see what happens.
+ fis.close();
+
+ // Test for expected behavior in the reader thread.
+ waitToDie(streamReaderThread);
+ assertSame(InterruptedIOException.class, streamReader.ioe.getClass());
+ assertFalse(streamReader.wasInterrupted);
+
+ // Tidy up the writer thread.
+ fifoWriter.tidyUp();
+ }
+
+ public void testStreamWrite_exceptionWhenAlreadyClosed() throws Exception {
+ FifoReader fifoReader = new FifoReader(fifoFile);
+ fifoReader.start();
+
+ FileOutputStream fos = new FileOutputStream(fifoFile);
+ byte[] buffer = new byte[10];
+ fos.close();
+
+ try {
+ fos.write(buffer);
+ fail();
+ } catch (IOException expected) {
+ assertSame(IOException.class, expected.getClass());
+ }
+
+ fifoReader.tidyUp();
+ }
+
+ // This test fails on the RI: close() does not wake up a blocking FileInputStream.write() call.
+ public void testStreamWrite_exceptionOnCloseWhenBlocked() throws Exception {
+ FifoReader fifoReader = new FifoReader(fifoFile);
+ fifoReader.start();
+
+ FileOutputStream fos = new FileOutputStream(fifoFile);
+ StreamWriter streamWriter = new StreamWriter(fos);
+ Thread streamWriterThread = createAndStartThread("StreamWriter", streamWriter);
+
+ // Delay until we can be fairly sure the writer thread is blocking.
+ streamWriter.waitForThreadToBlock();
+
+ // Now close the OutputStream to see what happens.
+ fos.close();
+
+ // Test for expected behavior in the writer thread.
+ waitToDie(streamWriterThread);
+ assertSame(InterruptedIOException.class, streamWriter.ioe.getClass());
+ assertFalse(streamWriter.wasInterrupted);
+
+ // Tidy up the reader thread.
+ fifoReader.tidyUp();
+ }
+
+ public void testChannelRead_exceptionWhenAlreadyClosed() throws Exception {
+ testChannelRead_exceptionWhenAlreadyClosed(ChannelReader.Method.READ);
+ }
+
+ public void testChannelReadV_exceptionWhenAlreadyClosed() throws Exception {
+ testChannelRead_exceptionWhenAlreadyClosed(ChannelReader.Method.READV);
+ }
+
+ private void testChannelRead_exceptionWhenAlreadyClosed(ChannelReader.Method method)
+ throws Exception {
+ FifoWriter fifoWriter = new FifoWriter(fifoFile);
+ fifoWriter.start();
+ FileInputStream fis = new FileInputStream(fifoFile);
+ FileChannel fileInputChannel = fis.getChannel();
+ fileInputChannel.close();
+
+ ByteBuffer buffer = ByteBuffer.allocateDirect(10);
+ try {
+ if (method == ChannelReader.Method.READ) {
+ fileInputChannel.read(buffer);
+ } else {
+ ByteBuffer buffer2 = ByteBuffer.allocateDirect(10);
+ fileInputChannel.read(new ByteBuffer[] { buffer, buffer2});
+ }
+ fail();
+ } catch (IOException expected) {
+ assertSame(ClosedChannelException.class, expected.getClass());
+ }
+
+ fifoWriter.tidyUp();
+ }
+
+ public void testChannelRead_exceptionOnCloseWhenBlocked() throws Exception {
+ testChannelRead_exceptionOnCloseWhenBlocked(ChannelReader.Method.READ);
+ }
+
+ public void testChannelReadV_exceptionOnCloseWhenBlocked() throws Exception {
+ testChannelRead_exceptionOnCloseWhenBlocked(ChannelReader.Method.READV);
+ }
+
+ private void testChannelRead_exceptionOnCloseWhenBlocked(ChannelReader.Method method)
+ throws Exception {
+ FifoWriter fifoWriter = new FifoWriter(fifoFile);
+ fifoWriter.start();
+ FileInputStream fis = new FileInputStream(fifoFile);
+ FileChannel fileInputChannel = fis.getChannel();
+
+ ChannelReader channelReader = new ChannelReader(fileInputChannel, method);
+ Thread channelReaderThread = createAndStartThread("ChannelReader", channelReader);
+
+ // Delay until we can be fairly sure the reader thread is blocking.
+ channelReader.waitForThreadToBlock();
+
+ // Now close the FileChannel to see what happens.
+ fileInputChannel.close();
+
+ // Test for expected behavior in the reader thread.
+ waitToDie(channelReaderThread);
+ assertSame(AsynchronousCloseException.class, channelReader.ioe.getClass());
+ assertFalse(channelReader.wasInterrupted);
+
+ // Tidy up the writer thread.
+ fifoWriter.tidyUp();
+ }
+
+ public void testChannelRead_exceptionOnInterrupt() throws Exception {
+ testChannelRead_exceptionOnInterrupt(ChannelReader.Method.READ);
+ }
+
+ public void testChannelReadV_exceptionOnInterrupt() throws Exception {
+ testChannelRead_exceptionOnInterrupt(ChannelReader.Method.READV);
+ }
+
+ private void testChannelRead_exceptionOnInterrupt(ChannelReader.Method method) throws Exception {
+ FifoWriter fifoWriter = new FifoWriter(fifoFile);
+ fifoWriter.start();
+ FileChannel fileChannel = new FileInputStream(fifoFile).getChannel();
+
+ ChannelReader channelReader = new ChannelReader(fileChannel, method);
+ Thread channelReaderThread = createAndStartThread("ChannelReader", channelReader);
+
+ // Delay until we can be fairly sure the reader thread is blocking.
+ channelReader.waitForThreadToBlock();
+
+ // Now interrupt the reader thread to see what happens.
+ channelReaderThread.interrupt();
+
+ // Test for expected behavior in the reader thread.
+ waitToDie(channelReaderThread);
+ assertSame(ClosedByInterruptException.class, channelReader.ioe.getClass());
+ assertTrue(channelReader.wasInterrupted);
+
+ // Tidy up the writer thread.
+ fifoWriter.tidyUp();
+ }
+
+ public void testChannelWrite_exceptionWhenAlreadyClosed() throws Exception {
+ testChannelWrite_exceptionWhenAlreadyClosed(ChannelWriter.Method.WRITE);
+ }
+
+ public void testChannelWriteV_exceptionWhenAlreadyClosed() throws Exception {
+ testChannelWrite_exceptionWhenAlreadyClosed(ChannelWriter.Method.WRITEV);
+ }
+
+ private void testChannelWrite_exceptionWhenAlreadyClosed(ChannelWriter.Method method)
+ throws Exception {
+ FifoReader fifoReader = new FifoReader(fifoFile);
+ fifoReader.start();
+ FileChannel fileOutputChannel = new FileOutputStream(fifoFile).getChannel();
+ fileOutputChannel.close();
+
+ ByteBuffer buffer = ByteBuffer.allocateDirect(10);
+ try {
+ if (method == ChannelWriter.Method.WRITE) {
+ fileOutputChannel.write(buffer);
+ } else {
+ ByteBuffer buffer2 = ByteBuffer.allocateDirect(10);
+ fileOutputChannel.write(new ByteBuffer[] { buffer, buffer2 });
+ }
+ fail();
+ } catch (IOException expected) {
+ assertSame(ClosedChannelException.class, expected.getClass());
+ }
+
+ fifoReader.tidyUp();
+ }
+
+ public void testChannelWrite_exceptionOnCloseWhenBlocked() throws Exception {
+ testChannelWrite_exceptionOnCloseWhenBlocked(ChannelWriter.Method.WRITE);
+ }
+
+ public void testChannelWriteV_exceptionOnCloseWhenBlocked() throws Exception {
+ testChannelWrite_exceptionOnCloseWhenBlocked(ChannelWriter.Method.WRITEV);
+ }
+
+ private void testChannelWrite_exceptionOnCloseWhenBlocked(ChannelWriter.Method method)
+ throws Exception {
+ FifoReader fifoReader = new FifoReader(fifoFile);
+ fifoReader.start();
+ FileChannel fileOutputChannel = new FileOutputStream(fifoFile).getChannel();
+
+ ChannelWriter channelWriter = new ChannelWriter(fileOutputChannel, method);
+ Thread channelWriterThread = createAndStartThread("ChannelWriter", channelWriter);
+
+ // Delay until we can be fairly sure the writer thread is blocking.
+ channelWriter.waitForThreadToBlock();
+
+ // Now close the channel to see what happens.
+ fileOutputChannel.close();
+
+ // Test for expected behavior in the writer thread.
+ waitToDie(channelWriterThread);
+ // The RI throws ChannelClosedException. AsynchronousCloseException is more correct according to
+ // the docs.
+ assertSame(AsynchronousCloseException.class, channelWriter.ioe.getClass());
+ assertFalse(channelWriter.wasInterrupted);
+
+ // Tidy up the writer thread.
+ fifoReader.tidyUp();
+ }
+
+ public void testChannelWrite_exceptionOnInterrupt() throws Exception {
+ testChannelWrite_exceptionOnInterrupt(ChannelWriter.Method.WRITE);
+ }
+
+ public void testChannelWriteV_exceptionOnInterrupt() throws Exception {
+ testChannelWrite_exceptionOnInterrupt(ChannelWriter.Method.WRITEV);
+ }
+
+ private void testChannelWrite_exceptionOnInterrupt(ChannelWriter.Method method) throws Exception {
+ FifoReader fifoReader = new FifoReader(fifoFile);
+ fifoReader.start();
+
+ FileChannel fileChannel = new FileOutputStream(fifoFile).getChannel();
+ ChannelWriter channelWriter = new ChannelWriter(fileChannel, method);
+ Thread channelWriterThread = createAndStartThread("ChannelWriter", channelWriter);
+
+ // Delay until we can be fairly sure the writer thread is blocking.
+ channelWriter.waitForThreadToBlock();
+
+ // Now interrupt the writer thread to see what happens.
+ channelWriterThread.interrupt();
+
+ // Test for expected behavior in the writer thread.
+ waitToDie(channelWriterThread);
+ assertSame(ClosedByInterruptException.class, channelWriter.ioe.getClass());
+ assertTrue(channelWriter.wasInterrupted);
+
+ // Tidy up the reader thread.
+ fifoReader.tidyUp();
+ }
+
+ private static class StreamReader implements Runnable {
+
+ private final FileInputStream inputStream;
+ volatile boolean started;
+ volatile IOException ioe;
+ volatile boolean wasInterrupted;
+
+ StreamReader(FileInputStream inputStream) {
+ this.inputStream = inputStream;
+ }
+
+ @Override
+ public void run() {
+ byte[] buffer = new byte[10];
+ try {
+ started = true;
+ int bytesRead = inputStream.read(buffer);
+ fail("This isn't supposed to happen: read() returned: " + bytesRead);
+ } catch (IOException e) {
+ this.ioe = e;
+ }
+ wasInterrupted = Thread.interrupted();
+ }
+
+ public void waitForThreadToBlock() {
+ for (int i = 0; i < 10 && !started; i++) {
+ delay(100);
+ }
+ assertTrue(started);
+ // Just give it some more time to start blocking.
+ delay(100);
+ }
+ }
+
+ private static class StreamWriter implements Runnable {
+
+ private final FileOutputStream outputStream;
+ volatile int bytesWritten;
+ volatile IOException ioe;
+ volatile boolean wasInterrupted;
+
+ StreamWriter(FileOutputStream outputStream) {
+ this.outputStream = outputStream;
+ }
+
+ @Override
+ public void run() {
+ // Writes to FIFOs are buffered. We try to fill the buffer and induce blocking (the
+ // buffer is typically 64k).
+ byte[] buffer = new byte[10000];
+ while (true) {
+ try {
+ outputStream.write(buffer);
+ bytesWritten += buffer.length;
+ } catch (IOException e) {
+ this.ioe = e;
+ break;
+ }
+ wasInterrupted = Thread.interrupted();
+ }
+ }
+
+ public void waitForThreadToBlock() {
+ int lastCount = bytesWritten;
+ for (int i = 0; i < 10; i++) {
+ delay(500);
+ int newBytesWritten = bytesWritten;
+ if (newBytesWritten > 0 && lastCount == newBytesWritten) {
+ // The thread is probably blocking.
+ return;
+ }
+ lastCount = bytesWritten;
+ }
+ fail("Writer never started blocking. Bytes written: " + bytesWritten);
+ }
+ }
+
+ private static class ChannelReader implements Runnable {
+ enum Method {
+ READ,
+ READV,
+ }
+
+ private final FileChannel channel;
+ private final Method method;
+ volatile boolean started;
+ volatile IOException ioe;
+ volatile boolean wasInterrupted;
+
+ ChannelReader(FileChannel channel, Method method) {
+ this.channel = channel;
+ this.method = method;
+ }
+
+ @Override
+ public void run() {
+ ByteBuffer buffer = ByteBuffer.allocateDirect(10);
+ try {
+ started = true;
+ if (method == Method.READ) {
+ channel.read(buffer);
+ } else {
+ ByteBuffer buffer2 = ByteBuffer.allocateDirect(10);
+ channel.read(new ByteBuffer[] { buffer, buffer2 });
+ }
+ fail("All tests should block until an exception");
+ } catch (IOException e) {
+ this.ioe = e;
+ }
+ wasInterrupted = Thread.interrupted();
+ }
+
+ public void waitForThreadToBlock() {
+ for (int i = 0; i < 10 && !started; i++) {
+ delay(100);
+ }
+ assertTrue(started);
+ // Just give it some more time to start blocking.
+ delay(100);
+ }
+ }
+
+ private static class ChannelWriter implements Runnable {
+ enum Method {
+ WRITE,
+ WRITEV,
+ }
+
+ private final FileChannel channel;
+ private final Method method;
+ volatile int bytesWritten;
+ volatile IOException ioe;
+ volatile boolean wasInterrupted;
+
+ ChannelWriter(FileChannel channel, Method method) {
+ this.channel = channel;
+ this.method = method;
+ }
+
+ @Override
+ public void run() {
+ ByteBuffer buffer1 = ByteBuffer.allocateDirect(10000);
+ ByteBuffer buffer2 = ByteBuffer.allocateDirect(10000);
+ // Writes to FIFOs are buffered. We try to fill the buffer and induce blocking (the
+ // buffer is typically 64k).
+ while (true) {
+ // Make the buffers look non-empty.
+ buffer1.position(0).limit(buffer1.capacity());
+ buffer2.position(0).limit(buffer2.capacity());
+ try {
+ if (method == Method.WRITE) {
+ bytesWritten += channel.write(buffer1);
+ } else {
+ bytesWritten += channel.write(new ByteBuffer[]{ buffer1, buffer2 });
+ }
+ } catch (IOException e) {
+ this.ioe = e;
+ break;
+ }
+ }
+ wasInterrupted = Thread.interrupted();
+ }
+
+ public void waitForThreadToBlock() {
+ int lastCount = bytesWritten;
+ for (int i = 0; i < 10; i++) {
+ delay(500);
+ int newBytesWritten = bytesWritten;
+ if (newBytesWritten > 0 && lastCount == newBytesWritten) {
+ // The thread is probably blocking.
+ return;
+ }
+ lastCount = bytesWritten;
+ }
+ fail("Writer never started blocking. Bytes written: " + bytesWritten);
+ }
+ }
+
+ /**
+ * Opens a FIFO for writing. Exists to unblock the other end of the FIFO.
+ */
+ private static class FifoWriter extends Thread {
+
+ private final File file;
+ private FileOutputStream fos;
+
+ public FifoWriter(File file) {
+ super("FifoWriter");
+ this.file = file;
+ }
+
+ @Override
+ public void run() {
+ try {
+ fos = new FileOutputStream(file);
+ } catch (IOException ignored) {
+ }
+ }
+
+ public void tidyUp() {
+ FileIOInterruptTest.waitToDie(this);
+ closeQuietly(fos);
+ }
+ }
+
+ /**
+ * Opens a FIFO for reading. Exists to unblock the other end of the FIFO.
+ */
+ private static class FifoReader extends Thread {
+
+ private final File file;
+ private FileInputStream fis;
+
+ public FifoReader(File file) {
+ super("FifoReader");
+ this.file = file;
+ }
+
+ @Override
+ public void run() {
+ try {
+ fis = new FileInputStream(file);
+ } catch (IOException ignored) {
+ }
+ }
+
+ public void tidyUp() {
+ FileIOInterruptTest.waitToDie(this);
+ closeQuietly(fis);
+ }
+ }
+
+ private static Thread createAndStartThread(String name, Runnable runnable) {
+ Thread t = new Thread(runnable, name);
+ t.setDaemon(true);
+ t.start();
+ return t;
+ }
+
+ private static void waitToDie(Thread thread) {
+ try {
+ thread.join(5000);
+ } catch (InterruptedException ignored) {
+ }
+
+ if (thread.isAlive()) {
+ fail("Thread \"" + thread.getName() + "\" did not exit.");
+ }
+ }
+
+ private static void delay(int millis) {
+ try {
+ Thread.sleep(millis);
+ } catch (InterruptedException ignored) {
+ }
+ }
+
+}