30
30
import java .util .Iterator ;
31
31
import java .util .Map ;
32
32
import java .util .WeakHashMap ;
33
+ import java .util .concurrent .ConcurrentHashMap ;
34
+ import java .util .concurrent .ConcurrentMap ;
33
35
import org .eclipse .core .internal .resources .ResourceException ;
34
36
import org .eclipse .core .internal .resources .ResourceStatus ;
35
37
import org .eclipse .core .internal .utils .Messages ;
@@ -169,28 +171,28 @@ public void beforeSaving(Bucket bucket) throws CoreException {
169
171
* where the key is the path of the object we are storing history for, and
170
172
* the value is the history entry data (UUID,timestamp) pairs.
171
173
*/
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 ;
174
176
175
177
/**
176
178
* The file system location of this bucket index file.
177
179
*/
178
- private File location ;
180
+ private volatile File location ;
179
181
/**
180
182
* Whether the in-memory bucket is dirty and needs saving
181
183
*/
182
- private boolean needSaving = false ;
184
+ private volatile boolean needSaving = false ;
183
185
/**
184
186
* The project name for the bucket currently loaded. <code>null</code> if this is the root bucket.
185
187
*/
186
- protected String projectName ;
188
+ protected volatile String projectName ;
187
189
188
190
public Bucket () {
189
191
this (false );
190
192
}
191
193
192
194
public Bucket (boolean cacheEntries ) {
193
- this .entries = new HashMap <>();
195
+ this .entries = new ConcurrentHashMap <>();
194
196
if (cacheEntries ) {
195
197
entriesCache = new SoftReference <>(null );
196
198
}
@@ -332,7 +334,7 @@ public void load(String newProjectName, File baseLocation, boolean force) throws
332
334
loadedEntries = loadEntries (this .location );
333
335
} else {
334
336
if (isCachingEnabled ()) {
335
- Map <Object , Map <String , Object >> cache = entriesCache .get ();
337
+ Map <String , Map <String , Object >> cache = entriesCache .get ();
336
338
if (cache != null ) {
337
339
loadedEntries = cache .get (createBucketKey ());
338
340
}
@@ -355,7 +357,7 @@ boolean isCachingEnabled() {
355
357
return entriesCache != null ;
356
358
}
357
359
358
- private Object createBucketKey () {
360
+ private String createBucketKey () {
359
361
return this .location == null ? null : this .location .getAbsolutePath ();
360
362
}
361
363
@@ -396,25 +398,24 @@ private String readEntryKey(DataInputStream source) throws IOException {
396
398
* Saves this bucket's contents back to its location.
397
399
*/
398
400
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 );
399
404
if (isCachingEnabled ()) {
400
- Object key = createBucketKey ();
405
+ String key = createBucketKey ();
401
406
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 ();
407
408
if (cache == null ) {
408
409
cache = new WeakHashMap <>();
409
410
entriesCache = new SoftReference <>(cache );
410
411
}
411
- cache .put (key , denseCopy ); // remember the entries in cache
412
+ cache .put (key , entriesSnapshot ); // remember the entries in cache
412
413
}
413
414
}
414
415
if (!needSaving )
415
416
return ;
416
417
try {
417
- if (entries .isEmpty ()) {
418
+ if (entriesSnapshot .isEmpty ()) {
418
419
needSaving = false ;
419
420
cleanUp (location );
420
421
return ;
@@ -426,8 +427,8 @@ public void save() throws CoreException {
426
427
parent .mkdirs ();
427
428
try (DataOutputStream destination = new DataOutputStream (new BufferedOutputStream (new FileOutputStream (location ), 8192 ))) {
428
429
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 ()) {
431
432
writeEntryKey (destination , entry .getKey ());
432
433
writeEntryValue (destination , entry .getValue ());
433
434
}
0 commit comments