Skip to content

Commit 7fa8bbc

Browse files
Jörg Kubitzjukzi
authored andcommitted
Bucket: fix concurrent Modification during save() - fixes #1721
Take a snapshot of the property map before processing the properties #1721
1 parent d3dd4bf commit 7fa8bbc

File tree

1 file changed

+19
-18
lines changed
  • resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore

1 file changed

+19
-18
lines changed

resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/Bucket.java

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import java.util.Iterator;
3131
import java.util.Map;
3232
import java.util.WeakHashMap;
33+
import java.util.concurrent.ConcurrentHashMap;
34+
import java.util.concurrent.ConcurrentMap;
3335
import org.eclipse.core.internal.resources.ResourceException;
3436
import org.eclipse.core.internal.resources.ResourceStatus;
3537
import org.eclipse.core.internal.utils.Messages;
@@ -169,28 +171,28 @@ public void beforeSaving(Bucket bucket) throws CoreException {
169171
* where the key is the path of the object we are storing history for, and
170172
* the value is the history entry data (UUID,timestamp) pairs.
171173
*/
172-
private final Map<String, Object> entries;
173-
private SoftReference<Map<Object, Map<String, Object>>> entriesCache;
174+
private final ConcurrentMap<String, Object> entries;
175+
private volatile SoftReference<Map<String, Map<String, Object>>> entriesCache;
174176

175177
/**
176178
* The file system location of this bucket index file.
177179
*/
178-
private File location;
180+
private volatile File location;
179181
/**
180182
* Whether the in-memory bucket is dirty and needs saving
181183
*/
182-
private boolean needSaving = false;
184+
private volatile boolean needSaving = false;
183185
/**
184186
* The project name for the bucket currently loaded. <code>null</code> if this is the root bucket.
185187
*/
186-
protected String projectName;
188+
protected volatile String projectName;
187189

188190
public Bucket() {
189191
this(false);
190192
}
191193

192194
public Bucket(boolean cacheEntries) {
193-
this.entries = new HashMap<>();
195+
this.entries = new ConcurrentHashMap<>();
194196
if (cacheEntries) {
195197
entriesCache = new SoftReference<>(null);
196198
}
@@ -332,7 +334,7 @@ public void load(String newProjectName, File baseLocation, boolean force) throws
332334
loadedEntries = loadEntries(this.location);
333335
} else {
334336
if (isCachingEnabled()) {
335-
Map<Object, Map<String, Object>> cache = entriesCache.get();
337+
Map<String, Map<String, Object>> cache = entriesCache.get();
336338
if (cache != null) {
337339
loadedEntries = cache.get(createBucketKey());
338340
}
@@ -355,7 +357,7 @@ boolean isCachingEnabled() {
355357
return entriesCache != null;
356358
}
357359

358-
private Object createBucketKey() {
360+
private String createBucketKey() {
359361
return this.location == null ? null : this.location.getAbsolutePath();
360362
}
361363

@@ -396,25 +398,24 @@ private String readEntryKey(DataInputStream source) throws IOException {
396398
* Saves this bucket's contents back to its location.
397399
*/
398400
public void save() throws CoreException {
401+
// We do need to make a copy of this.entries for caching, because that instance is reused
402+
// And we need atomic copy to prevent concurrent modification during save();
403+
Map<String, Object> entriesSnapshot = Map.copyOf(this.entries);
399404
if (isCachingEnabled()) {
400-
Object key = createBucketKey();
405+
String key = createBucketKey();
401406
if (key != null) {
402-
// we do need to make a copy from this.entries because that instance is reused
403-
@SuppressWarnings("unchecked")
404-
java.util.Map.Entry<String, Object>[] a = new java.util.Map.Entry[0];
405-
java.util.Map<String, Object> denseCopy = java.util.Map.ofEntries(this.entries.entrySet().toArray(a));
406-
Map<Object, Map<String, Object>> cache = entriesCache.get();
407+
Map<String, Map<String, Object>> cache = entriesCache.get();
407408
if (cache == null) {
408409
cache = new WeakHashMap<>();
409410
entriesCache = new SoftReference<>(cache);
410411
}
411-
cache.put(key, denseCopy); // remember the entries in cache
412+
cache.put(key, entriesSnapshot); // remember the entries in cache
412413
}
413414
}
414415
if (!needSaving)
415416
return;
416417
try {
417-
if (entries.isEmpty()) {
418+
if (entriesSnapshot.isEmpty()) {
418419
needSaving = false;
419420
cleanUp(location);
420421
return;
@@ -426,8 +427,8 @@ public void save() throws CoreException {
426427
parent.mkdirs();
427428
try (DataOutputStream destination = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(location), 8192))) {
428429
destination.write(getVersion());
429-
destination.writeInt(entries.size());
430-
for (java.util.Map.Entry<String, Object> entry : entries.entrySet()) {
430+
destination.writeInt(entriesSnapshot.size());
431+
for (java.util.Map.Entry<String, Object> entry : entriesSnapshot.entrySet()) {
431432
writeEntryKey(destination, entry.getKey());
432433
writeEntryValue(destination, entry.getValue());
433434
}

0 commit comments

Comments
 (0)