1 | /* |
2 | |
3 | Derby - Class org.apache.derby.impl.services.cache.Clock |
4 | |
5 | Copyright 1997, 2004 The Apache Software Foundation or its licensors, as applicable. |
6 | |
7 | Licensed under the Apache License, Version 2.0 (the "License"); |
8 | you may not use this file except in compliance with the License. |
9 | You may obtain a copy of the License at |
10 | |
11 | http://www.apache.org/licenses/LICENSE-2.0 |
12 | |
13 | Unless required by applicable law or agreed to in writing, software |
14 | distributed under the License is distributed on an "AS IS" BASIS, |
15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
16 | See the License for the specific language governing permissions and |
17 | limitations under the License. |
18 | |
19 | */ |
20 | |
21 | package org.apache.derby.impl.services.cache; |
22 | |
23 | import org.apache.derby.iapi.services.cache.CacheManager; |
24 | import org.apache.derby.iapi.services.cache.Cacheable; |
25 | import org.apache.derby.iapi.services.cache.CacheableFactory; |
26 | import org.apache.derby.iapi.services.cache.SizedCacheable; |
27 | import org.apache.derby.iapi.services.context.ContextManager; |
28 | import org.apache.derby.iapi.services.daemon.DaemonService; |
29 | import org.apache.derby.iapi.services.daemon.Serviceable; |
30 | |
31 | import org.apache.derby.iapi.error.StandardException; |
32 | |
33 | import org.apache.derby.iapi.services.monitor.Monitor; |
34 | |
35 | import org.apache.derby.iapi.services.sanity.SanityManager; |
36 | |
37 | import org.apache.derby.iapi.services.cache.ClassSize; |
38 | import org.apache.derby.iapi.util.Matchable; |
39 | import org.apache.derby.iapi.util.Operator; |
40 | import org.apache.derby.iapi.reference.SQLState; |
41 | |
42 | import java.util.ArrayList; |
43 | import java.util.Hashtable; |
44 | import java.util.Properties; |
45 | |
46 | |
47 | /** |
48 | A cache manager that uses a Hashtable and a ArrayList. The ArrayList holds |
49 | CachedItem objects, each with a holder object. The Hashtable is keyed |
50 | by the identity of the holder object (Cacheable.getIdentity()) and |
51 | the data portion is a pointer to the CachedItem. CachedItems that have |
52 | holder objects with no identity do not have entries in the hashtable. |
53 | <P> |
54 | CachedItems can in various state. |
55 | <UL> |
56 | <LI>isValid - the entry has a valid identity |
57 | <LI>inCreate - the entry is being created or being faulted in from persistent store |
58 | <LI>inClean - the entry is being written out to persistent store |
59 | <LI>isKept - the entry is currently being looked at/updated, do not remove or |
60 | clean it. |
61 | </OL> |
62 | |
63 | <P>Multithreading considerations:<BR> |
64 | A clock cache manager must be MT-safe. |
65 | All member variables are accessed single threaded (synchronized on this) or |
66 | set once or readonly. Assumptions: holders size() and addElement must be |
67 | synchronized. |
68 | <BR> |
69 | CachedItem is never passed out of the clock cache manager, only the |
70 | Cacheable object is. The cachedItem is responsible for the setting and |
71 | clearing of its own member fields (RESOLVE: now they are done in cache |
72 | manager, need to be moved to the cachedItem). The cache manager will |
73 | following the following rules while accessing a cacheditem: |
74 | <UL> |
75 | <LI>invalid item is never returned from the cache |
76 | <LI>setValidState and isValid() is only called single threaded through the cache manager. |
77 | <LI>keep() and isKept() is only called single threaded through the cache |
78 | manager once the item has been added to the holders array |
79 | <LI>item that isKept() won't be cleaned or removed or invalidated from the cache. |
80 | <LI>item that is inClean() or inCreate(), the cache manager |
81 | will wait on the cachedItem to finish cleaning or creating before it |
82 | returns the cached item outside of the cache. |
83 | </UL> |
84 | <BR> |
85 | The cacheable must be cleaned thru the cache if it is managed by a cache. |
86 | On CacheItem, a inClean state is maintain to stablelize the content of the |
87 | cacheable while it is being cleaned. Only unkept items are cleaned. If an |
88 | item is found to be inClean, it will wait until it exits the inClean state. |
89 | If a cached item calls it own clean method without notifying the cache, it |
90 | has to stablize its content for the duration of the clean. |
91 | <BR> |
92 | It is assumed that the cacheable object maintain its own MT-safeness.<BR> |
93 | |
94 | @see CachedItem |
95 | @see Cacheable |
96 | |
97 | */ |
98 | |
99 | final class Clock extends Hashtable implements CacheManager, Serviceable { |
100 | |
101 | /* |
102 | ** Fields |
103 | */ |
104 | public final CacheStat stat; |
105 | private DaemonService cleaner; // the background worker thread who is going to |
106 | // do pre-flush for this cache. |
107 | private final ArrayList holders; |
108 | private int validItemCount = 0; |
109 | private long maximumSize; |
110 | private boolean useByteCount; // regulate the total byte count or the entry count |
111 | private long currentByteCount = 0; |
112 | /* currentByteCount should be the sum of entry.getSize() for all entries in the cache. |
113 | * That is, it should be the sum of getItemSize( item, false) for each item in the holders |
114 | * vector. |
115 | */ |
116 | |
117 | private static final int ITEM_OVERHEAD = ClassSize.estimateBaseFromCatalog( CachedItem.class) |
118 | + ClassSize.getRefSize() // one ref per item in the holder ArrayList |
119 | + ClassSize.estimateHashEntrySize(); |
120 | |
121 | private final CacheableFactory holderFactory; |
122 | |
123 | private boolean active; // true if active for find/create |
124 | private String name; // name of the cache, mainly for debugging purposes. |
125 | private int clockHand; // the sweep of the clock hand |
126 | |
127 | |
128 | private int myClientNumber; // use this number to talk to cleaner service |
129 | private boolean wokenToClean; // true if the client was woken to clean, false if to shrink |
130 | private boolean cleanerRunning; |
131 | private boolean needService; |
132 | |
133 | /** |
134 | Construct a new clock cache manager. |
135 | |
136 | <P>MT - not needed for constructor. |
137 | |
138 | @param holderFactory the cacheable object class |
139 | @param name the name of the cache |
140 | @param initialSize the initial number of cachable object this cache |
141 | holds. |
142 | @param maximumSize the maximum size of the cache. The cache may grow |
143 | from initialSize to maximumSize if the cache policy notices that there |
144 | is not enough free buffers availiable. Once the cache hits maximumSize |
145 | it will not grow. If the cache is full, an exception will be thrown |
146 | |
147 | */ |
148 | Clock(CacheableFactory holderFactory, |
149 | String name, |
150 | int initialSize, |
151 | long maximumSize, |
152 | boolean useByteCount) |
153 | { |
154 | super(initialSize, (float) 0.95); |
155 | this.maximumSize = maximumSize; |
156 | this.holderFactory = holderFactory; |
157 | this.useByteCount = useByteCount; |
158 | |
159 | if (SanityManager.DEBUG) { |
160 | if (SanityManager.DEBUG_ON(ClockFactory.CacheTrace)) { |
161 | SanityManager.DEBUG(ClockFactory.CacheTrace, "initializing " + name + " cache to size " + initialSize); |
162 | } |
163 | } |
164 | |
165 | //int delta = initialSize / 2; |
166 | //if (delta < 5) |
167 | // delta = 5; |
168 | |
169 | holders = new ArrayList(initialSize); |
170 | this.name = name; |
171 | active = true; |
172 | |
173 | this.stat = new CacheStat(); |
174 | stat.initialSize = initialSize; |
175 | stat.maxSize = maximumSize; |
176 | } |
177 | |
178 | /** |
179 | Find the object or materialize one in the cache. If it has not been |
180 | created in the persistent store yet, return null. |
181 | |
182 | <P>MT - must be MT-safe. The cache is single threaded through finding |
183 | the item in cache and finding a free item if it is not in cache, thus |
184 | preventing another thread from creating the same item while is is being |
185 | faulted in. (RESOLVE - this is really low performance if the cache |
186 | cleaner cannot keep a steady supply of free items and we have to do an |
187 | I/O while blocking the cache). If it needs to be faulted in, the |
188 | inCreate bit is set. The item is kept before it exits the sync block. |
189 | <BR> |
190 | If the item is in cache but in the middle of being faulted in or |
191 | cleaned, it needs to wait until this is done being before returning. |
192 | <BR> |
193 | The keep status prevents other threads from removing this item. |
194 | The inCreate status prevents other threads from looking at or writing |
195 | out this item while it is being faulted in. |
196 | (RESOLVE: need to handle the case where the object is marked for |
197 | removal and being waited on) |
198 | |
199 | @param key the key to the object |
200 | @return a cacheable object that is kept in the cache. |
201 | @exception StandardException Cloudscape Standard error policy |
202 | */ |
203 | public Cacheable find(Object key) throws StandardException { |
204 | CachedItem item; |
205 | boolean add; |
206 | |
207 | /* |
208 | ** We will only loop if someone else tried to add the |
209 | ** same key as we did and they failed. In this case |
210 | ** we start all over. An example of this would be an |
211 | ** attempt to cache an object that failed due to a |
212 | ** transient error (e.g. deadlock), which should not |
213 | ** prevent another thread from trying to add the |
214 | ** key to the cache (e.g. it might be the one holding |
215 | ** the lock that caused the other thread to deadlock). |
216 | */ |
217 | while (true) |
218 | { |
219 | add = false; |
220 | |
221 | synchronized (this) { |
222 | |
223 | if (!active) |
224 | return null; |
225 | |
226 | item = (CachedItem) get(key); |
227 | |
228 | if (item != null) { |
229 | item.keepAfterSearch(); |
230 | |
231 | stat.findHit++; |
232 | |
233 | if (SanityManager.DEBUG) { |
234 | if (SanityManager.DEBUG_ON(ClockFactory.CacheTrace)) |
235 | { |
236 | SanityManager.DEBUG(ClockFactory.CacheTrace, name + ": Found key " + |
237 | key + " already in cache, item " + item); |
238 | } |
239 | } |
240 | } |
241 | } |
242 | |
243 | // no entry was found, need to add one |
244 | if (item == null) { |
245 | |
246 | // get a free item |
247 | item = findFreeItem(); |
248 | |
249 | stat.findMiss++; |
250 | |
251 | if (SanityManager.DEBUG) { |
252 | if (SanityManager.DEBUG_ON(ClockFactory.CacheTrace)) |
253 | { |
254 | SanityManager.DEBUG(ClockFactory.CacheTrace, name + ": Find key " + |
255 | key + " Not in cache, get free item " + item); |
256 | } |
257 | } |
258 | |
259 | |
260 | if (SanityManager.DEBUG) |
261 | SanityManager.ASSERT(item != null, "found null item"); |
262 | |
263 | synchronized (this) { |
264 | CachedItem inCacheItem = (CachedItem) get(key); |
265 | |
266 | if (inCacheItem != null) { |
267 | // some-one beat us to adding an item into the cache, |
268 | // just use that one |
269 | item.unkeepForCreate(); |
270 | |
271 | item = inCacheItem; |
272 | item.keepAfterSearch(); |
273 | } else { |
274 | // yes, we really are the ones to add it |
275 | put(key, item); |
276 | add = true; |
277 | if (SanityManager.DEBUG) { |
278 | |
279 | if (SanityManager.DEBUG_ON("memoryLeakTrace")) { |
280 | |
281 | if (size() > ((11 * maximumSize) / 10)) |
282 | System.out.println("memoryLeakTrace:Cache:" + name + " " + size()); |
283 | } |
284 | } |
285 | } |
286 | } |
287 | } |
288 | |
289 | if (add) { |
290 | |
291 | if (SanityManager.DEBUG) { |
292 | if (SanityManager.DEBUG_ON(ClockFactory.CacheTrace)) |
293 | { |
294 | SanityManager.DEBUG(ClockFactory.CacheTrace, name + " Added " + |
295 | key + " to cache, item " + item); |
296 | } |
297 | } |
298 | |
299 | stat.findFault++; |
300 | |
301 | return addEntry(item, key, false, (Object) null); |
302 | } |
303 | |
304 | Cacheable entry = item.use(); |
305 | if (entry == null) { |
306 | // item was not added by the other user successfully ... |
307 | synchronized (this) { |
308 | item.unkeep(); |
309 | } |
310 | |
311 | // try to hash the key again (see |
312 | // comment at head of loop) |
313 | continue; |
314 | } |
315 | |
316 | return entry; |
317 | } |
318 | } |
319 | |
320 | |
321 | /** |
322 | Find an object in the cache. Do not fault in or create the object if |
323 | is is not found in the cache. |
324 | |
325 | <P>MT - must be MT-safe. The cache is single threaded through finding |
326 | the item in cache. If it needs to wait for it to be faulted in or |
327 | cleaned it is synchronized/waited on the cached item itself. |
328 | |
329 | @param key the key to the object |
330 | @return a cacheable object that is kept in the cache. |
331 | */ |
332 | |
333 | public Cacheable findCached(Object key) throws StandardException { |
334 | |
335 | |
336 | CachedItem item; |
337 | |
338 | synchronized (this) { |
339 | |
340 | if (!active) |
341 | return null; |
342 | |
343 | item = (CachedItem) get(key); |
344 | |
345 | if (item == null) { |
346 | stat.findCachedMiss++; |
347 | return null; |
348 | } else |
349 | stat.findCachedHit++; |
350 | |
351 | item.keepAfterSearch(); |
352 | } |
353 | |
354 | Cacheable entry = item.use(); |
355 | if (entry == null) { |
356 | // item was not added by the other user successfully ... |
357 | synchronized (this) { |
358 | item.unkeep(); |
359 | } |
360 | } |
361 | |
362 | return entry; |
363 | } |
364 | |
365 | |
366 | /** |
367 | * Mark a set of entries as having been used. Normally this is done as a side effect |
368 | * of find() or findCached. Entries that are no longer in the cache are ignored. |
369 | * |
370 | * @param keys the key of the used entry. |
371 | */ |
372 | public void setUsed( Object[] keys) |
373 | { |
374 | CachedItem item; |
375 | |
376 | for( int i = 0; i < keys.length;) |
377 | { |
378 | // Do not hold the synchronization lock for too long. |
379 | synchronized (this) |
380 | { |
381 | if (!active) |
382 | return; |
383 | |
384 | int endIdx = i + 32; |
385 | if( endIdx > keys.length) |
386 | endIdx = keys.length; |
387 | for( ; i < endIdx; i++) |
388 | { |
389 | if( keys[i] == null) |
390 | return; |
391 | |
392 | item = (CachedItem) get(keys[i]); |
393 | if( null != item) |
394 | item.setUsed( true); |
395 | } |
396 | } |
397 | } |
398 | } // end of setUsed |
399 | |
400 | /** |
401 | Create a new object with the said key. |
402 | |
403 | <P>MT - must be MT-safe. Single thread thru verifying no such item |
404 | exist in cache and finding a free item, keep the item and set inCreate |
405 | state. The actual creating of the object is done outside |
406 | the sync block and is protected by the isKept and inCreate bits. |
407 | |
408 | @param key the key to the object |
409 | @return a cacheable object that is kept in the cache. |
410 | |
411 | @exception StandardException Cloudscape Standard error policy |
412 | */ |
413 | public Cacheable create(Object key, Object createParameter) throws StandardException { |
414 | |
415 | // assume the item is not already in the cache |
416 | CachedItem item = findFreeItem(); |
417 | |
418 | stat.create++; |
419 | |
420 | synchronized (this) { |
421 | |
422 | if (!active) |
423 | return null; |
424 | |
425 | if (get(key) != null) { |
426 | |
427 | item.unkeepForCreate(); |
428 | |
429 | throw StandardException.newException(SQLState.OBJECT_EXISTS_IN_CACHE, this.name, key); |
430 | } |
431 | |
432 | put(key, item); |
433 | |
434 | if (SanityManager.DEBUG) { |
435 | |
436 | if (SanityManager.DEBUG_ON("memoryLeakTrace")) { |
437 | |
438 | if (size() > ((11 * maximumSize) / 10)) |
439 | System.out.println("memoryLeakTrace:Cache:" + name + " " + size()); |
440 | } |
441 | } |
442 | } |
443 | |
444 | Cacheable entry = addEntry(item, key, true, createParameter); |
445 | |
446 | if (SanityManager.DEBUG) { |
447 | if (entry != null) |
448 | SanityManager.ASSERT(item.getEntry() == entry); |
449 | } |
450 | |
451 | return entry; |
452 | } |
453 | |
454 | |
455 | /** |
456 | The caller is no longer looking at or updating the entry. Since there |
457 | could be more than one piece of code looking at this entry, release |
458 | does not mean nobody is looking at or updating the entry, just one |
459 | less. If the cacheable is marked for remove (someone is waiting to |
460 | remove the persistent object once nobody is looking at it), then notify |
461 | the waiter if this is the last one looking at it. |
462 | <BR> |
463 | Unless there is a good reason to do otherwise, release should be used |
464 | to release a cachable and not directly call cachedItem unkeep, since |
465 | unkeep does not handle the case of remove. |
466 | |
467 | |
468 | <P>MT - must be MT-safe. Getting and deleteing item from the hashtable |
469 | is in the same synchronized block. If the cacheable object is waiting |
470 | to be removed, that is synchronized thru the cachedItem itself |
471 | (RESOLVE: need to move this sync block to cachedItem instead) |
472 | |
473 | @param entry the cached entry |
474 | |
475 | */ |
476 | public void release(Cacheable entry) { |
477 | boolean removeItem; |
478 | CachedItem item; |
479 | long toShrink = 0; |
480 | |
481 | synchronized (this) { |
482 | |
483 | item = (CachedItem) get(entry.getIdentity()); |
484 | |
485 | if (SanityManager.DEBUG) { |
486 | SanityManager.ASSERT(item != null, "item null"); |
487 | SanityManager.ASSERT(item.getEntry() == entry, "entry not equals keyed entry"); |
488 | SanityManager.ASSERT(item.isKept(), "item is not kept in release(Cachable)"); |
489 | } |
490 | |
491 | removeItem = item.unkeep(); |
492 | |
493 | if (removeItem) { |
494 | |
495 | remove(entry.getIdentity()); |
496 | |
497 | // we keep the item here to stop another thread trying to evict it |
498 | // while we are destroying it. |
499 | item.keepForClean(); |
500 | } |
501 | |
502 | if (cleaner == null) { |
503 | // try to shrink the cache on a release |
504 | toShrink = shrinkSize( getCurrentSize()); |
505 | } |
506 | } |
507 | |
508 | if (removeItem) { |
509 | |
510 | item.notifyRemover(); |
511 | } |
512 | |
513 | if (toShrink > 0) |
514 | performWork(true /* shrink only */); |
515 | } |
516 | |
517 | protected void release(CachedItem item) { |
518 | |
519 | boolean removeItem; |
520 | |
521 | synchronized (this) { |
522 | |
523 | if (SanityManager.DEBUG) { |
524 | SanityManager.ASSERT(item.isKept(), "item is not kept in released(CachedItem)"); |
525 | } |
526 | |
527 | removeItem = item.unkeep(); |
528 | |
529 | if (removeItem) { |
530 | |
531 | remove(item.getEntry().getIdentity()); |
532 | |
533 | // we keep the item here to stop another thread trying to evict it |
534 | // while we are destroying it. |
535 | item.keepForClean(); |
536 | } |
537 | } |
538 | |
539 | if (removeItem) { |
540 | |
541 | item.notifyRemover(); |
542 | } |
543 | } |
544 | |
545 | /** |
546 | Remove an object from the cache. The item will be placed into the NoIdentity |
547 | state through clean() (if required) and clearIdentity(). The removal of the |
548 | object will be delayed until it is not kept by anyone. |
549 | |
550 | After this call the caller must throw away the reference to item. |
551 | |
552 | <P>MT - must be MT-safe. Single thread thru finding and setting the |
553 | remove state of the item, the actual removal of the cacheable is |
554 | synchronized on the cachedItem itself. |
555 | |
556 | @exception StandardException Standard Cloudscape error policy. |
557 | */ |
558 | public void remove(Cacheable entry) throws StandardException { |
559 | |
560 | boolean removeNow; |
561 | CachedItem item; |
562 | long origItemSize = 0; |
563 | |
564 | stat.remove++; |
565 | |
566 | synchronized (this) { |
567 | |
568 | |
569 | |
570 | item = (CachedItem) get(entry.getIdentity()); |
571 | |
572 | if (SanityManager.DEBUG) { |
573 | SanityManager.ASSERT(item != null); |
574 | SanityManager.ASSERT(item.getEntry() == entry); |
575 | SanityManager.ASSERT(item.isKept()); |
576 | } |
577 | if( useByteCount) |
578 | origItemSize = getItemSize( item); |
579 | |
580 | item.setRemoveState(); |
581 | removeNow = item.unkeep(); |
582 | |
583 | if (removeNow) { |
584 | remove(entry.getIdentity()); |
585 | item.keepForClean(); |
586 | } |
587 | } |
588 | |
589 | try { |
590 | // if removeNow is false then this thread may sleep |
591 | item.remove(removeNow); |
592 | |
593 | } finally { |
594 | |
595 | synchronized (this) |
596 | { |
597 | // in the case where this thread didn't call keepForClean() the thread |
598 | // that woke us would have called keepForClean. |
599 | item.unkeep(); |
600 | item.setValidState(false); |
601 | validItemCount--; |
602 | item.getEntry().clearIdentity(); |
603 | if( useByteCount) |
604 | currentByteCount += getItemSize( item) - origItemSize; |
605 | } |
606 | } |
607 | |
608 | } |
609 | |
610 | /** |
611 | Clean all objects in the cache. |
612 | */ |
613 | public void cleanAll() throws StandardException { |
614 | stat.cleanAll++; |
615 | cleanCache((Matchable) null); |
616 | } |
617 | |
618 | /** |
619 | Clean all objects that match a partial key. |
620 | */ |
621 | public void clean(Matchable partialKey) throws StandardException { |
622 | |
623 | cleanCache(partialKey); |
624 | } |
625 | |
626 | /** |
627 | Age as many objects as possible out of the cache. |
628 | |
629 | <BR>MT - thread safe |
630 | |
631 | @see CacheManager#ageOut |
632 | */ |
633 | public void ageOut() { |
634 | |
635 | stat.ageOut++; |
636 | synchronized (this) { |
637 | |
638 | int size = holders.size(); |
639 | long toShrink = shrinkSize( getCurrentSize()); |
640 | boolean shrunk = false; |
641 | |
642 | for (int position = 0; position < size; position++) { |
643 | CachedItem item = (CachedItem) holders.get(position); |
644 | |
645 | if (item.isKept()) |
646 | continue; |
647 | if (!item.isValid()) |
648 | continue; |
649 | |
650 | if (item.getEntry().isDirty()) { |
651 | continue; |
652 | } |
653 | |
654 | long itemSize = removeIdentity(item); |
655 | |
656 | if (toShrink > 0) { |
657 | |
658 | if (SanityManager.DEBUG) { |
659 | if (SanityManager.DEBUG_ON(ClockFactory.CacheTrace)) { |
660 | SanityManager.DEBUG(ClockFactory.CacheTrace, name + |
661 | " shrinking item " + item + " at position " + position); |
662 | } |
663 | } |
664 | |
665 | toShrink -= itemSize; |
666 | shrunk = true; |
667 | } |
668 | |
669 | } // end of for loop |
670 | |
671 | if (shrunk) |
672 | trimToSize(); |
673 | |
674 | } // out of sync block |
675 | } // end of ageOut |
676 | |
677 | /** |
678 | MT - synchronization provided by caller |
679 | |
680 | @exception StandardException Standard Cloudscape error policy. |
681 | */ |
682 | public void shutdown() throws StandardException { |
683 | |
684 | if (cleaner != null) { |
685 | cleaner.unsubscribe(myClientNumber); |
686 | cleaner = null; |
687 | } |
688 | |
689 | synchronized (this) { |
690 | active = false; |
691 | } |
692 | |
693 | ageOut(); |
694 | cleanAll(); |
695 | ageOut(); |
696 | } |
697 | |
698 | /** |
699 | MT - synchronization provided by caller |
700 | |
701 | can use this Daemomn service if needed |
702 | */ |
703 | public void useDaemonService(DaemonService daemon) |
704 | { |
705 | // if we were using another cleaner, unsubscribe first |
706 | if (cleaner != null) |
707 | cleaner.unsubscribe(myClientNumber); |
708 | |
709 | cleaner = daemon; |
710 | myClientNumber = cleaner.subscribe(this, true /* onDemandOnly */); |
711 | } |
712 | /** |
713 | Discard all objects that match the partial key. |
714 | |
715 | <BR>MT - thread safe |
716 | */ |
717 | public boolean discard(Matchable partialKey) { |
718 | |
719 | // we miss something because it was kept |
720 | boolean noMisses = true; |
721 | |
722 | synchronized (this) { |
723 | |
724 | int size = holders.size(); |
725 | long toShrink = shrinkSize( getCurrentSize()); |
726 | boolean shrunk = false; |
727 | |
728 | for (int position = 0; position < size; position++) { |
729 | CachedItem item = (CachedItem) holders.get(position); |
730 | |
731 | if (!item.isValid()) |
732 | continue; |
733 | |
734 | Object key = item.getEntry().getIdentity(); |
735 | |
736 | if (partialKey != null && !partialKey.match(key)) |
737 | continue; |
738 | |
739 | if (item.isKept()) |
740 | { |
741 | noMisses = false; |
742 | continue; |
743 | } |
744 | |
745 | long itemSize = removeIdentity(item); |
746 | |
747 | if (toShrink > 0) { |
748 | |
749 | if (SanityManager.DEBUG) { |
750 | if (SanityManager.DEBUG_ON(ClockFactory.CacheTrace)) { |
751 | SanityManager.DEBUG(ClockFactory.CacheTrace, name + |
752 | " shrinking item " + item + " at position " + position); |
753 | } |
754 | } |
755 | |
756 | // and we shrunk one item |
757 | toShrink -= itemSize; |
758 | shrunk = true; |
759 | } |
760 | } |
761 | |
762 | if (shrunk) |
763 | trimToSize(); |
764 | } |
765 | |
766 | return noMisses; |
767 | } |
768 | |
769 | /** |
770 | Add a new CachedItem and a holder object to the cache. The holder object |
771 | is returned kept. |
772 | |
773 | <P>MT - need to be MT-safe. The insertion of the key into the hash |
774 | table is synchronized on this. |
775 | |
776 | */ |
777 | private Cacheable addEntry(CachedItem item, Object key, boolean forCreate, Object createParameter) |
778 | throws StandardException { |
779 | |
780 | Cacheable entry = null; |
781 | long origEntrySize = 0; |
782 | if( useByteCount) |
783 | origEntrySize = getItemSize( item); |
784 | |
785 | try |
786 | { |
787 | if (SanityManager.DEBUG) { |
788 | if (SanityManager.DEBUG_ON(ClockFactory.CacheTrace)) |
789 | { |
790 | SanityManager.DEBUG(ClockFactory.CacheTrace, name + |
791 | " item " + item + " take on identity " + key); |
792 | } |
793 | } |
794 | |
795 | // tell the object it needs to create itself |
796 | entry = item.takeOnIdentity(this, holderFactory, key, forCreate, createParameter); |
797 | } |
798 | finally |
799 | { |
800 | boolean notifyWaiters; |
801 | synchronized (this) { |
802 | |
803 | Object removed = remove(key); |
804 | if (SanityManager.DEBUG) { |
805 | SanityManager.ASSERT(removed == item); |
806 | } |
807 | |
808 | if (entry != null) { |
809 | // put the actual key into the hash table, not the one that was passed in |
810 | // for the find or create. This is because the caller may re-use the key |
811 | // for another cache operation, which would corrupt our hashtable |
812 | put(entry.getIdentity(), item); |
813 | if( useByteCount) |
814 | currentByteCount += ((SizedCacheable) entry).getSize() - origEntrySize; |
815 | item.setValidState(true); |
816 | validItemCount++; |
817 | notifyWaiters = true; |
818 | } else { |
819 | item.unkeep(); |
820 | notifyWaiters = item.isKept(); |
821 | } |
822 | } |
823 | |
824 | // whatever the outcome, we have to notify waiters ... |
825 | if (notifyWaiters) |
826 | item.settingIdentityComplete(); |
827 | } |
828 | |
829 | return entry; |
830 | } |
831 | |
832 | |
833 | protected CachedItem findFreeItem() throws StandardException { |
834 | |
835 | // Need to avoid thrashing the cache when we start out |
836 | // so if the cache is smaller than its maximum size |
837 | // then that's a good indication we should grow. |
838 | |
839 | long currentSize = getCurrentSize(); |
840 | |
841 | |
842 | if (currentSize >= maximumSize) { |
843 | // look at 20% |
844 | CachedItem item = rotateClock(0.2f); |
845 | if (item != null) |
846 | return item; |
847 | } |
848 | |
849 | // However, if the cache contains a large number of invalid |
850 | // items then we should see if we can avoid growing. |
851 | // This avoids simple use of Cloudscape looking like |
852 | // a memory leak, as the page cache fills the holders array |
853 | // with page objects including the 4k (or 32k) pages. |
854 | // size() is the number of valid entries in the hash table |
855 | |
856 | |
857 | // no need to sync on getting the sizes since if they are |
858 | // wrong we will discover it in the loop. |
859 | if (validItemCount < holders.size()) { |
860 | |
861 | synchronized (this) { |
862 | |
863 | // 1) find out how many invalid items there are in the |
864 | // cache |
865 | // 2) search for a free invalid item |
866 | // 3) stop searching when there are no more invalid |
867 | // items to find |
868 | |
869 | int invalidItems = holders.size() - validItemCount; |
870 | |
871 | // Invalid items might occur in the cache when |
872 | // a) a new item is created in growCache(), but it |
873 | // is not in use yet, or |
874 | // b) an item is deleted (usually when a table is |
875 | // dropped) |
876 | |
877 | // It is critical to break out of the loop as soon as |
878 | // possible since we are blocking others trying to |
879 | // access the page cache. New items are added to the |
880 | // end of the page cache, so the search for invalid |
881 | // items should start from the end. |
882 | |
883 | for (int i = holders.size() - 1; (invalidItems > 0) && (i >= 0) ; i--) { |
884 | CachedItem item = (CachedItem) holders.get(i); |
885 | |
886 | if (item.isKept()) { |
887 | if (!item.isValid()) invalidItems--; |
888 | continue; |
889 | } |
890 | |
891 | // found a free item, just use it |
892 | if (!item.isValid()) { |
893 | item.keepForCreate(); |
894 | return item; |
895 | } |
896 | } |
897 | } |
898 | } |
899 | |
900 | |
901 | return growCache(); |
902 | } |
903 | |
904 | /** |
905 | Go through the list of holder objects and find a free one. |
906 | <P>MT - must be MT-safe. The moving of the clockHand and finding of an |
907 | eviction candidate is synchronized. The cleaning of the cachable is |
908 | handled by the cacheable itself. |
909 | */ |
910 | protected CachedItem rotateClock(float percentOfClock) throws StandardException |
911 | { |
912 | // statistics -- only used in debug |
913 | int evictions = 0; |
914 | int cleaned = 0; |
915 | int resetUsed = 0; |
916 | int iskept = 0; |
917 | |
918 | // When we are managing the entry count (useByteCount == false) this method just |
919 | // has to find or manufacture an available item (a cache slot). When we are managing |
920 | // the total byte count then this method must find both available space and an |
921 | // available item. |
922 | CachedItem availableItem = null; |
923 | |
924 | boolean kickCleaner = false; |
925 | |
926 | try { |
927 | |
928 | |
929 | // this can be approximate |
930 | int itemCount = holders.size(); |
931 | int itemsToCheck; |
932 | if (itemCount < 20) |
933 | itemsToCheck = 2 * itemCount; |
934 | else |
935 | itemsToCheck = (int) (((float) itemCount) * percentOfClock); |
936 | |
937 | |
938 | // if we can grow then shrinking is OK too, if we can't grow |
939 | // then shrinking the cache won't help us find an item. |
940 | long toShrink = shrinkSize(getCurrentSize()); |
941 | |
942 | restartClock: |
943 | for (; itemsToCheck > 0;) { |
944 | |
945 | CachedItem item = null; |
946 | |
947 | synchronized (this) { |
948 | |
949 | if (SanityManager.DEBUG) { |
950 | if (SanityManager.DEBUG_ON(ClockFactory.CacheTrace)) |
951 | { |
952 | SanityManager.DEBUG(ClockFactory.CacheTrace, name + " rotateClock starting " + |
953 | clockHand + " itemsToCheck " + itemsToCheck); |
954 | } |
955 | } |
956 | |
957 | // size of holders cannot change while in the synchronized block. |
958 | int size = holders.size(); |
959 | for (; itemsToCheck > 0; item = null, itemsToCheck--, incrClockHand()) |
960 | { |
961 | // |
962 | // This uses a very simple clock algorithm. |
963 | // |
964 | // The cache consist of a circular list of cachedItems. Each cached item |
965 | // has a 'recentlyUsed' bit which is set every time that item is kept. |
966 | // Each clock cache manager keeps a global variable clockHand which |
967 | // refers to the item that is most recently replaced. |
968 | // |
969 | // to find a free item, the clock Hand moves to the next cached Item. |
970 | // If it is kept, or in the middle of being created, the clock hand |
971 | // moves on. |
972 | // If it is recentlyUsed, clear the recently used bit and moves on. |
973 | // If it is not recentlyUsed, clean the item and use |
974 | // |
975 | // If all the cached item is kept, then create a new entry. |
976 | // So it is possible, although very unlikely, that, in time, the cache |
977 | // will grow beyond the maximum size. |
978 | |
979 | |
980 | |
981 | if (clockHand >= size) { |
982 | if (size == 0) |
983 | break; |
984 | clockHand = 0; |
985 | } |
986 | |
987 | item = (CachedItem) holders.get(clockHand); |
988 | |
989 | if (item.isKept()) |
990 | { |
991 | if (SanityManager.DEBUG) // stats only in debug mode |
992 | iskept++; |
993 | continue; |
994 | } |
995 | |
996 | if (!item.isValid()) // found a free item, just use it |
997 | { |
998 | if( null != availableItem) |
999 | // We have found an available item, now we are looking for bytes |
1000 | continue; |
1001 | if (SanityManager.DEBUG) { |
1002 | if (SanityManager.DEBUG_ON(ClockFactory.CacheTrace)) |
1003 | { |
1004 | SanityManager.DEBUG(ClockFactory.CacheTrace, |
1005 | name + " found free item at " + clockHand + " item " + item); |
1006 | } |
1007 | } |
1008 | |
1009 | item.keepForCreate(); |
1010 | if( useByteCount && getCurrentSize() > maximumSize) |
1011 | { |
1012 | availableItem = item; |
1013 | // now look for bytes. |
1014 | continue; |
1015 | } |
1016 | // since we are using this item, move the clock past it. |
1017 | incrClockHand(); |
1018 | |
1019 | return item; |
1020 | } |
1021 | |
1022 | if (item.recentlyUsed()) |
1023 | { |
1024 | |
1025 | if (SanityManager.DEBUG) // stats only in debug mode |
1026 | resetUsed++; |
1027 | item.setUsed(false); |
1028 | continue; |
1029 | } |
1030 | |
1031 | |
1032 | if (toShrink > 0) { |
1033 | if (!cleanerRunning) { |
1034 | |
1035 | // try an get the cleaner to shrink the cache |
1036 | kickCleaner = true; |
1037 | cleanerRunning = true; |
1038 | needService = true; |
1039 | } |
1040 | } |
1041 | |
1042 | // we are seeing valid, not recently used buffers. Evict this. |
1043 | if (SanityManager.DEBUG) { |
1044 | evictions++; |
1045 | |
1046 | if (SanityManager.DEBUG_ON(ClockFactory.CacheTrace)) |
1047 | { |
1048 | SanityManager.DEBUG(ClockFactory.CacheTrace, |
1049 | name + " evicting item at " + |
1050 | clockHand + " item " + item); |
1051 | } |
1052 | } |
1053 | |
1054 | if (!item.getEntry().isDirty()) { |
1055 | |
1056 | if (SanityManager.DEBUG) { |
1057 | if (SanityManager.DEBUG_ON(ClockFactory.CacheTrace)) |
1058 | { |
1059 | SanityManager.DEBUG(ClockFactory.CacheTrace, |
1060 | name + " Evicting Item " + |
1061 | item + ", not dirty"); |
1062 | } |
1063 | } |
1064 | |
1065 | // a valid, unkept, clean item, clear its identity |
1066 | // and use it. |
1067 | long itemSize = removeIdentity(item); |
1068 | |
1069 | if( useByteCount) |
1070 | { |
1071 | toShrink -= itemSize; |
1072 | if( getCurrentSize() > maximumSize && 0 < toShrink) |
1073 | { |
1074 | if( null == availableItem) |
1075 | { |
1076 | item.keepForCreate(); |
1077 | availableItem = item; |
1078 | } |
1079 | continue; |
1080 | } |
1081 | } |
1082 | // since we are using it move the clock past it |
1083 | incrClockHand(); |
1084 | |
1085 | if( null != availableItem) |
1086 | return availableItem; |
1087 | |
1088 | // item is kept but not valid when returned |
1089 | item.keepForCreate(); |
1090 | return item; |
1091 | } |
1092 | // item is valid, unkept, and dirty. clean it. |
1093 | if ((cleaner != null) && !cleanerRunning) { |
1094 | kickCleaner = true; |
1095 | wokenToClean = true; |
1096 | cleanerRunning = true; // at least it soon will be |
1097 | } |
1098 | item.keepForClean(); |
1099 | |
1100 | // leave the clock hand where it is so that we will pick it |
1101 | // up if no-one else uses the cache. Other hunters will |
1102 | // skip over it as it is kept, and thus move the clock |
1103 | // hand past it. |
1104 | break; |
1105 | } |
1106 | if (item == null) { |
1107 | return availableItem; |
1108 | } |
1109 | |
1110 | } // out of synchronized block |
1111 | |
1112 | // clean the entry outside of a sync block |
1113 | try |
1114 | { |
1115 | if ( SanityManager.DEBUG) { |
1116 | if (SanityManager.DEBUG_ON(ClockFactory.CacheTrace)) { |
1117 | SanityManager.DEBUG(ClockFactory.CacheTrace,name + " cleaning item " + item); |
1118 | } |
1119 | } |
1120 | |
1121 | item.clean(false); |
1122 | |
1123 | if (SanityManager.DEBUG) // stats only in debug mode |
1124 | { |
1125 | cleaned++; |
1126 | } |
1127 | } |
1128 | finally { |
1129 | release(item); |
1130 | item = null; |
1131 | } |
1132 | |
1133 | // at this point the item we cleaned could be in any state |
1134 | // so we can't just re-use it. Continue searching |
1135 | continue restartClock; |
1136 | } |
1137 | return availableItem; |
1138 | } finally { |
1139 | |
1140 | |
1141 | if (SanityManager.DEBUG) |
1142 | { |
1143 | // report statistics |
1144 | if ( |
1145 | SanityManager.DEBUG_ON(ClockFactory.CacheTrace)) |
1146 | SanityManager.DEBUG(ClockFactory.CacheTrace, name + " evictions " + evictions + |
1147 | ", cleaned " + cleaned + |
1148 | ", resetUsed " + resetUsed + |
1149 | ", isKept " + iskept + |
1150 | ", size " + holders.size()); |
1151 | } |
1152 | |
1153 | if (kickCleaner && (cleaner != null)) |
1154 | { |
1155 | if (SanityManager.DEBUG) { |
1156 | if (SanityManager.DEBUG_ON(DaemonService.DaemonTrace)) { |
1157 | SanityManager.DEBUG(DaemonService.DaemonTrace, name + " client # " + myClientNumber + " calling cleaner "); |
1158 | } |
1159 | } |
1160 | |
1161 | cleaner.serviceNow(myClientNumber); |
1162 | |
1163 | if (SanityManager.DEBUG) { |
1164 | if (SanityManager.DEBUG_ON(DaemonService.DaemonTrace)) { |
1165 | SanityManager.DEBUG(DaemonService.DaemonTrace, name + Thread.currentThread().getName() + " cleaner called"); |
1166 | } |
1167 | } |
1168 | } |
1169 | } |
1170 | } // end of rotateClock |
1171 | |
1172 | /** |
1173 | Synchronously increment clock hand position |
1174 | */ |
1175 | private int incrClockHand() |
1176 | { |
1177 | if (++clockHand >= holders.size()) |
1178 | clockHand = 0; |
1179 | return clockHand; |
1180 | } |
1181 | |
1182 | /* |
1183 | * Serviceable methods |
1184 | */ |
1185 | |
1186 | public int performWork(ContextManager contextMgr /* ignored */) { |
1187 | |
1188 | int ret = performWork(false); |
1189 | synchronized (this) { |
1190 | cleanerRunning = false; |
1191 | } |
1192 | return ret; |
1193 | } |
1194 | |
1195 | |
1196 | /** |
1197 | <P>MT - read only. |
1198 | */ |
1199 | public boolean serviceASAP() |
1200 | { |
1201 | return needService; |
1202 | } |
1203 | |
1204 | |
1205 | // @return true, if this work needs to be done on a user thread immediately |
1206 | public boolean serviceImmediately() |
1207 | { |
1208 | return false; |
1209 | } |
1210 | |
1211 | |
1212 | public synchronized int getNumberInUse() { |
1213 | |
1214 | int size = holders.size(); |
1215 | int inUse = 0; |
1216 | |
1217 | for (int position = 0; position < size; position++) { |
1218 | |
1219 | CachedItem item = (CachedItem) holders.get(position); |
1220 | |
1221 | if (item.isValid()) { |
1222 | inUse++; |
1223 | } |
1224 | |
1225 | } |
1226 | return inUse; |
1227 | } |
1228 | /* |
1229 | public int getNumberKept() { |
1230 | |
1231 | synchronized (this) { |
1232 | |
1233 | int size = holders.size(); |
1234 | int inUse = 0; |
1235 | |
1236 | for (int position = 0; position < size; position++) { |
1237 | |
1238 | CachedItem item = (CachedItem) holders.get(position); |
1239 | |
1240 | if (item.isValid() && item.isKept()) { |
1241 | inUse++; |
1242 | } |
1243 | |
1244 | } |
1245 | return inUse; |
1246 | } |
1247 | } |
1248 | */ |
1249 | |
1250 | /** |
1251 | Grow the cache and return a unused, kept item. |
1252 | |
1253 | @exception StandardException Thrown if the cache cannot be grown. |
1254 | */ |
1255 | |
1256 | private CachedItem growCache() { |
1257 | |
1258 | CachedItem item = new CachedItem(); |
1259 | item.keepForCreate(); |
1260 | |
1261 | // if we run out of memory below here we don't |
1262 | // know what state the holders could be in |
1263 | // so don't trap it |
1264 | synchronized (this) { |
1265 | holders.add(item); |
1266 | // Do not adjust currentByteCount until we put the entry into the CachedItem. |
1267 | } |
1268 | |
1269 | return item; |
1270 | } |
1271 | |
1272 | |
1273 | /** |
1274 | Clear an item's identity. Item must be |
1275 | unkept and valid. This is called for |
1276 | dirty items from the discard code. |
1277 | |
1278 | Caller must hold the cache synchronization. |
1279 | |
1280 | @return the amount by which this shrinks the cache. |
1281 | */ |
1282 | protected long removeIdentity(CachedItem item) { |
1283 | |
1284 | long shrink = 1; |
1285 | |
1286 | if (SanityManager.DEBUG) { |
1287 | SanityManager.ASSERT(!item.isKept(), "item is kept"); |
1288 | SanityManager.ASSERT(item.isValid(), "item is not valid"); |
1289 | |
1290 | } |
1291 | |
1292 | if( useByteCount) |
1293 | shrink = ((SizedCacheable) item.getEntry()).getSize(); |
1294 | remove(item.getEntry().getIdentity()); |
1295 | item.setValidState(false); |
1296 | validItemCount--; |
1297 | item.getEntry().clearIdentity(); |
1298 | if( useByteCount) |
1299 | { |
1300 | shrink -= ((SizedCacheable) item.getEntry()).getSize(); |
1301 | currentByteCount -= shrink; |
1302 | } |
1303 | return shrink; |
1304 | } |
1305 | |
1306 | /** |
1307 | Write out all dirty buffers. |
1308 | |
1309 | <P>MT - must be MT safe. |
1310 | Single thread on the part that finds the next dirty buffer to write |
1311 | out, the synchronization of cleaning of the individual cachable is |
1312 | provided by the cacheable itself. |
1313 | */ |
1314 | protected void cleanCache(Matchable partialKey) throws StandardException { |
1315 | |
1316 | int position; |
1317 | |
1318 | synchronized(this) |
1319 | { |
1320 | // this is at many dirty buffers as the cleaner is ever going to |
1321 | // see |
1322 | position = holders.size() - 1; |
1323 | } |
1324 | |
1325 | |
1326 | outerscan: |
1327 | for (;;) { |
1328 | |
1329 | CachedItem item = null; |
1330 | |
1331 | synchronized (this) { |
1332 | |
1333 | // the cache may have shrunk by quite a bit since we last came |
1334 | // in here |
1335 | int size = holders.size(); |
1336 | if (position >= size) |
1337 | position = size - 1; |
1338 | |
1339 | innerscan: |
1340 | // go from position (the last cached item in the holder array |
1341 | // to 0 (the first). Otherwise, if we go from 0 to |
1342 | // position, some other thread may come in and shrink items |
1343 | // which are between 0 and position. Since a shrink moves all |
1344 | // items up, we may skip some items without cleaning. |
1345 | for ( ; position >= 0; position--, item = null) { |
1346 | |
1347 | item = (CachedItem) holders.get(position); |
1348 | |
1349 | if (!item.isValid()) |
1350 | continue innerscan; |
1351 | |
1352 | if (!item.getEntry().isDirty()) |
1353 | continue innerscan; |
1354 | |
1355 | if (partialKey != null) { |
1356 | |
1357 | Object key = item.getEntry().getIdentity(); |
1358 | |
1359 | if (!partialKey.match(key)) |
1360 | continue; |
1361 | } |
1362 | |
1363 | item.keepForClean(); |
1364 | break innerscan; |
1365 | } |
1366 | } // end of synchronized block |
1367 | |
1368 | if (position < 0) |
1369 | { |
1370 | return; |
1371 | } |
1372 | |
1373 | try { |
1374 | |
1375 | item.clean(false); |
1376 | } finally { |
1377 | release(item); |
1378 | } |
1379 | position--; |
1380 | |
1381 | } // for (;;) |
1382 | } |
1383 | |
1384 | |
1385 | protected long shrinkSize(long currentSize) { |
1386 | |
1387 | long maxSize = getMaximumSize(); |
1388 | |
1389 | long toShrink = currentSize - maxSize; |
1390 | if (toShrink <= 0) |
1391 | return 0; |
1392 | |
1393 | // only shrink 10% of the maximum size |
1394 | long shrinkLimit = maxSize / 10; |
1395 | if (shrinkLimit == 0) |
1396 | shrinkLimit = 2; |
1397 | |
1398 | if (toShrink < shrinkLimit) |
1399 | return toShrink; |
1400 | else |
1401 | return shrinkLimit; |
1402 | } |
1403 | |
1404 | /** |
1405 | The background cleaner tries to make sure that there are serveral |
1406 | cleaned or invalied buffers ahead of the clock hand so that when they |
1407 | are evicted, they don't need to be cleaned. |
1408 | |
1409 | The way this routine work is as follows, starting at the current clock |
1410 | hand position, go forward around the cache buffers, moving the same |
1411 | route that the clock hand moves. It keep tracks of the number of |
1412 | invalid or not recently used buffers it sees along the way. If it sees |
1413 | a not recently used buffer, it will clean it. After it has seen N |
1414 | invalid or not recently used buffers, or it has gone around and visited |
1415 | all buffers in the cache, it finished. |
1416 | |
1417 | It does not clean recently used buffers. |
1418 | |
1419 | <P>MT - must be MT-safe. It takes a snapshot of the current clock hand |
1420 | position (a synchronous call). Getting and looking at the next |
1421 | serveral cached item is synchronized on this (RESOLVE: probably doesn't |
1422 | need to be). Cleaning of the cacheable is handle by the cacheable itself. |
1423 | |
1424 | */ |
1425 | protected int performWork(boolean shrinkOnly) |
1426 | { |
1427 | long target; |
1428 | long toShrink; |
1429 | int maxLooks; |
1430 | |
1431 | synchronized(this) |
1432 | { |
1433 | if (!active) { |
1434 | needService = false; |
1435 | return Serviceable.DONE; |
1436 | } |
1437 | else { |
1438 | long currentSize = getCurrentSize(); |
1439 | target = currentSize / 20; // attempt to get 5% of the cache clean |
1440 | toShrink = wokenToClean ? 0 : shrinkSize(currentSize); |
1441 | } |
1442 | |
1443 | if (target == 0) { |
1444 | wokenToClean = false; |
1445 | needService = false; |
1446 | return Serviceable.DONE; |
1447 | } |
1448 | |
1449 | if (!wokenToClean && (toShrink <= 0)) { |
1450 | needService = false; |
1451 | return Serviceable.DONE; |
1452 | } |
1453 | |
1454 | maxLooks = useByteCount ? (holders.size()/10) : (int) (target * 2); |
1455 | } |
1456 | |
1457 | // try to clean the next N (target) cached item, |
1458 | long clean = 0; |
1459 | int cleaned = 0; // only used in debug |
1460 | CachedItem item = null; |
1461 | int currentPosition = 0; |
1462 | |
1463 | String ThreadName = null; |
1464 | |
1465 | if (SanityManager.DEBUG) { |
1466 | if (SanityManager.DEBUG_ON(DaemonService.DaemonTrace)) |
1467 | { |
1468 | ThreadName = Thread.currentThread().getName(); |
1469 | SanityManager.DEBUG(DaemonService.DaemonTrace, ThreadName + " Cleaning " + name + " clientNumber " + myClientNumber); |
1470 | } |
1471 | } |
1472 | |
1473 | |
1474 | synchronized(this) |
1475 | { |
1476 | int itemCount = holders.size(); |
1477 | currentPosition = clockHand; |
1478 | |
1479 | // see if the cache needs to shrink |
1480 | boolean shrunk = false; |
1481 | long currentSize = getCurrentSize(); |
1482 | |
1483 | for (; shrinkOnly ? (currentSize > maximumSize && toShrink > 0) : (clean < target); item = null) |
1484 | { |
1485 | if (++currentPosition >= itemCount) { |
1486 | if (itemCount == 0) |
1487 | break; |
1488 | |
1489 | currentPosition = 0; |
1490 | |
1491 | } |
1492 | |
1493 | if (maxLooks-- <= 0) |
1494 | { |
1495 | if (SanityManager.DEBUG) { |
1496 | if (SanityManager.DEBUG_ON(DaemonService.DaemonTrace)) { |
1497 | SanityManager.DEBUG(DaemonService.DaemonTrace, ThreadName + " done one round of " + name); |
1498 | } |
1499 | } |
1500 | |
1501 | break; // done one round |
1502 | } |
1503 | |
1504 | item = (CachedItem) holders.get(currentPosition); |
1505 | |
1506 | if (item.isKept()) |
1507 | continue; |
1508 | |
1509 | if (!item.isValid()) |
1510 | { |
1511 | if (toShrink > 0) { |
1512 | |
1513 | if (SanityManager.DEBUG) { |
1514 | if (SanityManager.DEBUG_ON(ClockFactory.CacheTrace)) { |
1515 | SanityManager.DEBUG(ClockFactory.CacheTrace, name + |
1516 | " shrinking item " + item + " at position " + currentPosition); |
1517 | } |
1518 | } |
1519 | |
1520 | toShrink -= currentSize; |
1521 | holders.remove(currentPosition); |
1522 | if( useByteCount) |
1523 | currentByteCount -= getItemSize( item); |
1524 | currentSize = getCurrentSize(); |
1525 | toShrink += currentSize; |
1526 | itemCount--; |
1527 | |
1528 | // account for the fact all the items have shifted down |
1529 | currentPosition--; |
1530 | |
1531 | shrunk = true; |
1532 | } |
1533 | continue; |
1534 | } |
1535 | |
1536 | if (item.recentlyUsed()) |
1537 | continue; |
1538 | |
1539 | // found a valid, not kept, and not recently used item |
1540 | // this item will be cleaned |
1541 | int itemSize = getItemSize( item); |
1542 | clean += itemSize; |
1543 | if (!item.getEntry().isDirty()) { |
1544 | |
1545 | if (toShrink > 0) { |
1546 | if (SanityManager.DEBUG) { |
1547 | if (SanityManager.DEBUG_ON(ClockFactory.CacheTrace)) { |
1548 | SanityManager.DEBUG(ClockFactory.CacheTrace, name + |
1549 | " shrinking item " + item + " at position " + currentPosition); |
1550 | } |
1551 | } |
1552 | |
1553 | toShrink -= currentSize; |
1554 | removeIdentity(item); |
1555 | holders.remove(currentPosition); |
1556 | if( useByteCount) |
1557 | currentByteCount -= getItemSize( item); |
1558 | currentSize = getCurrentSize(); |
1559 | toShrink += currentSize; |
1560 | itemCount--; |
1561 | shrunk = true; |
1562 | |
1563 | // account for the fact all the items have shifted down |
1564 | currentPosition--; |
1565 | } |
1566 | continue; |
1567 | } |
1568 | |
1569 | if (shrinkOnly) |
1570 | continue; |
1571 | |
1572 | // found one that needs cleaning, keep it to clean |
1573 | item.keepForClean(); |
1574 | break; |
1575 | } // end of for loop |
1576 | |
1577 | if (shrunk) |
1578 | trimToSize(); |
1579 | |
1580 | if (item == null) { |
1581 | wokenToClean = false; |
1582 | needService = false; |
1583 | return Serviceable.DONE; |
1584 | } |
1585 | } // end of sync block |
1586 | |
1587 | try |
1588 | { |
1589 | if (SanityManager.DEBUG) { |
1590 | if (SanityManager.DEBUG_ON(DaemonService.DaemonTrace)) { |
1591 | SanityManager.DEBUG(DaemonService.DaemonTrace, ThreadName + " cleaning entry in " + name); |
1592 | } |
1593 | } |
1594 | |
1595 | item.clean(false); |
1596 | if (SanityManager.DEBUG) // only need stats for debug |
1597 | cleaned++; |
1598 | |
1599 | } catch (StandardException se) { |
1600 | // RESOLVE - should probably throw the error into the log. |
1601 | } |
1602 | finally |
1603 | { |
1604 | release(item); |
1605 | item = null; |
1606 | } |
1607 | |
1608 | if (SanityManager.DEBUG) { |
1609 | if (SanityManager.DEBUG_ON(DaemonService.DaemonTrace)) { |
1610 | SanityManager.DEBUG(DaemonService.DaemonTrace, ThreadName + " Found " + clean + " clean items, cleaned " + |
1611 | cleaned + " items in " + name ); |
1612 | } |
1613 | } |
1614 | |
1615 | needService = true; |
1616 | return Serviceable.REQUEUE; // return is actually ignored. |
1617 | } // end of performWork |
1618 | |
1619 | private int getItemSize( CachedItem item) |
1620 | { |
1621 | if( ! useByteCount) |
1622 | return 1; |
1623 | SizedCacheable entry = (SizedCacheable) item.getEntry(); |
1624 | if( null == entry) |
1625 | return 0; |
1626 | return entry.getSize(); |
1627 | } // end of getItemSize |
1628 | |
1629 | /** |
1630 | Return statistics about cache that may be implemented. |
1631 | **/ |
1632 | public synchronized long[] getCacheStats() |
1633 | { |
1634 | stat.currentSize = getCurrentSize(); |
1635 | return stat.getStats(); |
1636 | } |
1637 | |
1638 | /** |
1639 | Reset the statistics to 0. |
1640 | **/ |
1641 | public void resetCacheStats() |
1642 | { |
1643 | stat.reset(); |
1644 | } |
1645 | |
1646 | /** |
1647 | * @return the current maximum size of the cache. |
1648 | */ |
1649 | public synchronized long getMaximumSize() |
1650 | { |
1651 | return maximumSize; |
1652 | } |
1653 | |
1654 | /** |
1655 | * Change the maximum size of the cache. If the size is decreased then cache entries |
1656 | * will be thrown out. |
1657 | * |
1658 | * @param newSize the new maximum cache size |
1659 | * |
1660 | * @exception StandardException Cloudscape Standard error policy |
1661 | */ |
1662 | public void resize( long newSize) throws StandardException |
1663 | { |
1664 | boolean shrink; |
1665 | |
1666 | synchronized( this) |
1667 | { |
1668 | maximumSize = newSize; |
1669 | stat.maxSize = maximumSize; |
1670 | shrink = ( shrinkSize( getCurrentSize()) > 0); |
1671 | } |
1672 | if( shrink) |
1673 | { |
1674 | performWork(true /* shrink only */); |
1675 | /* performWork does not remove recently used entries nor does it mark them as |
1676 | * not recently used. Therefore if the cache has not shrunk enough we will call rotateClock |
1677 | * to free up some entries. |
1678 | */ |
1679 | if( shrinkSize( getCurrentSize()) > 0) |
1680 | { |
1681 | CachedItem freeItem = rotateClock( (float) 2.0); |
1682 | /* rotateClock(2.0) means that the clock will rotate through the cache as much as |
1683 | * twice. If it does not find sufficient unused items the first time through it |
1684 | * will almost certainly find enough of them the second time through, because it |
1685 | * marked all the items as not recently used in the first pass. |
1686 | * |
1687 | * If the cache is very heavily used by other threads then a lot of the items marked as |
1688 | * unused in the first pass may be used before rotateClock passes over them again. In this |
1689 | * unlikely case rotateClock( 2.0) may not be able to clear out enough space to bring the |
1690 | * current size down to the maximum. However the cache size should come down as rotateClock |
1691 | * is called in the normal course of operation. |
1692 | */ |
1693 | if( freeItem != null) |
1694 | freeItem.unkeepForCreate(); |
1695 | } |
1696 | } |
1697 | |
1698 | } // end of resize; |
1699 | |
1700 | private synchronized long getCurrentSize() |
1701 | { |
1702 | if( ! useByteCount) |
1703 | return holders.size(); |
1704 | return currentByteCount + holders.size()*ITEM_OVERHEAD; |
1705 | } |
1706 | |
1707 | /** |
1708 | * Perform an operation on (approximately) all entries that matches the filter, |
1709 | * or all entries if the filter is null. Entries that are added while the |
1710 | * cache is being scanned might or might not be missed. |
1711 | * |
1712 | * @param filter |
1713 | * @param operator |
1714 | */ |
1715 | public void scan( Matchable filter, Operator operator) |
1716 | { |
1717 | int itemCount = 1; |
1718 | Cacheable entry = null; |
1719 | CachedItem item = null; |
1720 | |
1721 | // Do not call the operator while holding the synchronization lock. |
1722 | // However we cannot access an item's links without holding the synchronization lock, |
1723 | // nor can we assume that an item is still in the cache unless we hold the synchronization |
1724 | // lock or the item is marked as kept. |
1725 | for( int position = 0;; position++) |
1726 | { |
1727 | synchronized( this) |
1728 | { |
1729 | if( null != item) |
1730 | { |
1731 | release( item); |
1732 | item = null; |
1733 | } |
1734 | |
1735 | for( ; position < holders.size(); position++) |
1736 | { |
1737 | item = (CachedItem) holders.get( position); |
1738 | if( null != item) |
1739 | { |
1740 | try |
1741 | { |
1742 | entry = item.use(); |
1743 | } |
1744 | catch( StandardException se) |
1745 | { |
1746 | continue; |
1747 | } |
1748 | |
1749 | if( null != entry && (null == filter || filter.match( entry))) |
1750 | { |
1751 | item.keepForClean(); |
1752 | break; |
1753 | } |
1754 | } |
1755 | } |
1756 | if( position >= holders.size()) |
1757 | return; |
1758 | |
1759 | } // end of synchronization |
1760 | operator.operate( entry); |
1761 | // Do not release the item until we have re-acquired the synchronization lock. |
1762 | // Otherwise the item may be removed and its next link invalidated. |
1763 | } |
1764 | } // end of scan |
1765 | |
1766 | private int trimRequests = 0; |
1767 | |
1768 | /* Trim out invalid items from holders if there are a lot of them. This is expensive if |
1769 | * holders is large. |
1770 | * The caller must hold the cache synchronization lock. |
1771 | */ |
1772 | private void trimToSize() |
1773 | { |
1774 | int size = holders.size(); |
1775 | |
1776 | // Trimming is expensive, don't do it often. |
1777 | trimRequests++; |
1778 | if( trimRequests < size/8) |
1779 | return; |
1780 | trimRequests = 0; |
1781 | |
1782 | // move invalid items to the end. |
1783 | int endPosition = size - 1; |
1784 | |
1785 | int invalidCount = 0; |
1786 | for (int i = 0; i <= endPosition; i++) |
1787 | { |
1788 | CachedItem item = (CachedItem) holders.get(i); |
1789 | |
1790 | if (item.isKept()) |
1791 | continue; |
1792 | |
1793 | if (item.isValid()) |
1794 | continue; |
1795 | |
1796 | invalidCount++; |
1797 | |
1798 | // swap with an item later in the list |
1799 | // try to keep free items at the end of the holders array. |
1800 | for (; endPosition > i; endPosition--) { |
1801 | CachedItem last = (CachedItem) holders.get(endPosition); |
1802 | if (last.isValid()) { |
1803 | holders.set(i, last); |
1804 | holders.set(endPosition, item); |
1805 | endPosition--; |
1806 | break; |
1807 | } |
1808 | } |
1809 | } |
1810 | // small cache - don't shrink. |
1811 | if (size < 32) |
1812 | return; |
1813 | |
1814 | // now decide if we need to shrink the holder array or not. |
1815 | int validItems = size - invalidCount; |
1816 | |
1817 | // over 75% entries used, don't shrink. |
1818 | if (validItems > ((3 * size) / 4)) |
1819 | return; |
1820 | |
1821 | // keep 10% new items. |
1822 | int newSize = validItems + (validItems / 10); |
1823 | |
1824 | if (newSize >= size) |
1825 | return; |
1826 | |
1827 | // remove items, starting at the end, where |
1828 | // hopefully most of the free items are. |
1829 | for (int r = size - 1; r > newSize; r--) { |
1830 | CachedItem remove = (CachedItem) holders.get(r); |
1831 | if (remove.isKept() || remove.isValid()) { |
1832 | continue; |
1833 | } |
1834 | |
1835 | if (useByteCount) { |
1836 | currentByteCount -= getItemSize(remove); |
1837 | } |
1838 | |
1839 | holders.remove(r); |
1840 | } |
1841 | |
1842 | holders.trimToSize(); |
1843 | // move the clock hand to the start of the invalid items. |
1844 | clockHand = validItems + 1; |
1845 | |
1846 | } // end of trimToSize |
1847 | } |