EMMA Coverage Report (generated Wed Jun 28 22:15:27 PDT 2006)
[all classes][org.apache.derby.impl.services.cache]

COVERAGE SUMMARY FOR SOURCE FILE [Clock.java]

nameclass, %method, %block, %line, %
Clock.java100% (1/1)78%  (28/36)68%  (1827/2675)77%  (474.2/615)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class Clock100% (1/1)78%  (28/36)68%  (1827/2675)77%  (474.2/615)
<static initializer> 100% (1/1)93%  (14/15)93%  (0.9/1)
Clock (CacheableFactory, String, int, long, boolean): void 100% (1/1)100% (66/66)100% (16/16)
addEntry (CachedItem, Object, boolean, Object): Cacheable 100% (1/1)81%  (96/118)90%  (23.3/26)
ageOut (): void 100% (1/1)77%  (73/95)93%  (21.4/23)
clean (Matchable): void 100% (1/1)100% (4/4)100% (2/2)
cleanAll (): void 100% (1/1)100% (12/12)100% (3/3)
cleanCache (Matchable): void 100% (1/1)79%  (76/96)91%  (24.6/27)
create (Object, Object): Cacheable 100% (1/1)66%  (63/95)82%  (13.9/17)
discard (Matchable): boolean 100% (1/1)78%  (77/99)93%  (23.4/25)
find (Object): Cacheable 100% (1/1)78%  (171/218)88%  (35.3/40)
findCached (Object): Cacheable 100% (1/1)79%  (54/68)90%  (14.4/16)
findFreeItem (): CachedItem 100% (1/1)94%  (74/79)96%  (16.4/17)
getCacheStats (): long [] 0%   (0/1)0%   (0/9)0%   (0/2)
getCurrentSize (): long 100% (1/1)44%  (8/18)67%  (2/3)
getItemSize (CachedItem): int 100% (1/1)29%  (5/17)33%  (2/6)
getMaximumSize (): long 100% (1/1)100% (3/3)100% (1/1)
getNumberInUse (): int 0%   (0/1)0%   (0/25)0%   (0/7)
growCache (): CachedItem 100% (1/1)80%  (20/25)96%  (5.8/6)
incrClockHand (): int 100% (1/1)100% (17/17)100% (3/3)
performWork (ContextManager): int 100% (1/1)76%  (16/21)95%  (4.7/5)
performWork (boolean): int 100% (1/1)63%  (269/429)83%  (77.8/94)
release (Cacheable): void 100% (1/1)90%  (66/73)96%  (17.3/18)
release (CachedItem): void 100% (1/1)85%  (29/34)94%  (9.4/10)
remove (Cacheable): void 100% (1/1)73%  (87/119)87%  (23.6/27)
removeIdentity (CachedItem): long 100% (1/1)65%  (39/60)76%  (9.9/13)
resetCacheStats (): void 0%   (0/1)0%   (0/4)0%   (0/2)
resize (long): void 0%   (0/1)0%   (0/53)0%   (0/12)
rotateClock (float): CachedItem 100% (1/1)75%  (323/428)82%  (73.5/90)
scan (Matchable, Operator): void 0%   (0/1)0%   (0/76)0%   (0/22)
serviceASAP (): boolean 0%   (0/1)0%   (0/3)0%   (0/1)
serviceImmediately (): boolean 0%   (0/1)0%   (0/2)0%   (0/1)
setUsed (Object []): void 0%   (0/1)0%   (0/61)0%   (0/15)
shrinkSize (long): long 100% (1/1)100% (31/31)100% (10/10)
shutdown (): void 100% (1/1)85%  (28/33)97%  (9.7/10)
trimToSize (): void 100% (1/1)61%  (92/150)67%  (26/39)
useDaemonService (DaemonService): void 100% (1/1)74%  (14/19)80%  (4/5)

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 
21package org.apache.derby.impl.services.cache;
22 
23import org.apache.derby.iapi.services.cache.CacheManager;
24import org.apache.derby.iapi.services.cache.Cacheable;
25import org.apache.derby.iapi.services.cache.CacheableFactory;
26import org.apache.derby.iapi.services.cache.SizedCacheable;
27import org.apache.derby.iapi.services.context.ContextManager;
28import org.apache.derby.iapi.services.daemon.DaemonService;
29import org.apache.derby.iapi.services.daemon.Serviceable;
30 
31import org.apache.derby.iapi.error.StandardException;
32 
33import org.apache.derby.iapi.services.monitor.Monitor;
34 
35import org.apache.derby.iapi.services.sanity.SanityManager;
36 
37import org.apache.derby.iapi.services.cache.ClassSize;
38import org.apache.derby.iapi.util.Matchable;
39import org.apache.derby.iapi.util.Operator;
40import org.apache.derby.iapi.reference.SQLState;
41 
42import java.util.ArrayList;
43import java.util.Hashtable;
44import 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 
99final 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 
942restartClock:
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 
1326outerscan:
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 
1339innerscan:
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}

[all classes][org.apache.derby.impl.services.cache]
EMMA 2.0.5312 (C) Vladimir Roubtsov