0001: /*
0002:
0003: Derby - Class org.apache.derby.impl.services.cache.Clock
0004:
0005: Licensed to the Apache Software Foundation (ASF) under one or more
0006: contributor license agreements. See the NOTICE file distributed with
0007: this work for additional information regarding copyright ownership.
0008: The ASF licenses this file to you under the Apache License, Version 2.0
0009: (the "License"); you may not use this file except in compliance with
0010: the License. You may obtain a copy of the License at
0011:
0012: http://www.apache.org/licenses/LICENSE-2.0
0013:
0014: Unless required by applicable law or agreed to in writing, software
0015: distributed under the License is distributed on an "AS IS" BASIS,
0016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0017: See the License for the specific language governing permissions and
0018: limitations under the License.
0019:
0020: */
0021:
0022: package org.apache.derby.impl.services.cache;
0023:
0024: import org.apache.derby.iapi.services.cache.CacheManager;
0025: import org.apache.derby.iapi.services.cache.Cacheable;
0026: import org.apache.derby.iapi.services.cache.CacheableFactory;
0027: import org.apache.derby.iapi.services.cache.SizedCacheable;
0028: import org.apache.derby.iapi.services.context.ContextManager;
0029: import org.apache.derby.iapi.services.daemon.DaemonService;
0030: import org.apache.derby.iapi.services.daemon.Serviceable;
0031:
0032: import org.apache.derby.iapi.error.StandardException;
0033:
0034: import org.apache.derby.iapi.services.monitor.Monitor;
0035:
0036: import org.apache.derby.iapi.services.sanity.SanityManager;
0037:
0038: import org.apache.derby.iapi.services.cache.ClassSize;
0039: import org.apache.derby.iapi.util.Matchable;
0040: import org.apache.derby.iapi.util.Operator;
0041: import org.apache.derby.iapi.reference.SQLState;
0042:
0043: import java.util.ArrayList;
0044: import java.util.Hashtable;
0045: import java.util.Properties;
0046:
0047: /**
0048: A cache manager that uses a Hashtable and a ArrayList. The ArrayList holds
0049: CachedItem objects, each with a holder object. The Hashtable is keyed
0050: by the identity of the holder object (Cacheable.getIdentity()) and
0051: the data portion is a pointer to the CachedItem. CachedItems that have
0052: holder objects with no identity do not have entries in the hashtable.
0053: <P>
0054: CachedItems can in various state.
0055: <UL>
0056: <LI>isValid - the entry has a valid identity
0057: <LI>inCreate - the entry is being created or being faulted in from persistent store
0058: <LI>inClean - the entry is being written out to persistent store
0059: <LI>isKept - the entry is currently being looked at/updated, do not remove or
0060: clean it.
0061: </OL>
0062:
0063: <P>Multithreading considerations:<BR>
0064: A clock cache manager must be MT-safe.
0065: All member variables are accessed single threaded (synchronized on this) or
0066: set once or readonly. Assumptions: holders size() and addElement must be
0067: synchronized.
0068: <BR>
0069: CachedItem is never passed out of the clock cache manager, only the
0070: Cacheable object is. The cachedItem is responsible for the setting and
0071: clearing of its own member fields (RESOLVE: now they are done in cache
0072: manager, need to be moved to the cachedItem). The cache manager will
0073: following the following rules while accessing a cacheditem:
0074: <UL>
0075: <LI>invalid item is never returned from the cache
0076: <LI>setValidState and isValid() is only called single threaded through the cache manager.
0077: <LI>keep() and isKept() is only called single threaded through the cache
0078: manager once the item has been added to the holders array
0079: <LI>item that isKept() won't be cleaned or removed or invalidated from the cache.
0080: <LI>item that is inClean() or inCreate(), the cache manager
0081: will wait on the cachedItem to finish cleaning or creating before it
0082: returns the cached item outside of the cache.
0083: </UL>
0084: <BR>
0085: The cacheable must be cleaned thru the cache if it is managed by a cache.
0086: On CacheItem, a inClean state is maintain to stablelize the content of the
0087: cacheable while it is being cleaned. Only unkept items are cleaned. If an
0088: item is found to be inClean, it will wait until it exits the inClean state.
0089: If a cached item calls it own clean method without notifying the cache, it
0090: has to stablize its content for the duration of the clean.
0091: <BR>
0092: It is assumed that the cacheable object maintain its own MT-safeness.<BR>
0093:
0094: @see CachedItem
0095: @see Cacheable
0096:
0097: */
0098:
0099: final class Clock extends Hashtable implements CacheManager,
0100: Serviceable {
0101:
0102: /*
0103: ** Fields
0104: */
0105: public final CacheStat stat;
0106: private DaemonService cleaner; // the background worker thread who is going to
0107: // do pre-flush for this cache.
0108: private final ArrayList holders;
0109: private int validItemCount = 0;
0110: private long maximumSize;
0111: private boolean useByteCount; // regulate the total byte count or the entry count
0112: private long currentByteCount = 0;
0113: /* currentByteCount should be the sum of entry.getSize() for all entries in the cache.
0114: * That is, it should be the sum of getItemSize( item, false) for each item in the holders
0115: * vector.
0116: */
0117:
0118: private static final int ITEM_OVERHEAD = ClassSize
0119: .estimateBaseFromCatalog(CachedItem.class)
0120: + ClassSize.getRefSize() // one ref per item in the holder ArrayList
0121: + ClassSize.estimateHashEntrySize();
0122:
0123: private final CacheableFactory holderFactory;
0124:
0125: private boolean active; // true if active for find/create
0126: private String name; // name of the cache, mainly for debugging purposes.
0127: private int clockHand; // the sweep of the clock hand
0128:
0129: private int myClientNumber; // use this number to talk to cleaner service
0130: private boolean wokenToClean; // true if the client was woken to clean, false if to shrink
0131: private boolean cleanerRunning;
0132: private boolean needService;
0133:
0134: /**
0135: Construct a new clock cache manager.
0136:
0137: <P>MT - not needed for constructor.
0138:
0139: @param holderFactory the cacheable object class
0140: @param name the name of the cache
0141: @param initialSize the initial number of cachable object this cache
0142: holds.
0143: @param maximumSize the maximum size of the cache. The cache may grow
0144: from initialSize to maximumSize if the cache policy notices that there
0145: is not enough free buffers availiable. Once the cache hits maximumSize
0146: it will not grow. If the cache is full, an exception will be thrown
0147:
0148: */
0149: Clock(CacheableFactory holderFactory, String name, int initialSize,
0150: long maximumSize, boolean useByteCount) {
0151: super (initialSize, (float) 0.95);
0152: this .maximumSize = maximumSize;
0153: this .holderFactory = holderFactory;
0154: this .useByteCount = useByteCount;
0155:
0156: if (SanityManager.DEBUG) {
0157: if (SanityManager.DEBUG_ON(ClockFactory.CacheTrace)) {
0158: SanityManager.DEBUG(ClockFactory.CacheTrace,
0159: "initializing " + name + " cache to size "
0160: + initialSize);
0161: }
0162: }
0163:
0164: //int delta = initialSize / 2;
0165: //if (delta < 5)
0166: // delta = 5;
0167:
0168: holders = new ArrayList(initialSize);
0169: this .name = name;
0170: active = true;
0171:
0172: this .stat = new CacheStat();
0173: stat.initialSize = initialSize;
0174: stat.maxSize = maximumSize;
0175: }
0176:
0177: /**
0178: Find the object or materialize one in the cache. If it has not been
0179: created in the persistent store yet, return null.
0180:
0181: <P>MT - must be MT-safe. The cache is single threaded through finding
0182: the item in cache and finding a free item if it is not in cache, thus
0183: preventing another thread from creating the same item while is is being
0184: faulted in. (RESOLVE - this is really low performance if the cache
0185: cleaner cannot keep a steady supply of free items and we have to do an
0186: I/O while blocking the cache). If it needs to be faulted in, the
0187: inCreate bit is set. The item is kept before it exits the sync block.
0188: <BR>
0189: If the item is in cache but in the middle of being faulted in or
0190: cleaned, it needs to wait until this is done being before returning.
0191: <BR>
0192: The keep status prevents other threads from removing this item.
0193: The inCreate status prevents other threads from looking at or writing
0194: out this item while it is being faulted in.
0195: (RESOLVE: need to handle the case where the object is marked for
0196: removal and being waited on)
0197:
0198: @param key the key to the object
0199: @return a cacheable object that is kept in the cache.
0200: @exception StandardException Cloudscape Standard error policy
0201: */
0202: public Cacheable find(Object key) throws StandardException {
0203: CachedItem item;
0204: boolean add;
0205:
0206: /*
0207: ** We will only loop if someone else tried to add the
0208: ** same key as we did and they failed. In this case
0209: ** we start all over. An example of this would be an
0210: ** attempt to cache an object that failed due to a
0211: ** transient error (e.g. deadlock), which should not
0212: ** prevent another thread from trying to add the
0213: ** key to the cache (e.g. it might be the one holding
0214: ** the lock that caused the other thread to deadlock).
0215: */
0216: while (true) {
0217: add = false;
0218:
0219: synchronized (this ) {
0220:
0221: if (!active)
0222: return null;
0223:
0224: item = (CachedItem) get(key);
0225:
0226: if (item != null) {
0227: item.keepAfterSearch();
0228:
0229: stat.findHit++;
0230:
0231: if (SanityManager.DEBUG) {
0232: if (SanityManager
0233: .DEBUG_ON(ClockFactory.CacheTrace)) {
0234: SanityManager
0235: .DEBUG(
0236: ClockFactory.CacheTrace,
0237: name
0238: + ": Found key "
0239: + key
0240: + " already in cache, item "
0241: + item);
0242: }
0243: }
0244: }
0245: }
0246:
0247: // no entry was found, need to add one
0248: if (item == null) {
0249:
0250: // get a free item
0251: item = findFreeItem();
0252:
0253: stat.findMiss++;
0254:
0255: if (SanityManager.DEBUG) {
0256: if (SanityManager.DEBUG_ON(ClockFactory.CacheTrace)) {
0257: SanityManager
0258: .DEBUG(
0259: ClockFactory.CacheTrace,
0260: name
0261: + ": Find key "
0262: + key
0263: + " Not in cache, get free item "
0264: + item);
0265: }
0266: }
0267:
0268: if (SanityManager.DEBUG)
0269: SanityManager.ASSERT(item != null,
0270: "found null item");
0271:
0272: synchronized (this ) {
0273: CachedItem inCacheItem = (CachedItem) get(key);
0274:
0275: if (inCacheItem != null) {
0276: // some-one beat us to adding an item into the cache,
0277: // just use that one
0278: item.unkeepForCreate();
0279:
0280: item = inCacheItem;
0281: item.keepAfterSearch();
0282: } else {
0283: // yes, we really are the ones to add it
0284: put(key, item);
0285: add = true;
0286: if (SanityManager.DEBUG) {
0287:
0288: if (SanityManager
0289: .DEBUG_ON("memoryLeakTrace")) {
0290:
0291: if (size() > ((11 * maximumSize) / 10))
0292: System.out
0293: .println("memoryLeakTrace:Cache:"
0294: + name
0295: + " "
0296: + size());
0297: }
0298: }
0299: }
0300: }
0301: }
0302:
0303: if (add) {
0304:
0305: if (SanityManager.DEBUG) {
0306: if (SanityManager.DEBUG_ON(ClockFactory.CacheTrace)) {
0307: SanityManager.DEBUG(ClockFactory.CacheTrace,
0308: name + " Added " + key
0309: + " to cache, item " + item);
0310: }
0311: }
0312:
0313: stat.findFault++;
0314:
0315: return addEntry(item, key, false, (Object) null);
0316: }
0317:
0318: Cacheable entry = item.use();
0319: if (entry == null) {
0320: // item was not added by the other user successfully ...
0321: synchronized (this ) {
0322: item.unkeep();
0323: }
0324:
0325: // try to hash the key again (see
0326: // comment at head of loop)
0327: continue;
0328: }
0329:
0330: return entry;
0331: }
0332: }
0333:
0334: /**
0335: Find an object in the cache. Do not fault in or create the object if
0336: is is not found in the cache.
0337:
0338: <P>MT - must be MT-safe. The cache is single threaded through finding
0339: the item in cache. If it needs to wait for it to be faulted in or
0340: cleaned it is synchronized/waited on the cached item itself.
0341:
0342: @param key the key to the object
0343: @return a cacheable object that is kept in the cache.
0344: */
0345:
0346: public Cacheable findCached(Object key) throws StandardException {
0347:
0348: CachedItem item;
0349:
0350: synchronized (this ) {
0351:
0352: if (!active)
0353: return null;
0354:
0355: item = (CachedItem) get(key);
0356:
0357: if (item == null) {
0358: stat.findCachedMiss++;
0359: return null;
0360: } else
0361: stat.findCachedHit++;
0362:
0363: item.keepAfterSearch();
0364: }
0365:
0366: Cacheable entry = item.use();
0367: if (entry == null) {
0368: // item was not added by the other user successfully ...
0369: synchronized (this ) {
0370: item.unkeep();
0371: }
0372: }
0373:
0374: return entry;
0375: }
0376:
0377: /**
0378: * Mark a set of entries as having been used. Normally this is done as a side effect
0379: * of find() or findCached. Entries that are no longer in the cache are ignored.
0380: *
0381: * @param keys the key of the used entry.
0382: */
0383: public void setUsed(Object[] keys) {
0384: CachedItem item;
0385:
0386: for (int i = 0; i < keys.length;) {
0387: // Do not hold the synchronization lock for too long.
0388: synchronized (this ) {
0389: if (!active)
0390: return;
0391:
0392: int endIdx = i + 32;
0393: if (endIdx > keys.length)
0394: endIdx = keys.length;
0395: for (; i < endIdx; i++) {
0396: if (keys[i] == null)
0397: return;
0398:
0399: item = (CachedItem) get(keys[i]);
0400: if (null != item)
0401: item.setUsed(true);
0402: }
0403: }
0404: }
0405: } // end of setUsed
0406:
0407: /**
0408: Create a new object with the said key.
0409:
0410: <P>MT - must be MT-safe. Single thread thru verifying no such item
0411: exist in cache and finding a free item, keep the item and set inCreate
0412: state. The actual creating of the object is done outside
0413: the sync block and is protected by the isKept and inCreate bits.
0414:
0415: @param key the key to the object
0416: @return a cacheable object that is kept in the cache.
0417:
0418: @exception StandardException Cloudscape Standard error policy
0419: */
0420: public Cacheable create(Object key, Object createParameter)
0421: throws StandardException {
0422:
0423: // assume the item is not already in the cache
0424: CachedItem item = findFreeItem();
0425:
0426: stat.create++;
0427:
0428: synchronized (this ) {
0429:
0430: if (!active)
0431: return null;
0432:
0433: if (get(key) != null) {
0434:
0435: item.unkeepForCreate();
0436:
0437: throw StandardException
0438: .newException(SQLState.OBJECT_EXISTS_IN_CACHE,
0439: this .name, key);
0440: }
0441:
0442: put(key, item);
0443:
0444: if (SanityManager.DEBUG) {
0445:
0446: if (SanityManager.DEBUG_ON("memoryLeakTrace")) {
0447:
0448: if (size() > ((11 * maximumSize) / 10))
0449: System.out.println("memoryLeakTrace:Cache:"
0450: + name + " " + size());
0451: }
0452: }
0453: }
0454:
0455: Cacheable entry = addEntry(item, key, true, createParameter);
0456:
0457: if (SanityManager.DEBUG) {
0458: if (entry != null)
0459: SanityManager.ASSERT(item.getEntry() == entry);
0460: }
0461:
0462: return entry;
0463: }
0464:
0465: /**
0466: The caller is no longer looking at or updating the entry. Since there
0467: could be more than one piece of code looking at this entry, release
0468: does not mean nobody is looking at or updating the entry, just one
0469: less. If the cacheable is marked for remove (someone is waiting to
0470: remove the persistent object once nobody is looking at it), then notify
0471: the waiter if this is the last one looking at it.
0472: <BR>
0473: Unless there is a good reason to do otherwise, release should be used
0474: to release a cachable and not directly call cachedItem unkeep, since
0475: unkeep does not handle the case of remove.
0476:
0477:
0478: <P>MT - must be MT-safe. Getting and deleteing item from the hashtable
0479: is in the same synchronized block. If the cacheable object is waiting
0480: to be removed, that is synchronized thru the cachedItem itself
0481: (RESOLVE: need to move this sync block to cachedItem instead)
0482:
0483: @param entry the cached entry
0484:
0485: */
0486: public void release(Cacheable entry) {
0487: boolean removeItem;
0488: CachedItem item;
0489: long toShrink = 0;
0490:
0491: synchronized (this ) {
0492:
0493: item = (CachedItem) get(entry.getIdentity());
0494:
0495: if (SanityManager.DEBUG) {
0496: SanityManager.ASSERT(item != null, "item null");
0497: SanityManager.ASSERT(item.getEntry() == entry,
0498: "entry not equals keyed entry");
0499: SanityManager.ASSERT(item.isKept(),
0500: "item is not kept in release(Cachable)");
0501: }
0502:
0503: removeItem = item.unkeep();
0504:
0505: if (removeItem) {
0506:
0507: remove(entry.getIdentity());
0508:
0509: // we keep the item here to stop another thread trying to evict it
0510: // while we are destroying it.
0511: item.keepForClean();
0512: }
0513:
0514: if (cleaner == null) {
0515: // try to shrink the cache on a release
0516: toShrink = shrinkSize(getCurrentSize());
0517: }
0518: }
0519:
0520: if (removeItem) {
0521:
0522: item.notifyRemover();
0523: }
0524:
0525: if (toShrink > 0)
0526: performWork(true /* shrink only */);
0527: }
0528:
0529: protected void release(CachedItem item) {
0530:
0531: boolean removeItem;
0532:
0533: synchronized (this ) {
0534:
0535: if (SanityManager.DEBUG) {
0536: SanityManager.ASSERT(item.isKept(),
0537: "item is not kept in released(CachedItem)");
0538: }
0539:
0540: removeItem = item.unkeep();
0541:
0542: if (removeItem) {
0543:
0544: remove(item.getEntry().getIdentity());
0545:
0546: // we keep the item here to stop another thread trying to evict it
0547: // while we are destroying it.
0548: item.keepForClean();
0549: }
0550: }
0551:
0552: if (removeItem) {
0553:
0554: item.notifyRemover();
0555: }
0556: }
0557:
0558: /**
0559: Remove an object from the cache. The item will be placed into the NoIdentity
0560: state through clean() (if required) and clearIdentity(). The removal of the
0561: object will be delayed until it is not kept by anyone.
0562:
0563: After this call the caller must throw away the reference to item.
0564:
0565: <P>MT - must be MT-safe. Single thread thru finding and setting the
0566: remove state of the item, the actual removal of the cacheable is
0567: synchronized on the cachedItem itself.
0568:
0569: @exception StandardException Standard Cloudscape error policy.
0570: */
0571: public void remove(Cacheable entry) throws StandardException {
0572:
0573: boolean removeNow;
0574: CachedItem item;
0575: long origItemSize = 0;
0576:
0577: stat.remove++;
0578:
0579: synchronized (this ) {
0580:
0581: item = (CachedItem) get(entry.getIdentity());
0582:
0583: if (SanityManager.DEBUG) {
0584: SanityManager.ASSERT(item != null);
0585: SanityManager.ASSERT(item.getEntry() == entry);
0586: SanityManager.ASSERT(item.isKept());
0587: }
0588: if (useByteCount)
0589: origItemSize = getItemSize(item);
0590:
0591: item.setRemoveState();
0592: removeNow = item.unkeep();
0593:
0594: if (removeNow) {
0595: remove(entry.getIdentity());
0596: item.keepForClean();
0597: }
0598: }
0599:
0600: try {
0601: // if removeNow is false then this thread may sleep
0602: item.remove(removeNow);
0603:
0604: } finally {
0605:
0606: synchronized (this ) {
0607: // in the case where this thread didn't call keepForClean() the thread
0608: // that woke us would have called keepForClean.
0609: item.unkeep();
0610: item.setValidState(false);
0611: validItemCount--;
0612: item.getEntry().clearIdentity();
0613: if (useByteCount)
0614: currentByteCount += getItemSize(item)
0615: - origItemSize;
0616: }
0617: }
0618:
0619: }
0620:
0621: /**
0622: Clean all objects in the cache.
0623: */
0624: public void cleanAll() throws StandardException {
0625: stat.cleanAll++;
0626: cleanCache((Matchable) null);
0627: }
0628:
0629: /**
0630: Clean all objects that match a partial key.
0631: */
0632: public void clean(Matchable partialKey) throws StandardException {
0633:
0634: cleanCache(partialKey);
0635: }
0636:
0637: /**
0638: Age as many objects as possible out of the cache.
0639:
0640: <BR>MT - thread safe
0641:
0642: @see CacheManager#ageOut
0643: */
0644: public void ageOut() {
0645:
0646: stat.ageOut++;
0647: synchronized (this ) {
0648:
0649: int size = holders.size();
0650: long toShrink = shrinkSize(getCurrentSize());
0651: boolean shrunk = false;
0652:
0653: for (int position = 0; position < size; position++) {
0654: CachedItem item = (CachedItem) holders.get(position);
0655:
0656: if (item.isKept())
0657: continue;
0658: if (!item.isValid())
0659: continue;
0660:
0661: if (item.getEntry().isDirty()) {
0662: continue;
0663: }
0664:
0665: long itemSize = removeIdentity(item);
0666:
0667: if (toShrink > 0) {
0668:
0669: if (SanityManager.DEBUG) {
0670: if (SanityManager
0671: .DEBUG_ON(ClockFactory.CacheTrace)) {
0672: SanityManager.DEBUG(
0673: ClockFactory.CacheTrace, name
0674: + " shrinking item " + item
0675: + " at position "
0676: + position);
0677: }
0678: }
0679:
0680: toShrink -= itemSize;
0681: shrunk = true;
0682: }
0683:
0684: } // end of for loop
0685:
0686: if (shrunk)
0687: trimToSize();
0688:
0689: } // out of sync block
0690: } // end of ageOut
0691:
0692: /**
0693: MT - synchronization provided by caller
0694:
0695: @exception StandardException Standard Cloudscape error policy.
0696: */
0697: public void shutdown() throws StandardException {
0698:
0699: if (cleaner != null) {
0700: cleaner.unsubscribe(myClientNumber);
0701: cleaner = null;
0702: }
0703:
0704: synchronized (this ) {
0705: active = false;
0706: }
0707:
0708: ageOut();
0709: cleanAll();
0710: ageOut();
0711: }
0712:
0713: /**
0714: MT - synchronization provided by caller
0715:
0716: can use this Daemomn service if needed
0717: */
0718: public void useDaemonService(DaemonService daemon) {
0719: // if we were using another cleaner, unsubscribe first
0720: if (cleaner != null)
0721: cleaner.unsubscribe(myClientNumber);
0722:
0723: cleaner = daemon;
0724: myClientNumber = cleaner
0725: .subscribe(this , true /* onDemandOnly */);
0726: }
0727:
0728: /**
0729: Discard all objects that match the partial key.
0730:
0731: <BR>MT - thread safe
0732: */
0733: public boolean discard(Matchable partialKey) {
0734:
0735: // we miss something because it was kept
0736: boolean noMisses = true;
0737:
0738: synchronized (this ) {
0739:
0740: int size = holders.size();
0741: long toShrink = shrinkSize(getCurrentSize());
0742: boolean shrunk = false;
0743:
0744: for (int position = 0; position < size; position++) {
0745: CachedItem item = (CachedItem) holders.get(position);
0746:
0747: if (!item.isValid())
0748: continue;
0749:
0750: Object key = item.getEntry().getIdentity();
0751:
0752: if (partialKey != null && !partialKey.match(key))
0753: continue;
0754:
0755: if (item.isKept()) {
0756: noMisses = false;
0757: continue;
0758: }
0759:
0760: long itemSize = removeIdentity(item);
0761:
0762: if (toShrink > 0) {
0763:
0764: if (SanityManager.DEBUG) {
0765: if (SanityManager
0766: .DEBUG_ON(ClockFactory.CacheTrace)) {
0767: SanityManager.DEBUG(
0768: ClockFactory.CacheTrace, name
0769: + " shrinking item " + item
0770: + " at position "
0771: + position);
0772: }
0773: }
0774:
0775: // and we shrunk one item
0776: toShrink -= itemSize;
0777: shrunk = true;
0778: }
0779: }
0780:
0781: if (shrunk)
0782: trimToSize();
0783: }
0784:
0785: return noMisses;
0786: }
0787:
0788: /**
0789: Add a new CachedItem and a holder object to the cache. The holder object
0790: is returned kept.
0791:
0792: <P>MT - need to be MT-safe. The insertion of the key into the hash
0793: table is synchronized on this.
0794:
0795: */
0796: private Cacheable addEntry(CachedItem item, Object key,
0797: boolean forCreate, Object createParameter)
0798: throws StandardException {
0799:
0800: Cacheable entry = null;
0801: long origEntrySize = 0;
0802: if (useByteCount)
0803: origEntrySize = getItemSize(item);
0804:
0805: try {
0806: if (SanityManager.DEBUG) {
0807: if (SanityManager.DEBUG_ON(ClockFactory.CacheTrace)) {
0808: SanityManager.DEBUG(ClockFactory.CacheTrace, name
0809: + " item " + item + " take on identity "
0810: + key);
0811: }
0812: }
0813:
0814: // tell the object it needs to create itself
0815: entry = item.takeOnIdentity(this , holderFactory, key,
0816: forCreate, createParameter);
0817: } finally {
0818: boolean notifyWaiters;
0819: synchronized (this ) {
0820:
0821: Object removed = remove(key);
0822: if (SanityManager.DEBUG) {
0823: SanityManager.ASSERT(removed == item);
0824: }
0825:
0826: if (entry != null) {
0827: // put the actual key into the hash table, not the one that was passed in
0828: // for the find or create. This is because the caller may re-use the key
0829: // for another cache operation, which would corrupt our hashtable
0830: put(entry.getIdentity(), item);
0831: if (useByteCount)
0832: currentByteCount += ((SizedCacheable) entry)
0833: .getSize()
0834: - origEntrySize;
0835: item.setValidState(true);
0836: validItemCount++;
0837: notifyWaiters = true;
0838: } else {
0839: item.unkeep();
0840: notifyWaiters = item.isKept();
0841: }
0842: }
0843:
0844: // whatever the outcome, we have to notify waiters ...
0845: if (notifyWaiters)
0846: item.settingIdentityComplete();
0847: }
0848:
0849: return entry;
0850: }
0851:
0852: protected CachedItem findFreeItem() throws StandardException {
0853:
0854: // Need to avoid thrashing the cache when we start out
0855: // so if the cache is smaller than its maximum size
0856: // then that's a good indication we should grow.
0857:
0858: long currentSize = getCurrentSize();
0859:
0860: if (currentSize >= maximumSize) {
0861: // look at 20%
0862: CachedItem item = rotateClock(0.2f);
0863: if (item != null)
0864: return item;
0865: }
0866:
0867: // However, if the cache contains a large number of invalid
0868: // items then we should see if we can avoid growing.
0869: // This avoids simple use of Cloudscape looking like
0870: // a memory leak, as the page cache fills the holders array
0871: // with page objects including the 4k (or 32k) pages.
0872: // size() is the number of valid entries in the hash table
0873:
0874: // no need to sync on getting the sizes since if they are
0875: // wrong we will discover it in the loop.
0876: if (validItemCount < holders.size()) {
0877:
0878: synchronized (this ) {
0879:
0880: // 1) find out how many invalid items there are in the
0881: // cache
0882: // 2) search for a free invalid item
0883: // 3) stop searching when there are no more invalid
0884: // items to find
0885:
0886: int invalidItems = holders.size() - validItemCount;
0887:
0888: // Invalid items might occur in the cache when
0889: // a) a new item is created in growCache(), but it
0890: // is not in use yet, or
0891: // b) an item is deleted (usually when a table is
0892: // dropped)
0893:
0894: // It is critical to break out of the loop as soon as
0895: // possible since we are blocking others trying to
0896: // access the page cache. New items are added to the
0897: // end of the page cache, so the search for invalid
0898: // items should start from the end.
0899:
0900: for (int i = holders.size() - 1; (invalidItems > 0)
0901: && (i >= 0); i--) {
0902: CachedItem item = (CachedItem) holders.get(i);
0903:
0904: if (item.isKept()) {
0905: if (!item.isValid())
0906: invalidItems--;
0907: continue;
0908: }
0909:
0910: // found a free item, just use it
0911: if (!item.isValid()) {
0912: item.keepForCreate();
0913: return item;
0914: }
0915: }
0916: }
0917: }
0918:
0919: return growCache();
0920: }
0921:
0922: /**
0923: Go through the list of holder objects and find a free one.
0924: <P>MT - must be MT-safe. The moving of the clockHand and finding of an
0925: eviction candidate is synchronized. The cleaning of the cachable is
0926: handled by the cacheable itself.
0927: */
0928: protected CachedItem rotateClock(float percentOfClock)
0929: throws StandardException {
0930: // statistics -- only used in debug
0931: int evictions = 0;
0932: int cleaned = 0;
0933: int resetUsed = 0;
0934: int iskept = 0;
0935:
0936: // When we are managing the entry count (useByteCount == false) this method just
0937: // has to find or manufacture an available item (a cache slot). When we are managing
0938: // the total byte count then this method must find both available space and an
0939: // available item.
0940: CachedItem availableItem = null;
0941:
0942: boolean kickCleaner = false;
0943:
0944: try {
0945:
0946: // this can be approximate
0947: int itemCount = holders.size();
0948: int itemsToCheck;
0949: if (itemCount < 20)
0950: itemsToCheck = 2 * itemCount;
0951: else
0952: itemsToCheck = (int) (((float) itemCount) * percentOfClock);
0953:
0954: // if we can grow then shrinking is OK too, if we can't grow
0955: // then shrinking the cache won't help us find an item.
0956: long toShrink = shrinkSize(getCurrentSize());
0957:
0958: restartClock: for (; itemsToCheck > 0;) {
0959:
0960: CachedItem item = null;
0961:
0962: synchronized (this ) {
0963:
0964: if (SanityManager.DEBUG) {
0965: if (SanityManager
0966: .DEBUG_ON(ClockFactory.CacheTrace)) {
0967: SanityManager.DEBUG(
0968: ClockFactory.CacheTrace, name
0969: + " rotateClock starting "
0970: + clockHand
0971: + " itemsToCheck "
0972: + itemsToCheck);
0973: }
0974: }
0975:
0976: // size of holders cannot change while in the synchronized block.
0977: int size = holders.size();
0978: for (; itemsToCheck > 0; item = null, itemsToCheck--, incrClockHand()) {
0979: //
0980: // This uses a very simple clock algorithm.
0981: //
0982: // The cache consist of a circular list of cachedItems. Each cached item
0983: // has a 'recentlyUsed' bit which is set every time that item is kept.
0984: // Each clock cache manager keeps a global variable clockHand which
0985: // refers to the item that is most recently replaced.
0986: //
0987: // to find a free item, the clock Hand moves to the next cached Item.
0988: // If it is kept, or in the middle of being created, the clock hand
0989: // moves on.
0990: // If it is recentlyUsed, clear the recently used bit and moves on.
0991: // If it is not recentlyUsed, clean the item and use
0992: //
0993: // If all the cached item is kept, then create a new entry.
0994: // So it is possible, although very unlikely, that, in time, the cache
0995: // will grow beyond the maximum size.
0996:
0997: if (clockHand >= size) {
0998: if (size == 0)
0999: break;
1000: clockHand = 0;
1001: }
1002:
1003: item = (CachedItem) holders.get(clockHand);
1004:
1005: if (item.isKept()) {
1006: if (SanityManager.DEBUG) // stats only in debug mode
1007: iskept++;
1008: continue;
1009: }
1010:
1011: if (!item.isValid()) // found a free item, just use it
1012: {
1013: if (null != availableItem)
1014: // We have found an available item, now we are looking for bytes
1015: continue;
1016: if (SanityManager.DEBUG) {
1017: if (SanityManager
1018: .DEBUG_ON(ClockFactory.CacheTrace)) {
1019: SanityManager
1020: .DEBUG(
1021: ClockFactory.CacheTrace,
1022: name
1023: + " found free item at "
1024: + clockHand
1025: + " item "
1026: + item);
1027: }
1028: }
1029:
1030: item.keepForCreate();
1031: if (useByteCount
1032: && getCurrentSize() > maximumSize) {
1033: availableItem = item;
1034: // now look for bytes.
1035: continue;
1036: }
1037: // since we are using this item, move the clock past it.
1038: incrClockHand();
1039:
1040: return item;
1041: }
1042:
1043: if (item.recentlyUsed()) {
1044:
1045: if (SanityManager.DEBUG) // stats only in debug mode
1046: resetUsed++;
1047: item.setUsed(false);
1048: continue;
1049: }
1050:
1051: if (toShrink > 0) {
1052: if (!cleanerRunning) {
1053:
1054: // try an get the cleaner to shrink the cache
1055: kickCleaner = true;
1056: cleanerRunning = true;
1057: needService = true;
1058: }
1059: }
1060:
1061: // we are seeing valid, not recently used buffers. Evict this.
1062: if (SanityManager.DEBUG) {
1063: evictions++;
1064:
1065: if (SanityManager
1066: .DEBUG_ON(ClockFactory.CacheTrace)) {
1067: SanityManager.DEBUG(
1068: ClockFactory.CacheTrace, name
1069: + " evicting item at "
1070: + clockHand + " item "
1071: + item);
1072: }
1073: }
1074:
1075: if (!item.getEntry().isDirty()) {
1076:
1077: if (SanityManager.DEBUG) {
1078: if (SanityManager
1079: .DEBUG_ON(ClockFactory.CacheTrace)) {
1080: SanityManager.DEBUG(
1081: ClockFactory.CacheTrace,
1082: name + " Evicting Item "
1083: + item
1084: + ", not dirty");
1085: }
1086: }
1087:
1088: // a valid, unkept, clean item, clear its identity
1089: // and use it.
1090: long itemSize = removeIdentity(item);
1091:
1092: if (useByteCount) {
1093: toShrink -= itemSize;
1094: if (getCurrentSize() > maximumSize
1095: && 0 < toShrink) {
1096: if (null == availableItem) {
1097: item.keepForCreate();
1098: availableItem = item;
1099: }
1100: continue;
1101: }
1102: }
1103: // since we are using it move the clock past it
1104: incrClockHand();
1105:
1106: if (null != availableItem)
1107: return availableItem;
1108:
1109: // item is kept but not valid when returned
1110: item.keepForCreate();
1111: return item;
1112: }
1113: // item is valid, unkept, and dirty. clean it.
1114: if ((cleaner != null) && !cleanerRunning) {
1115: kickCleaner = true;
1116: wokenToClean = true;
1117: cleanerRunning = true; // at least it soon will be
1118: }
1119: item.keepForClean();
1120:
1121: // leave the clock hand where it is so that we will pick it
1122: // up if no-one else uses the cache. Other hunters will
1123: // skip over it as it is kept, and thus move the clock
1124: // hand past it.
1125: break;
1126: }
1127: if (item == null) {
1128: return availableItem;
1129: }
1130:
1131: } // out of synchronized block
1132:
1133: // clean the entry outside of a sync block
1134: try {
1135: if (SanityManager.DEBUG) {
1136: if (SanityManager
1137: .DEBUG_ON(ClockFactory.CacheTrace)) {
1138: SanityManager.DEBUG(
1139: ClockFactory.CacheTrace, name
1140: + " cleaning item " + item);
1141: }
1142: }
1143:
1144: item.clean(false);
1145:
1146: if (SanityManager.DEBUG) // stats only in debug mode
1147: {
1148: cleaned++;
1149: }
1150: } finally {
1151: release(item);
1152: item = null;
1153: }
1154:
1155: // at this point the item we cleaned could be in any state
1156: // so we can't just re-use it. Continue searching
1157: continue restartClock;
1158: }
1159: return availableItem;
1160: } finally {
1161:
1162: if (SanityManager.DEBUG) {
1163: // report statistics
1164: if (SanityManager.DEBUG_ON(ClockFactory.CacheTrace))
1165: SanityManager.DEBUG(ClockFactory.CacheTrace, name
1166: + " evictions " + evictions + ", cleaned "
1167: + cleaned + ", resetUsed " + resetUsed
1168: + ", isKept " + iskept + ", size "
1169: + holders.size());
1170: }
1171:
1172: if (kickCleaner && (cleaner != null)) {
1173: if (SanityManager.DEBUG) {
1174: if (SanityManager
1175: .DEBUG_ON(DaemonService.DaemonTrace)) {
1176: SanityManager.DEBUG(DaemonService.DaemonTrace,
1177: name + " client # " + myClientNumber
1178: + " calling cleaner ");
1179: }
1180: }
1181:
1182: cleaner.serviceNow(myClientNumber);
1183:
1184: if (SanityManager.DEBUG) {
1185: if (SanityManager
1186: .DEBUG_ON(DaemonService.DaemonTrace)) {
1187: SanityManager.DEBUG(DaemonService.DaemonTrace,
1188: name + Thread.currentThread().getName()
1189: + " cleaner called");
1190: }
1191: }
1192: }
1193: }
1194: } // end of rotateClock
1195:
1196: /**
1197: Synchronously increment clock hand position
1198: */
1199: private int incrClockHand() {
1200: if (++clockHand >= holders.size())
1201: clockHand = 0;
1202: return clockHand;
1203: }
1204:
1205: /*
1206: * Serviceable methods
1207: */
1208:
1209: public int performWork(ContextManager contextMgr /* ignored */) {
1210:
1211: int ret = performWork(false);
1212: synchronized (this ) {
1213: cleanerRunning = false;
1214: }
1215: return ret;
1216: }
1217:
1218: /**
1219: <P>MT - read only.
1220: */
1221: public boolean serviceASAP() {
1222: return needService;
1223: }
1224:
1225: // @return true, if this work needs to be done on a user thread immediately
1226: public boolean serviceImmediately() {
1227: return false;
1228: }
1229:
1230: public synchronized int getNumberInUse() {
1231:
1232: int size = holders.size();
1233: int inUse = 0;
1234:
1235: for (int position = 0; position < size; position++) {
1236:
1237: CachedItem item = (CachedItem) holders.get(position);
1238:
1239: if (item.isValid()) {
1240: inUse++;
1241: }
1242:
1243: }
1244: return inUse;
1245: }
1246:
1247: /*
1248: public int getNumberKept() {
1249:
1250: synchronized (this) {
1251:
1252: int size = holders.size();
1253: int inUse = 0;
1254:
1255: for (int position = 0; position < size; position++) {
1256:
1257: CachedItem item = (CachedItem) holders.get(position);
1258:
1259: if (item.isValid() && item.isKept()) {
1260: inUse++;
1261: }
1262:
1263: }
1264: return inUse;
1265: }
1266: }
1267: */
1268:
1269: /**
1270: Grow the cache and return a unused, kept item.
1271:
1272: @exception StandardException Thrown if the cache cannot be grown.
1273: */
1274:
1275: private CachedItem growCache() {
1276:
1277: CachedItem item = new CachedItem();
1278: item.keepForCreate();
1279:
1280: // if we run out of memory below here we don't
1281: // know what state the holders could be in
1282: // so don't trap it
1283: synchronized (this ) {
1284: holders.add(item);
1285: // Do not adjust currentByteCount until we put the entry into the CachedItem.
1286: }
1287:
1288: return item;
1289: }
1290:
1291: /**
1292: Clear an item's identity. Item must be
1293: unkept and valid. This is called for
1294: dirty items from the discard code.
1295:
1296: Caller must hold the cache synchronization.
1297:
1298: @return the amount by which this shrinks the cache.
1299: */
1300: protected long removeIdentity(CachedItem item) {
1301:
1302: long shrink = 1;
1303:
1304: if (SanityManager.DEBUG) {
1305: SanityManager.ASSERT(!item.isKept(), "item is kept");
1306: SanityManager.ASSERT(item.isValid(), "item is not valid");
1307:
1308: }
1309:
1310: if (useByteCount)
1311: shrink = ((SizedCacheable) item.getEntry()).getSize();
1312: remove(item.getEntry().getIdentity());
1313: item.setValidState(false);
1314: validItemCount--;
1315: item.getEntry().clearIdentity();
1316: if (useByteCount) {
1317: shrink -= ((SizedCacheable) item.getEntry()).getSize();
1318: currentByteCount -= shrink;
1319: }
1320: return shrink;
1321: }
1322:
1323: /**
1324: Write out all dirty buffers.
1325:
1326: <P>MT - must be MT safe.
1327: Single thread on the part that finds the next dirty buffer to write
1328: out, the synchronization of cleaning of the individual cachable is
1329: provided by the cacheable itself.
1330: */
1331: protected void cleanCache(Matchable partialKey)
1332: throws StandardException {
1333:
1334: int position;
1335:
1336: synchronized (this ) {
1337: // this is at many dirty buffers as the cleaner is ever going to
1338: // see
1339: position = holders.size() - 1;
1340: }
1341:
1342: outerscan: for (;;) {
1343:
1344: CachedItem item = null;
1345:
1346: synchronized (this ) {
1347:
1348: // the cache may have shrunk by quite a bit since we last came
1349: // in here
1350: int size = holders.size();
1351: if (position >= size)
1352: position = size - 1;
1353:
1354: innerscan:
1355: // go from position (the last cached item in the holder array
1356: // to 0 (the first). Otherwise, if we go from 0 to
1357: // position, some other thread may come in and shrink items
1358: // which are between 0 and position. Since a shrink moves all
1359: // items up, we may skip some items without cleaning.
1360: for (; position >= 0; position--, item = null) {
1361:
1362: item = (CachedItem) holders.get(position);
1363:
1364: if (!item.isValid())
1365: continue innerscan;
1366:
1367: if (!item.getEntry().isDirty())
1368: continue innerscan;
1369:
1370: if (partialKey != null) {
1371:
1372: Object key = item.getEntry().getIdentity();
1373:
1374: if (!partialKey.match(key))
1375: continue;
1376: }
1377:
1378: item.keepForClean();
1379: break innerscan;
1380: }
1381: } // end of synchronized block
1382:
1383: if (position < 0) {
1384: return;
1385: }
1386:
1387: try {
1388:
1389: item.clean(false);
1390: } finally {
1391: release(item);
1392: }
1393: position--;
1394:
1395: } // for (;;)
1396: }
1397:
1398: protected long shrinkSize(long currentSize) {
1399:
1400: long maxSize = getMaximumSize();
1401:
1402: long toShrink = currentSize - maxSize;
1403: if (toShrink <= 0)
1404: return 0;
1405:
1406: // only shrink 10% of the maximum size
1407: long shrinkLimit = maxSize / 10;
1408: if (shrinkLimit == 0)
1409: shrinkLimit = 2;
1410:
1411: if (toShrink < shrinkLimit)
1412: return toShrink;
1413: else
1414: return shrinkLimit;
1415: }
1416:
1417: /**
1418: The background cleaner tries to make sure that there are serveral
1419: cleaned or invalied buffers ahead of the clock hand so that when they
1420: are evicted, they don't need to be cleaned.
1421:
1422: The way this routine work is as follows, starting at the current clock
1423: hand position, go forward around the cache buffers, moving the same
1424: route that the clock hand moves. It keep tracks of the number of
1425: invalid or not recently used buffers it sees along the way. If it sees
1426: a not recently used buffer, it will clean it. After it has seen N
1427: invalid or not recently used buffers, or it has gone around and visited
1428: all buffers in the cache, it finished.
1429:
1430: It does not clean recently used buffers.
1431:
1432: <P>MT - must be MT-safe. It takes a snapshot of the current clock hand
1433: position (a synchronous call). Getting and looking at the next
1434: serveral cached item is synchronized on this (RESOLVE: probably doesn't
1435: need to be). Cleaning of the cacheable is handle by the cacheable itself.
1436:
1437: */
1438: protected int performWork(boolean shrinkOnly) {
1439: long target;
1440: long toShrink;
1441: int maxLooks;
1442:
1443: synchronized (this ) {
1444: if (!active) {
1445: needService = false;
1446: return Serviceable.DONE;
1447: } else {
1448: long currentSize = getCurrentSize();
1449: target = currentSize / 20; // attempt to get 5% of the cache clean
1450: toShrink = wokenToClean ? 0 : shrinkSize(currentSize);
1451: }
1452:
1453: if (target == 0) {
1454: wokenToClean = false;
1455: needService = false;
1456: return Serviceable.DONE;
1457: }
1458:
1459: if (!wokenToClean && (toShrink <= 0)) {
1460: needService = false;
1461: return Serviceable.DONE;
1462: }
1463:
1464: maxLooks = useByteCount ? (holders.size() / 10)
1465: : (int) (target * 2);
1466: }
1467:
1468: // try to clean the next N (target) cached item,
1469: long clean = 0;
1470: int cleaned = 0; // only used in debug
1471: CachedItem item = null;
1472: int currentPosition = 0;
1473:
1474: String ThreadName = null;
1475:
1476: if (SanityManager.DEBUG) {
1477: if (SanityManager.DEBUG_ON(DaemonService.DaemonTrace)) {
1478: ThreadName = Thread.currentThread().getName();
1479: SanityManager.DEBUG(DaemonService.DaemonTrace,
1480: ThreadName + " Cleaning " + name
1481: + " clientNumber " + myClientNumber);
1482: }
1483: }
1484:
1485: synchronized (this ) {
1486: int itemCount = holders.size();
1487: currentPosition = clockHand;
1488:
1489: // see if the cache needs to shrink
1490: boolean shrunk = false;
1491: long currentSize = getCurrentSize();
1492:
1493: for (; shrinkOnly ? (currentSize > maximumSize && toShrink > 0)
1494: : (clean < target); item = null) {
1495: if (++currentPosition >= itemCount) {
1496: if (itemCount == 0)
1497: break;
1498:
1499: currentPosition = 0;
1500:
1501: }
1502:
1503: if (maxLooks-- <= 0) {
1504: if (SanityManager.DEBUG) {
1505: if (SanityManager
1506: .DEBUG_ON(DaemonService.DaemonTrace)) {
1507: SanityManager.DEBUG(
1508: DaemonService.DaemonTrace,
1509: ThreadName + " done one round of "
1510: + name);
1511: }
1512: }
1513:
1514: break; // done one round
1515: }
1516:
1517: item = (CachedItem) holders.get(currentPosition);
1518:
1519: if (item.isKept())
1520: continue;
1521:
1522: if (!item.isValid()) {
1523: if (toShrink > 0) {
1524:
1525: if (SanityManager.DEBUG) {
1526: if (SanityManager
1527: .DEBUG_ON(ClockFactory.CacheTrace)) {
1528: SanityManager.DEBUG(
1529: ClockFactory.CacheTrace, name
1530: + " shrinking item "
1531: + item
1532: + " at position "
1533: + currentPosition);
1534: }
1535: }
1536:
1537: toShrink -= currentSize;
1538: holders.remove(currentPosition);
1539: if (useByteCount)
1540: currentByteCount -= getItemSize(item);
1541: currentSize = getCurrentSize();
1542: toShrink += currentSize;
1543: itemCount--;
1544:
1545: // account for the fact all the items have shifted down
1546: currentPosition--;
1547:
1548: shrunk = true;
1549: }
1550: continue;
1551: }
1552:
1553: if (item.recentlyUsed())
1554: continue;
1555:
1556: // found a valid, not kept, and not recently used item
1557: // this item will be cleaned
1558: int itemSize = getItemSize(item);
1559: clean += itemSize;
1560: if (!item.getEntry().isDirty()) {
1561:
1562: if (toShrink > 0) {
1563: if (SanityManager.DEBUG) {
1564: if (SanityManager
1565: .DEBUG_ON(ClockFactory.CacheTrace)) {
1566: SanityManager.DEBUG(
1567: ClockFactory.CacheTrace, name
1568: + " shrinking item "
1569: + item
1570: + " at position "
1571: + currentPosition);
1572: }
1573: }
1574:
1575: toShrink -= currentSize;
1576: removeIdentity(item);
1577: holders.remove(currentPosition);
1578: if (useByteCount)
1579: currentByteCount -= getItemSize(item);
1580: currentSize = getCurrentSize();
1581: toShrink += currentSize;
1582: itemCount--;
1583: shrunk = true;
1584:
1585: // account for the fact all the items have shifted down
1586: currentPosition--;
1587: }
1588: continue;
1589: }
1590:
1591: if (shrinkOnly)
1592: continue;
1593:
1594: // found one that needs cleaning, keep it to clean
1595: item.keepForClean();
1596: break;
1597: } // end of for loop
1598:
1599: if (shrunk)
1600: trimToSize();
1601:
1602: if (item == null) {
1603: wokenToClean = false;
1604: needService = false;
1605: return Serviceable.DONE;
1606: }
1607: } // end of sync block
1608:
1609: try {
1610: if (SanityManager.DEBUG) {
1611: if (SanityManager.DEBUG_ON(DaemonService.DaemonTrace)) {
1612: SanityManager.DEBUG(DaemonService.DaemonTrace,
1613: ThreadName + " cleaning entry in " + name);
1614: }
1615: }
1616:
1617: item.clean(false);
1618: if (SanityManager.DEBUG) // only need stats for debug
1619: cleaned++;
1620:
1621: } catch (StandardException se) {
1622: // RESOLVE - should probably throw the error into the log.
1623: } finally {
1624: release(item);
1625: item = null;
1626: }
1627:
1628: if (SanityManager.DEBUG) {
1629: if (SanityManager.DEBUG_ON(DaemonService.DaemonTrace)) {
1630: SanityManager.DEBUG(DaemonService.DaemonTrace,
1631: ThreadName + " Found " + clean
1632: + " clean items, cleaned " + cleaned
1633: + " items in " + name);
1634: }
1635: }
1636:
1637: needService = true;
1638: return Serviceable.REQUEUE; // return is actually ignored.
1639: } // end of performWork
1640:
1641: private int getItemSize(CachedItem item) {
1642: if (!useByteCount)
1643: return 1;
1644: SizedCacheable entry = (SizedCacheable) item.getEntry();
1645: if (null == entry)
1646: return 0;
1647: return entry.getSize();
1648: } // end of getItemSize
1649:
1650: /**
1651: Return statistics about cache that may be implemented.
1652: **/
1653: public synchronized long[] getCacheStats() {
1654: stat.currentSize = getCurrentSize();
1655: return stat.getStats();
1656: }
1657:
1658: /**
1659: Reset the statistics to 0.
1660: **/
1661: public void resetCacheStats() {
1662: stat.reset();
1663: }
1664:
1665: /**
1666: * @return the current maximum size of the cache.
1667: */
1668: public synchronized long getMaximumSize() {
1669: return maximumSize;
1670: }
1671:
1672: /**
1673: * Change the maximum size of the cache. If the size is decreased then cache entries
1674: * will be thrown out.
1675: *
1676: * @param newSize the new maximum cache size
1677: *
1678: * @exception StandardException Cloudscape Standard error policy
1679: */
1680: public void resize(long newSize) throws StandardException {
1681: boolean shrink;
1682:
1683: synchronized (this ) {
1684: maximumSize = newSize;
1685: stat.maxSize = maximumSize;
1686: shrink = (shrinkSize(getCurrentSize()) > 0);
1687: }
1688: if (shrink) {
1689: performWork(true /* shrink only */);
1690: /* performWork does not remove recently used entries nor does it mark them as
1691: * not recently used. Therefore if the cache has not shrunk enough we will call rotateClock
1692: * to free up some entries.
1693: */
1694: if (shrinkSize(getCurrentSize()) > 0) {
1695: CachedItem freeItem = rotateClock((float) 2.0);
1696: /* rotateClock(2.0) means that the clock will rotate through the cache as much as
1697: * twice. If it does not find sufficient unused items the first time through it
1698: * will almost certainly find enough of them the second time through, because it
1699: * marked all the items as not recently used in the first pass.
1700: *
1701: * If the cache is very heavily used by other threads then a lot of the items marked as
1702: * unused in the first pass may be used before rotateClock passes over them again. In this
1703: * unlikely case rotateClock( 2.0) may not be able to clear out enough space to bring the
1704: * current size down to the maximum. However the cache size should come down as rotateClock
1705: * is called in the normal course of operation.
1706: */
1707: if (freeItem != null)
1708: freeItem.unkeepForCreate();
1709: }
1710: }
1711:
1712: } // end of resize;
1713:
1714: private synchronized long getCurrentSize() {
1715: if (!useByteCount)
1716: return holders.size();
1717: return currentByteCount + holders.size() * ITEM_OVERHEAD;
1718: }
1719:
1720: /**
1721: * Perform an operation on (approximately) all entries that matches the filter,
1722: * or all entries if the filter is null. Entries that are added while the
1723: * cache is being scanned might or might not be missed.
1724: *
1725: * @param filter
1726: * @param operator
1727: */
1728: public void scan(Matchable filter, Operator operator) {
1729: int itemCount = 1;
1730: Cacheable entry = null;
1731: CachedItem item = null;
1732:
1733: // Do not call the operator while holding the synchronization lock.
1734: // However we cannot access an item's links without holding the synchronization lock,
1735: // nor can we assume that an item is still in the cache unless we hold the synchronization
1736: // lock or the item is marked as kept.
1737: for (int position = 0;; position++) {
1738: synchronized (this ) {
1739: if (null != item) {
1740: release(item);
1741: item = null;
1742: }
1743:
1744: for (; position < holders.size(); position++) {
1745: item = (CachedItem) holders.get(position);
1746: if (null != item) {
1747: try {
1748: entry = item.use();
1749: } catch (StandardException se) {
1750: continue;
1751: }
1752:
1753: if (null != entry
1754: && (null == filter || filter
1755: .match(entry))) {
1756: item.keepForClean();
1757: break;
1758: }
1759: }
1760: }
1761: if (position >= holders.size())
1762: return;
1763:
1764: } // end of synchronization
1765: operator.operate(entry);
1766: // Do not release the item until we have re-acquired the synchronization lock.
1767: // Otherwise the item may be removed and its next link invalidated.
1768: }
1769: } // end of scan
1770:
1771: private int trimRequests = 0;
1772:
1773: /* Trim out invalid items from holders if there are a lot of them. This is expensive if
1774: * holders is large.
1775: * The caller must hold the cache synchronization lock.
1776: */
1777: private void trimToSize() {
1778: int size = holders.size();
1779:
1780: // Trimming is expensive, don't do it often.
1781: trimRequests++;
1782: if (trimRequests < size / 8)
1783: return;
1784: trimRequests = 0;
1785:
1786: // move invalid items to the end.
1787: int endPosition = size - 1;
1788:
1789: int invalidCount = 0;
1790: for (int i = 0; i <= endPosition; i++) {
1791: CachedItem item = (CachedItem) holders.get(i);
1792:
1793: if (item.isKept())
1794: continue;
1795:
1796: if (item.isValid())
1797: continue;
1798:
1799: invalidCount++;
1800:
1801: // swap with an item later in the list
1802: // try to keep free items at the end of the holders array.
1803: for (; endPosition > i; endPosition--) {
1804: CachedItem last = (CachedItem) holders.get(endPosition);
1805: if (last.isValid()) {
1806: holders.set(i, last);
1807: holders.set(endPosition, item);
1808: endPosition--;
1809: break;
1810: }
1811: }
1812: }
1813: // small cache - don't shrink.
1814: if (size < 32)
1815: return;
1816:
1817: // now decide if we need to shrink the holder array or not.
1818: int validItems = size - invalidCount;
1819:
1820: // over 75% entries used, don't shrink.
1821: if (validItems > ((3 * size) / 4))
1822: return;
1823:
1824: // keep 10% new items.
1825: int newSize = validItems + (validItems / 10);
1826:
1827: if (newSize >= size)
1828: return;
1829:
1830: // remove items, starting at the end, where
1831: // hopefully most of the free items are.
1832: for (int r = size - 1; r > newSize; r--) {
1833: CachedItem remove = (CachedItem) holders.get(r);
1834: if (remove.isKept() || remove.isValid()) {
1835: continue;
1836: }
1837:
1838: if (useByteCount) {
1839: currentByteCount -= getItemSize(remove);
1840: }
1841:
1842: holders.remove(r);
1843: }
1844:
1845: holders.trimToSize();
1846: // move the clock hand to the start of the invalid items.
1847: clockHand = validItems + 1;
1848:
1849: } // end of trimToSize
1850: }
|