3030import java .util .Iterator ;
3131import java .util .Map ;
3232import java .util .WeakHashMap ;
33+ import java .util .concurrent .ConcurrentHashMap ;
34+ import java .util .concurrent .ConcurrentMap ;
3335import org .eclipse .core .internal .resources .ResourceException ;
3436import org .eclipse .core .internal .resources .ResourceStatus ;
3537import 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