Skip to content
This repository was archived by the owner on Jul 19, 2024. It is now read-only.

Commit 15b176d

Browse files
SAXParser concurrency bug fix.
DETAILS: There is a concurrency bug in the XML parsing logic which can cause the results of a List Blobs operation to appear empty. Please refer to Utility.java#L131. Utility.saxParserThreadLocal is a ThreadLocal<SAXParser> with a SAXParserFactory member variable. The factory is initialized in ThreadLocal.initialValue which then continues to use the member variable reference. Because initialValue is called once for each thread, it is possible for Thread A to be after the call to "factory.setNamespaceAware(true)" but before the call to "return factory.newSAXParser()" when it yields to Thread B which calls "factory = SAXParserFactory.newInstance()" and then yields to Thread A which then calls "return factory.newSAXParser()". Since this is a reference to the factory member variable, this instance would therefore not be namespace aware since setNamespaceAware(true) has not been called. It therefore returns a SAXParser that is not namespace aware. A SAXParser that is not namespace aware will always return a list count of 0. Please refer to BlobListHandler.java#L84. BlobListHandler.startElement takes the localName parameter and compares it with "Blob" on line 84, to find each blob item in the list. When a SAXParser is not namespace aware, the localName parameter is always empty. Please refer to the documentation for DefaultHandler.startElement, which describes the localName parameter as follows, "The local name (without prefix), or the empty string if Namespace processing is not being performed." This was also confirmed by testing, so the documentation is correct. Thus when the SAXParser is not initialized correctly (is not namespace aware) the results of List Blobs operation will always appear to be empty. TESTS: The test testSAXParserConcurrency was added to validate that the SAXParser returned by Utility.getSAXParser is correctly configured, even when called under a highly concurrent load. This test will occassionaly fail without the fix, but the probability of faiulre is very low. To increease the likelihood of failure you can add calls to sleep before and after the call to "factory.setNamespaceAware(true)" in Utitlity.java as shown below: private static void sleep(long l) { try { Thread.sleep(l); } catch (InterruptedException e) { } } /** * Thread local for SAXParser. */ private static final ThreadLocal<SAXParser> saxParserThreadLocal = new ThreadLocal<SAXParser>() { SAXParserFactory factory; @OverRide public SAXParser initialValue() { factory = SAXParserFactory.newInstance(); sleep(100); factory.setNamespaceAware(true); sleep(10); try { return factory.newSAXParser(); } catch (SAXException e) { throw new RuntimeException("Unable to create SAXParser", e); } catch (ParserConfigurationException e) { throw new RuntimeException("Check parser configuration", e); } } };
1 parent 82e86f4 commit 15b176d

File tree

2 files changed

+50
-2
lines changed

2 files changed

+50
-2
lines changed

microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobContainerTests.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,14 @@
2222
import java.net.URISyntaxException;
2323
import java.security.InvalidKeyException;
2424
import java.util.*;
25+
import java.util.concurrent.Executors;
26+
import java.util.concurrent.ThreadPoolExecutor;
27+
import java.util.concurrent.TimeUnit;
28+
import java.util.concurrent.atomic.AtomicInteger;
29+
import javax.xml.parsers.SAXParser;
2530

2631
import com.microsoft.azure.storage.*;
32+
import com.microsoft.azure.storage.core.Utility;
2733
import org.junit.After;
2834
import org.junit.Before;
2935
import org.junit.Test;
@@ -541,6 +547,49 @@ public void testCloudBlobContainerListBlobs() throws StorageException, IOExcepti
541547
assertTrue(blobNames.size() == 0);
542548
}
543549

550+
@Test
551+
@Category({DevFabricTests.class, DevStoreTests.class})
552+
public void testSAXParserConcurrency() throws Exception {
553+
final int totalCount = 200000;
554+
final int numThreads = 200;
555+
final AtomicInteger currentCount = new AtomicInteger(0);
556+
final AtomicInteger pending = new AtomicInteger(0);
557+
final AtomicInteger failureCount = new AtomicInteger(0);
558+
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(numThreads);
559+
560+
do {
561+
final int count = currentCount.incrementAndGet();
562+
pending.incrementAndGet();
563+
executor.execute(new Runnable() {
564+
@Override
565+
public void run() {
566+
pending.decrementAndGet();
567+
if (count > totalCount) {
568+
return;
569+
}
570+
try {
571+
SAXParser parser = Utility.getSAXParser();
572+
if (!parser.isNamespaceAware()) {
573+
failureCount.incrementAndGet();
574+
}
575+
assertEquals(true, parser.isNamespaceAware());
576+
} catch (Exception e) {
577+
fail(e.toString());
578+
}
579+
}
580+
});
581+
582+
assertEquals(0, failureCount.get());
583+
584+
while (pending.get() > numThreads * 2) {
585+
Thread.sleep(10);
586+
}
587+
} while (currentCount.get() < totalCount);
588+
executor.shutdown();
589+
executor.awaitTermination(1, TimeUnit.MINUTES);
590+
executor.shutdownNow();
591+
}
592+
544593
/**
545594
* List the blobs in a container with a prefix
546595
*

microsoft-azure-storage/src/com/microsoft/azure/storage/core/Utility.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,8 @@ protected DateFormat initialValue() {
129129
* Thread local for SAXParser.
130130
*/
131131
private static final ThreadLocal<SAXParser> saxParserThreadLocal = new ThreadLocal<SAXParser>() {
132-
SAXParserFactory factory;
133132
@Override public SAXParser initialValue() {
134-
factory = SAXParserFactory.newInstance();
133+
SAXParserFactory factory = SAXParserFactory.newInstance();
135134
factory.setNamespaceAware(true);
136135
try {
137136
return factory.newSAXParser();

0 commit comments

Comments
 (0)