commit bde697f44702c3f93cfff7953ff8f5962582e2e1 Author: Karsten Loesing karsten.loesing@gmx.net Date: Thu Oct 24 21:29:07 2019 +0200
Make NetworkStatusEntryImpl#parseSLine thread-safe.
The bug was that we accessed static class members, namely the two maps NetworkStatusEntryImpl#flagIndexes and #flagStrings, during instance creation without synchronization. This worked just fine with a single thread creating instances, but it breaks with multiple threads doing that at the same time.
The fix is to keep a separate map per NetworkStatusImpl instance and share that between all its NetworkStatusEntryImpl instances. This doesn't save as much memory as sharing maps between all NetworksStatusEntryImpl instances ever created, but it's a reasonable compromise between memory and runtime efficiency. In contrast to that, synchronizing map access would have put a major runtime performance penalty on parsing.
Fixes #32194. --- CHANGELOG.md | 3 +++ .../descriptor/impl/NetworkStatusEntryImpl.java | 27 +++++++++++----------- .../descriptor/impl/NetworkStatusImpl.java | 8 ++++++- .../impl/RelayNetworkStatusConsensusImpl.java | 3 ++- 4 files changed, 26 insertions(+), 15 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c7610a..73022cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changes in version 2.?.? - 2019-??-??
+ * Medium changes + - Make NetworkStatusEntryImpl#parseSLine thread-safe. +
# Changes in version 2.8.0 - 2019-10-18
diff --git a/src/main/java/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java b/src/main/java/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java index 2ddaaa0..e6a78f6 100644 --- a/src/main/java/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java +++ b/src/main/java/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java @@ -12,7 +12,6 @@ import org.torproject.descriptor.NetworkStatusEntry; import java.util.ArrayList; import java.util.BitSet; import java.util.EnumSet; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -48,13 +47,19 @@ public class NetworkStatusEntryImpl implements NetworkStatusEntry { return lines; }
+ private Map<String, Integer> flagIndexes; + + private Map<Integer, String> flagStrings; + protected NetworkStatusEntryImpl(DescriptorImpl parent, int offset, - int length, boolean microdescConsensus) - throws DescriptorParseException { + int length, boolean microdescConsensus, Map<String, Integer> flagIndexes, + Map<Integer, String> flagStrings) throws DescriptorParseException { this.parent = parent; this.offset = offset; this.length = length; this.microdescConsensus = microdescConsensus; + this.flagIndexes = flagIndexes; + this.flagStrings = flagStrings; this.parseStatusEntryBytes(); this.clearAtMostOnceKeys(); } @@ -160,21 +165,17 @@ public class NetworkStatusEntryImpl implements NetworkStatusEntry { this.orAddresses.add(parts[1]); }
- private static Map<String, Integer> flagIndexes = new HashMap<>(); - - private static Map<Integer, String> flagStrings = new HashMap<>(); - private void parseSLine(String[] parts) throws DescriptorParseException { this.parsedAtMostOnceKey(Key.S); - BitSet flags = new BitSet(flagIndexes.size()); + BitSet flags = new BitSet(this.flagIndexes.size()); for (int i = 1; i < parts.length; i++) { String flag = parts[i]; - if (!flagIndexes.containsKey(flag)) { - flagStrings.put(flagIndexes.size(), flag); - flagIndexes.put(flag, flagIndexes.size()); + if (!this.flagIndexes.containsKey(flag)) { + this.flagStrings.put(this.flagIndexes.size(), flag); + this.flagIndexes.put(flag, this.flagIndexes.size()); } - flags.set(flagIndexes.get(flag)); + flags.set(this.flagIndexes.get(flag)); } this.flags = flags; } @@ -353,7 +354,7 @@ public class NetworkStatusEntryImpl implements NetworkStatusEntry { if (this.flags != null) { for (int i = this.flags.nextSetBit(0); i >= 0; i = this.flags.nextSetBit(i + 1)) { - result.add(flagStrings.get(i)); + result.add(this.flagStrings.get(i)); } } return result; diff --git a/src/main/java/org/torproject/descriptor/impl/NetworkStatusImpl.java b/src/main/java/org/torproject/descriptor/impl/NetworkStatusImpl.java index 0694aff..b763f30 100644 --- a/src/main/java/org/torproject/descriptor/impl/NetworkStatusImpl.java +++ b/src/main/java/org/torproject/descriptor/impl/NetworkStatusImpl.java @@ -10,7 +10,9 @@ import org.torproject.descriptor.NetworkStatusEntry;
import java.io.File; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.SortedMap; import java.util.TreeMap;
@@ -19,6 +21,10 @@ import java.util.TreeMap; * delegate the specific parts to the subclasses. */ public abstract class NetworkStatusImpl extends DescriptorImpl {
+ protected Map<String, Integer> flagIndexes = new HashMap<>(); + + protected Map<Integer, String> flagStrings = new HashMap<>(); + protected NetworkStatusImpl(byte[] rawDescriptorBytes, int[] offsetAndLength, File descriptorFile, boolean containsDirSourceEntries, boolean blankLinesAllowed) throws DescriptorParseException { @@ -140,7 +146,7 @@ public abstract class NetworkStatusImpl extends DescriptorImpl { protected void parseStatusEntry(int offset, int length) throws DescriptorParseException { NetworkStatusEntryImpl statusEntry = new NetworkStatusEntryImpl( - this, offset, length, false); + this, offset, length, false, this.flagIndexes, this.flagStrings); this.statusEntries.put(statusEntry.getFingerprint(), statusEntry); List<String> unrecognizedStatusEntryLines = statusEntry .getAndClearUnrecognizedLines(); diff --git a/src/main/java/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java b/src/main/java/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java index 904dc35..66b8eaa 100644 --- a/src/main/java/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java +++ b/src/main/java/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java @@ -119,7 +119,8 @@ public class RelayNetworkStatusConsensusImpl extends NetworkStatusImpl protected void parseStatusEntry(int offset, int length) throws DescriptorParseException { NetworkStatusEntryImpl statusEntry = new NetworkStatusEntryImpl(this, - offset, length, this.microdescConsensus); + offset, length, this.microdescConsensus, this.flagIndexes, + this.flagStrings); this.statusEntries.put(statusEntry.getFingerprint(), statusEntry); List<String> unrecognizedStatusEntryLines = statusEntry .getAndClearUnrecognizedLines();
tor-commits@lists.torproject.org