2121import java .io .IOException ;
2222import java .util .Collection ;
2323import java .util .Set ;
24+ import java .util .concurrent .locks .Lock ;
25+ import java .util .concurrent .locks .ReentrantReadWriteLock ;
2426
2527import io .undertow .UndertowLogger ;
2628import io .undertow .server .handlers .cache .DirectBufferCache ;
29+ import io .undertow .server .handlers .cache .DirectBufferCache .CacheEntry ;
2730import io .undertow .server .handlers .cache .LRUCache ;
2831
2932/**
@@ -57,7 +60,9 @@ public class CachingResourceManager implements ResourceManager {
5760 /**
5861 * A cache of file metadata, such as if a file exists or not
5962 */
60- private final LRUCache <String , Object > cache ;
63+ private LRUCache <String , Object > cache ;
64+
65+ private final ReentrantReadWriteLock cacheAccessLock = new ReentrantReadWriteLock ();
6166
6267 private int maxAge ;
6368
@@ -107,16 +112,30 @@ public CachedResource getResource(final String p) throws IOException {
107112 } else {
108113 path = p ;
109114 }
110- Object res = cache .get (path );
115+ // ReadLock will allow multiple reads and changes, guarded by WriteLock for purge
116+ final Lock readLock = this .cacheAccessLock .readLock ();
117+ final Lock writeLock = this .cacheAccessLock .writeLock ();
118+ Object res = null ;
119+ try {
120+ readLock .lock ();
121+ res = cache .get (path );
122+ } finally {
123+ readLock .unlock ();
124+ }
111125 if (res instanceof NoResourceMarker ) {
112126 NoResourceMarker marker = (NoResourceMarker ) res ;
113127 long nextCheck = marker .getNextCheckTime ();
114- if (nextCheck > 0 ) {
128+ if (nextCheck > 0 ) {
115129 long time = System .currentTimeMillis ();
116- if (time > nextCheck ) {
130+ if (time > nextCheck ) {
117131 marker .setNextCheckTime (time + maxAge );
118- if (underlyingResourceManager .getResource (path ) != null ) {
119- cache .remove (path );
132+ if (underlyingResourceManager .getResource (path ) != null ) {
133+ try {
134+ writeLock .lock ();
135+ cache .remove (path );
136+ } finally {
137+ writeLock .unlock ();
138+ }
120139 } else {
121140 return null ;
122141 }
@@ -131,19 +150,47 @@ public CachedResource getResource(final String p) throws IOException {
131150 if (resource .checkStillValid ()) {
132151 return resource ;
133152 } else {
134- invalidate (path );
153+ try {
154+ writeLock .lock ();
155+ invalidate (this .cache , path );
156+ } finally {
157+ writeLock .unlock ();
158+ }
135159 }
136160 }
137161 final Resource underlying = underlyingResourceManager .getResource (path );
138162 if (underlying == null ) {
139- if (this .maxAge != MAX_AGE_NO_CACHING ) {
140- cache .add (path , new NoResourceMarker (maxAge > 0 ? System .currentTimeMillis () + maxAge : -1 ));
163+ if (this .maxAge != MAX_AGE_NO_CACHING ) {
164+ try {
165+ writeLock .lock ();
166+ cache .add (path , new NoResourceMarker (maxAge > 0 ? System .currentTimeMillis () + maxAge : -1 ));
167+ } finally {
168+ writeLock .unlock ();
169+ }
141170 }
142171 return null ;
172+ // NOTE; in case of purge, no need to clear this, if there was resource, now there is none
173+ // invalidete() will purge potential dataCache entry
174+ }
175+ CachedResource resource = null ;
176+ try {
177+ writeLock .lock ();
178+ resource = new CachedResource (this , underlying , path );
179+ final DirectBufferCache dataCache = getDataCache ();
180+ if (dataCache != null ) {
181+ final CacheEntry o = dataCache .get (resource .getCacheKey ());
182+ if (o != null ) {
183+ // lazy remove, #invalidate() might have not get to this point.
184+ dataCache .remove (o .key ());
185+ }
186+ }
187+
188+ cache .add (path , resource );
189+ } finally {
190+ writeLock .unlock ();
143191 }
144- final CachedResource resource = new CachedResource (this , underlying , path );
145- cache .add (path , resource );
146192 return resource ;
193+
147194 }
148195
149196 @ Override
@@ -162,10 +209,38 @@ public void removeResourceChangeListener(ResourceChangeListener listener) {
162209 }
163210
164211 public void invalidate (String path ) {
212+ final Lock writeLock = this .cacheAccessLock .writeLock ();
213+ writeLock .lock ();
214+ try {
215+ this .invalidate (this .cache , path );
216+ } finally {
217+ writeLock .unlock ();
218+ }
219+ }
220+
221+ public void invalidate () {
222+ final Lock writeLock = this .cacheAccessLock .writeLock ();
223+ final LRUCache <String , Object > localCopy = this .cache ;
224+ writeLock .lock ();
225+ try {
226+ this .cache = new LRUCache (localCopy .getMaxEntries (),localCopy .getMaxAge ());
227+ } finally {
228+ writeLock .unlock ();
229+ }
230+ for (String key :localCopy .keySet ()) {
231+ //clear dataCache while new entries are made. This can potentially
232+ //remove dataCache entries, but those can be recreated.
233+ this .invalidate (localCopy , key );
234+ }
235+ //just in case
236+ localCopy .clear ();
237+ }
238+
239+ private void invalidate (LRUCache <String , Object > localCopy , String path ) {
165240 if (path .startsWith ("/" )) {
166241 path = path .substring (1 );
167242 }
168- Object entry = cache .remove (path );
243+ Object entry = localCopy .remove (path );
169244 if (entry instanceof CachedResource ) {
170245 ((CachedResource ) entry ).invalidate ();
171246 }
0 commit comments