Ticket #12272: patch-fix-keys-synchronisation.patch

File patch-fix-keys-synchronisation.patch, 14.2 KB (added by michael2402, 10 years ago)
  • src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java

    From 3d6ca162672f576b60d079c2b0f6dbf545d0c756 Mon Sep 17 00:00:00 2001
    From: Michael Zangl <michael.zangl@student.kit.edu>
    Date: Sat, 2 Jan 2016 14:14:56 +0100
    Subject: [PATCH 1/2] Fixed keys array access on some places
    
    ---
     .../josm/data/osm/AbstractPrimitive.java           | 69 +++++++++++++++-------
     .../openstreetmap/josm/data/osm/OsmPrimitive.java  | 36 +++++------
     2 files changed, 65 insertions(+), 40 deletions(-)
    
    diff --git a/src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java b/src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java
    index bb7127c..c716f23 100644
    a b public abstract class AbstractPrimitive implements IPrimitive {  
    481481     * Keys handling
    482482     ------------*/
    483483
    484     // Note that all methods that read keys first make local copy of keys array reference. This is to ensure thread safety - reading
    485     // doesn't have to be locked so it's possible that keys array will be modified. But all write methods make copy of keys array so
    486     // the array itself will be never modified - only reference will be changed
    487 
    488484    /**
    489485     * The key/value list for this primitive.
     486     * <p>
     487     * Note that the keys field is synchronized using RCU.
     488     * Writes to it are not synchronized by this object, the writers have to synchronize writes themselves.
     489     * <p>
     490     * In short this means that you should not rely on this variable being the same value when read again and your should always copy it on writes.
    490491     */
    491     protected String[] keys;
     492    protected volatile String[] keys;
    492493
    493494    /**
    494495     * Replies the map of key/value pairs. Never replies null. The map can be empty, though.
    public abstract class AbstractPrimitive implements IPrimitive {  
    530531     * Sets the keys of this primitives to the key/value pairs in <code>keys</code>.
    531532     * Old key/value pairs are removed.
    532533     * If <code>keys</code> is null, clears existing key/value pairs.
     534     * <p>
     535     * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used from multiple threads.
    533536     *
    534537     * @param keys the key/value pairs to set. If null, removes all existing key/value pairs.
    535538     */
    public abstract class AbstractPrimitive implements IPrimitive {  
    554557    /**
    555558     * Set the given value to the given key. If key is null, does nothing. If value is null,
    556559     * removes the key and behaves like {@link #remove(String)}.
     560     * <p>
     561     * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used from multiple threads.
    557562     *
    558563     * @param key  The key, for which the value is to be set. Can be null or empty, does nothing in this case.
    559564     * @param value The value for the key. If null, removes the respective key/value pair.
    public abstract class AbstractPrimitive implements IPrimitive {  
    571576            keys = new String[] {key, value};
    572577            keysChangedImpl(originalKeys);
    573578        } else {
    574             for (int i = 0; i < keys.length; i += 2) {
    575                 if (keys[i].equals(key)) {
    576                     // This modifies the keys array but it doesn't make it invalidate for any time so its ok (see note no top)
    577                     keys[i+1] = value;
    578                     keysChangedImpl(originalKeys);
    579                     return;
    580                 }
     579            int keyIndex = indexOfKey(keys, key);
     580            int tagArrayLength = keys.length;
     581            if (keyIndex < 0) {
     582                keyIndex = tagArrayLength;
     583                tagArrayLength += 2;
    581584            }
    582             String[] newKeys = Arrays.copyOf(keys, keys.length + 2);
    583             newKeys[keys.length] = key;
    584             newKeys[keys.length + 1] = value;
     585
     586            // Do not try to optimize this array creation if the key already exists.
     587            // We would need to convert the keys array to be an AtomicReferenceArray
     588            // Or we would at least need a volatile write after the array was modified to
     589            // ensure that changes are visible by other threads.
     590            String[] newKeys = Arrays.copyOf(keys, tagArrayLength);
     591            newKeys[keyIndex] = key;
     592            newKeys[keyIndex + 1] = value;
    585593            keys = newKeys;
    586594            keysChangedImpl(originalKeys);
    587595        }
    588596    }
    589597
    590598    /**
     599     * Scans a key/value array for a given key.
     600     * @param keys The key array. It is not modified. It may be null to indicate an emtpy array.
     601     * @param key The key to search for.
     602     * @return The position of that key in the keys array - which is always a multiple of 2 - or -1 if it was not found.
     603     */
     604    private static int indexOfKey(String[] keys, String key) {
     605        if (keys == null) {
     606            return -1;
     607        }
     608        for (int i = 0; i < keys.length; i += 2) {
     609            if (keys[i].equals(key)) {
     610                return i;
     611            }
     612        }
     613        return -1;
     614    }
     615
     616    /**
    591617     * Remove the given key from the list
     618     * <p>
     619     * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used from multiple threads.
    592620     *
    593621     * @param key  the key to be removed. Ignored, if key is null.
    594622     */
    public abstract class AbstractPrimitive implements IPrimitive {  
    617645
    618646    /**
    619647     * Removes all keys from this primitive.
     648     * <p>
     649     * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used from multiple threads.
    620650     */
    621651    @Override
    622652    public void removeAll() {
    public abstract class AbstractPrimitive implements IPrimitive {  
    680710    }
    681711
    682712    public final int getNumKeys() {
     713        String[] keys = this.keys;
    683714        return keys == null ? 0 : keys.length / 2;
    684715    }
    685716
    public abstract class AbstractPrimitive implements IPrimitive {  
    718749     * @return true, if his primitive has a tag with key <code>key</code>
    719750     */
    720751    public boolean hasKey(String key) {
    721         String[] keys = this.keys;
    722         if (key == null) return false;
    723         if (keys == null) return false;
    724         for (int i = 0; i < keys.length; i += 2) {
    725             if (keys[i].equals(key)) return true;
    726         }
    727         return false;
     752        return key != null && indexOfKey(keys, key) >= 0;
    728753    }
    729754
    730755    /**
  • src/org/openstreetmap/josm/data/osm/OsmPrimitive.java

    diff --git a/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java b/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java
    index 9d5cb5d..b90e896 100644
    a b public abstract class OsmPrimitive extends AbstractPrimitive implements Comparab  
    775775
    776776    /**
    777777     * Returns {@link #getKeys()} for which {@code key} does not fulfill {@link #isUninterestingKey}.
    778      * @return list of interesting tags
     778     * @return A map of interesting tags
    779779     */
    780780    public Map<String, String> getInterestingTags() {
    781781        Map<String, String> result = new HashMap<>();
    public abstract class OsmPrimitive extends AbstractPrimitive implements Comparab  
    832832    }
    833833
    834834    private void updateTagged() {
    835         if (keys != null) {
    836             for (String key: keySet()) {
    837                 // 'area' is not really uninteresting (putting it in that list may have unpredictable side effects)
    838                 // but it's clearly not enough to consider an object as tagged (see #9261)
    839                 if (!isUninterestingKey(key) && !"area".equals(key)) {
    840                     updateFlagsNoLock(FLAG_TAGGED, true);
    841                     return;
    842                 }
     835        for (String key: keySet()) {
     836            // 'area' is not really uninteresting (putting it in that list may have unpredictable side effects)
     837            // but it's clearly not enough to consider an object as tagged (see #9261)
     838            if (!isUninterestingKey(key) && !"area".equals(key)) {
     839                updateFlagsNoLock(FLAG_TAGGED, true);
     840                return;
    843841            }
    844842        }
    845843        updateFlagsNoLock(FLAG_TAGGED, false);
    846844    }
    847845
    848846    private void updateAnnotated() {
    849         if (keys != null) {
    850             for (String key: keySet()) {
    851                 if (getWorkInProgressKeys().contains(key)) {
    852                     updateFlagsNoLock(FLAG_ANNOTATED, true);
    853                     return;
    854                 }
     847        for (String key: keySet()) {
     848            if (getWorkInProgressKeys().contains(key)) {
     849                updateFlagsNoLock(FLAG_ANNOTATED, true);
     850                return;
    855851            }
    856852        }
    857853        updateFlagsNoLock(FLAG_ANNOTATED, false);
    public abstract class OsmPrimitive extends AbstractPrimitive implements Comparab  
    11901186        // We cannot directly use Arrays.equals(keys, other.keys) as keys is not ordered by key
    11911187        // but we can at least check if both arrays are null or of the same size before creating
    11921188        // and comparing the key maps (costly operation, see #7159)
    1193         return (keys == null && other.keys == null)
    1194                 || (keys != null && other.keys != null && keys.length == other.keys.length
    1195                         && (keys.length == 0 || getInterestingTags().equals(other.getInterestingTags())));
     1189        // Note: This implementation is just broken. myKeys.length == 0 should not happen and
     1190        // myKeys.length == otherKeys.length is not required for the same interesting tags.
     1191        String[] myKeys = keys;
     1192        String[] otherKeys = other.keys;
     1193        return (myKeys == null && otherKeys == null)
     1194                || (myKeys != null && otherKeys != null && myKeys.length == otherKeys.length
     1195                        && (myKeys.length == 0 || getInterestingTags().equals(other.getInterestingTags())));
    11961196    }
    11971197
    11981198    /**
  • src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java

    -- 
    1.9.1
    
    
    From 23bb9c0e031d5407de2562f56eb7b339a4363c11 Mon Sep 17 00:00:00 2001
    From: Michael Zangl <michael.zangl@student.kit.edu>
    Date: Sat, 2 Jan 2016 17:31:27 +0100
    Subject: [PATCH 2/2] Fixed checkstyle. added links for RCU
    
    ---
     .../josm/data/osm/AbstractPrimitive.java           | 24 +++++++++++++++++-----
     1 file changed, 19 insertions(+), 5 deletions(-)
    
    diff --git a/src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java b/src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java
    index c716f23..90ba106 100644
    a b import java.util.Map;  
    1414import java.util.Map.Entry;
    1515import java.util.Objects;
    1616import java.util.Set;
     17import java.util.concurrent.CopyOnWriteArrayList;
    1718import java.util.concurrent.atomic.AtomicLong;
    1819
    1920import org.openstreetmap.josm.tools.LanguageInfo;
    public abstract class AbstractPrimitive implements IPrimitive {  
    487488     * Note that the keys field is synchronized using RCU.
    488489     * Writes to it are not synchronized by this object, the writers have to synchronize writes themselves.
    489490     * <p>
    490      * In short this means that you should not rely on this variable being the same value when read again and your should always copy it on writes.
     491     * In short this means that you should not rely on this variable being the same value when read again and your should always
     492     * copy it on writes.
     493     * <p>
     494     * Further reading:
     495     * <ul>
     496     * <li> The {@link CopyOnWriteArrayList} class
     497     * <li> http://stackoverflow.com/questions/2950871/how-can-copyonwritearraylist-be-thread-safe
     498     * <li> https://en.wikipedia.org/wiki/Read-copy-update
     499     * (mind that we have a Garbage collector, rcu_assign_pointer and rcu_dereference are ensured by the volatile keyword)
     500     * </ul>
    491501     */
    492502    protected volatile String[] keys;
    493503
    public abstract class AbstractPrimitive implements IPrimitive {  
    532542     * Old key/value pairs are removed.
    533543     * If <code>keys</code> is null, clears existing key/value pairs.
    534544     * <p>
    535      * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used from multiple threads.
     545     * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used
     546     * from multiple threads.
    536547     *
    537548     * @param keys the key/value pairs to set. If null, removes all existing key/value pairs.
    538549     */
    public abstract class AbstractPrimitive implements IPrimitive {  
    558569     * Set the given value to the given key. If key is null, does nothing. If value is null,
    559570     * removes the key and behaves like {@link #remove(String)}.
    560571     * <p>
    561      * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used from multiple threads.
     572     * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used
     573     * from multiple threads.
    562574     *
    563575     * @param key  The key, for which the value is to be set. Can be null or empty, does nothing in this case.
    564576     * @param value The value for the key. If null, removes the respective key/value pair.
    public abstract class AbstractPrimitive implements IPrimitive {  
    616628    /**
    617629     * Remove the given key from the list
    618630     * <p>
    619      * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used from multiple threads.
     631     * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used
     632     * from multiple threads.
    620633     *
    621634     * @param key  the key to be removed. Ignored, if key is null.
    622635     */
    public abstract class AbstractPrimitive implements IPrimitive {  
    646659    /**
    647660     * Removes all keys from this primitive.
    648661     * <p>
    649      * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used from multiple threads.
     662     * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used
     663     * from multiple threads.
    650664     */
    651665    @Override
    652666    public void removeAll() {