db-derby-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From kahat...@apache.org
Subject svn commit: r596265 - in /db/derby/code/trunk/java/engine/org/apache/derby: iapi/services/cache/ impl/services/ impl/services/cache/
Date Mon, 19 Nov 2007 11:02:38 GMT
Author: kahatlen
Date: Mon Nov 19 03:02:37 2007
New Revision: 596265

URL: http://svn.apache.org/viewvc?rev=596265&view=rev
Log:
DERBY-2911 (partial) Implemented background cleaner for ConcurrentCache

Created a background cleaner which enables the user threads to request
that the cleaning of a cached object happens asynchronously in Derby's
service daemon.

Added:
    db/derby/code/trunk/java/engine/org/apache/derby/impl/services/cache/BackgroundCleaner.java
  (with props)
Modified:
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/cache/CacheManager.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/services/build.xml
    db/derby/code/trunk/java/engine/org/apache/derby/impl/services/cache/CacheEntry.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/services/cache/ClockPolicy.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/services/cache/ConcurrentCache.java

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/cache/CacheManager.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/cache/CacheManager.java?rev=596265&r1=596264&r2=596265&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/cache/CacheManager.java
(original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/cache/CacheManager.java
Mon Nov 19 03:02:37 2007
@@ -231,7 +231,9 @@
 
 	/**
 		This cache can use this DaemonService if it needs some work to be done
-		int he background 
+		in the background. The caller must ensure that it has exclusive access
+        to the cache when this method is called. No synchronization is required
+        in the implementations of this method.
 	*/
 	public void useDaemonService(DaemonService daemon);
 

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/services/build.xml
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/services/build.xml?rev=596265&r1=596264&r2=596265&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/services/build.xml (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/services/build.xml Mon Nov 19 03:02:37
2007
@@ -57,6 +57,7 @@
       <exclude name="${derby.dir}/impl/services/cache/Concurrent*.java"/>
       <exclude name="${derby.dir}/impl/services/cache/CacheEntry.java"/>
       <exclude name="${derby.dir}/impl/services/cache/*Policy.java"/>
+      <exclude name="${derby.dir}/impl/services/cache/BackgroundCleaner.java"/>
     </javac>
  </target>
  
@@ -85,6 +86,7 @@
      <include name="${derby.dir}/impl/services/cache/Concurrent*.java"/>
      <include name="${derby.dir}/impl/services/cache/CacheEntry.java"/>
      <include name="${derby.dir}/impl/services/cache/*Policy.java"/>
+     <include name="${derby.dir}/impl/services/cache/BackgroundCleaner.java"/>
    </javac>
  </target>
 

Added: db/derby/code/trunk/java/engine/org/apache/derby/impl/services/cache/BackgroundCleaner.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/services/cache/BackgroundCleaner.java?rev=596265&view=auto
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/services/cache/BackgroundCleaner.java
(added)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/services/cache/BackgroundCleaner.java
Mon Nov 19 03:02:37 2007
@@ -0,0 +1,159 @@
+/*
+
+   Derby - Class org.apache.derby.impl.services.cache.BackgroundCleaner
+
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to you under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+ */
+
+package org.apache.derby.impl.services.cache;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.apache.derby.iapi.error.StandardException;
+import org.apache.derby.iapi.services.context.ContextManager;
+import org.apache.derby.iapi.services.daemon.DaemonService;
+import org.apache.derby.iapi.services.daemon.Serviceable;
+
+/**
+ * A background cleaner which can be used by <code>ConcurrentCache</code> so
+ * that it doesn't have to wait for clean operations to finish. When the
+ * background cleaner is asked to clean an item, it puts the item in a queue
+ * and requests to be serviced by a <code>DaemonService</code> running in a
+ * separate thread.
+ */
+final class BackgroundCleaner implements Serviceable {
+
+    /** The service thread which performs the clean operations. */
+    private final DaemonService daemonService;
+
+    /** Subscription number for this <code>Serviceable</code>. */
+    private final int clientNumber;
+
+    /**
+     * Flag which tells whether the cleaner has a still unprocessed job
+     * scheduled with the daemon service. If this flag is <code>true</code>,
+     * calls to <code>serviceNow()</code> won't result in the cleaner being
+     * serviced.
+     */
+    private final AtomicBoolean scheduled = new AtomicBoolean();
+
+    /** A queue of cache entries that need to be cleaned. */
+    private final ArrayBlockingQueue<CacheEntry> queue;
+
+    /** The cache manager owning this cleaner. */
+    private final ConcurrentCache cacheManager;
+
+    /**
+     * Create a background cleaner instance and subscribe it to a daemon
+     * service.
+     *
+     * @param cache the cache manager that owns the cleaner
+     * @param daemon the daemon service which perfoms the work
+     * @param queueSize the maximum number of entries to keep in the queue
+     * (must be greater than 0)
+     */
+    BackgroundCleaner(
+            ConcurrentCache cache, DaemonService daemon, int queueSize) {
+        queue = new ArrayBlockingQueue<CacheEntry>(queueSize);
+        daemonService = daemon;
+        cacheManager = cache;
+        // subscribe with the onDemandOnly flag
+        clientNumber = daemon.subscribe(this, true);
+    }
+
+    /**
+     * Try to schedule a clean operation in the background cleaner.
+     *
+     * @param entry the entry that needs to be cleaned
+     * @return <code>true</code> if the entry has been scheduled for clean,
+     * <code>false</code> if the background cleaner can't clean the entry (its
+     * queue is full)
+     */
+    boolean scheduleWork(CacheEntry entry) {
+        final boolean queued = queue.offer(entry);
+        if (queued) {
+            requestService();
+        }
+        return queued;
+    }
+
+    /**
+     * Notify the daemon service that the cleaner needs to be serviced.
+     */
+    private void requestService() {
+        // Calling serviceNow() doesn't have any effect if we have already
+        // called it and the request hasn't been processed yet. Therefore, we
+        // only call serviceNow() if we can atomically change scheduled from
+        // false to true. If the cleaner is waiting for service (schedule is
+        // true), we don't need to call serviceNow() since the cleaner will
+        // re-request service when it finishes its current operation and
+        // detects that there is more work in the queue.
+        if (scheduled.compareAndSet(false, true)) {
+            daemonService.serviceNow(clientNumber);
+        }
+    }
+
+    /**
+     * Stop subscribing to the daemon service.
+     */
+    void unsubscribe() {
+        daemonService.unsubscribe(clientNumber);
+    }
+
+    /**
+     * Clean the first entry in the queue. If there is more work, re-request
+     * service from the daemon service.
+     *
+     * @param context ignored
+     * @return status for the performed work (normally
+     * <code>Serviceable.DONE</code>)
+     * @throws StandardException if <code>Cacheable.clean()</code> fails
+     */
+    public int performWork(ContextManager context) throws StandardException {
+        // allow others to schedule more work
+        scheduled.set(false);
+        CacheEntry e = queue.poll();
+        if (e != null) {
+            try {
+                cacheManager.cleanEntry(e);
+            } finally {
+                if (!queue.isEmpty()) {
+                    // We have more work in the queue. Request service again.
+                    requestService();
+                }
+            }
+        }
+        return Serviceable.DONE;
+    }
+
+    /**
+     * Indicate that we want to be serviced ASAP.
+     * @return <code>true</code>
+     */
+    public boolean serviceASAP() {
+        return true;
+    }
+
+    /**
+     * Indicate that we don't want the work to happen immediately in the
+     * user thread.
+     * @return <code>false</code>
+     */
+    public boolean serviceImmediately() {
+        return false;
+    }
+}

Propchange: db/derby/code/trunk/java/engine/org/apache/derby/impl/services/cache/BackgroundCleaner.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/services/cache/CacheEntry.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/services/cache/CacheEntry.java?rev=596265&r1=596264&r2=596265&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/services/cache/CacheEntry.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/services/cache/CacheEntry.java Mon
Nov 19 03:02:37 2007
@@ -119,13 +119,19 @@
     /**
      * Increase the keep count for this entry. An entry which is kept cannot be
      * removed from the cache.
+     *
+     * @param accessed if <code>true</code>, notify the entry's callback object
+     * that it has been accessed (normally because of calls to create, find or
+     * findCached); otherwise, don't notify the callback object
      */
-    void keep() {
+    void keep(boolean accessed) {
         if (SanityManager.DEBUG) {
             SanityManager.ASSERT(mutex.isHeldByCurrentThread());
         }
         keepCount++;
-        callback.access();
+        if (accessed) {
+            callback.access();
+        }
     }
 
     /**

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/services/cache/ClockPolicy.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/services/cache/ClockPolicy.java?rev=596265&r1=596264&r2=596265&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/services/cache/ClockPolicy.java
(original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/services/cache/ClockPolicy.java
Mon Nov 19 03:02:37 2007
@@ -355,6 +355,10 @@
                 continue;
             }
 
+            // This variable will hold a dirty cacheable that should be cleaned
+            // after the try/finally block.
+            final Cacheable dirty;
+
             e.lock();
             try {
                 if (h.getEntry() != e) {
@@ -381,21 +385,38 @@
                     continue;
                 }
 
-                // OK, we can use this Holder
+                // The entry is not in use, and has not been used for at least
+                // one round on the clock. See if it needs to be cleaned.
                 Cacheable c = e.getCacheable();
-                if (c.isDirty()) {
-                    c.clean(false);
+                if (!c.isDirty()) {
+                    // Not in use and not dirty. Take over the holder.
+                    h.switchEntry(entry);
+                    cacheManager.evictEntry(c.getIdentity());
+                    return h;
                 }
 
-                h.switchEntry(entry);
-
-                cacheManager.evictEntry(c.getIdentity());
+                // Ask the background cleaner to clean the entry.
+                BackgroundCleaner cleaner = cacheManager.getBackgroundCleaner();
+                if (cleaner != null && cleaner.scheduleWork(e)) {
+                    // Successfully scheduled the clean operation. Move on to
+                    // the next entry.
+                    continue;
+                }
 
-                return h;
+                // There is no background cleaner, or the background cleaner
+                // has no free capacity. Let's clean the object ourselves.
+                // First, mark the entry as kept to prevent eviction until
+                // we have cleaned it, but don't mark it as accessed (recently
+                // used).
+                e.keep(false);
+                dirty = c;
 
             } finally {
                 e.unlock();
             }
+
+            // Clean the entry and unkeep it.
+            cacheManager.cleanAndUnkeepEntry(e, dirty);
         }
 
         return null;

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/services/cache/ConcurrentCache.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/services/cache/ConcurrentCache.java?rev=596265&r1=596264&r2=596265&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/services/cache/ConcurrentCache.java
(original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/services/cache/ConcurrentCache.java
Mon Nov 19 03:02:37 2007
@@ -57,6 +57,8 @@
     private final CacheableFactory holderFactory;
     /** Name of this cache. */
     private final String name;
+    /** The maximum size (number of elements) for this cache. */
+    private final int maxSize;
     /** Replacement policy to be used for this cache. */
     private final ReplacementPolicy replacementPolicy;
 
@@ -70,6 +72,12 @@
     private volatile boolean stopped;
 
     /**
+     * Background cleaner which can be used to clean cached objects in a
+     * separate thread to avoid blocking the user threads.
+     */
+    private BackgroundCleaner cleaner;
+
+    /**
      * Creates a new cache manager.
      *
      * @param holderFactory factory which creates <code>Cacheable</code>s
@@ -81,6 +89,7 @@
         replacementPolicy = new ClockPolicy(this, maxSize);
         this.holderFactory = holderFactory;
         this.name = name;
+        this.maxSize = maxSize;
     }
 
     /**
@@ -222,7 +231,7 @@
         // the entry as kept.
         if (c != null) {
             entry.setCacheable(c);
-            entry.keep();
+            entry.keep(true);
         }
         return c;
     }
@@ -247,7 +256,7 @@
         try {
             Cacheable item = entry.getCacheable();
             if (item != null) {
-                entry.keep();
+                entry.keep(true);
                 return item;
             }
             // not currently in the cache
@@ -284,7 +293,7 @@
             // locked it, getCacheable() returns null and so should we do.
             Cacheable item = entry.getCacheable();
             if (item != null) {
-                entry.keep();
+                entry.keep(true);
             }
             return item;
         } finally {
@@ -398,14 +407,89 @@
      */
     private void cleanCache(Matchable partialKey) throws StandardException {
         for (CacheEntry entry : cache.values()) {
+            final Cacheable dirtyObject;
             entry.lock();
             try {
+                if (!entry.isValid()) {
+                    // no need to clean an invalid entry
+                    continue;
+                }
                 Cacheable c = entry.getCacheable();
-                if (c != null && c.isDirty() &&
-                        (partialKey == null ||
-                             partialKey.match(c.getIdentity()))) {
-                    c.clean(false);
+                if (partialKey != null && !partialKey.match(c.getIdentity())) {
+                    // don't clean objects that don't match the partial key
+                    continue;
+                }
+                if (!c.isDirty()) {
+                    // already clean
+                    continue;
                 }
+
+                // Increment the keep count for this entry to prevent others
+                // from removing it. Then release the lock on the entry to
+                // avoid blocking others when the object is cleaned.
+                entry.keep(false);
+                dirtyObject = c;
+
+            } finally {
+                entry.unlock();
+            }
+
+            // Clean the object and decrement the keep count.
+            cleanAndUnkeepEntry(entry, dirtyObject);
+        }
+    }
+
+    /**
+     * Clean an entry in the cache.
+     *
+     * @param entry the entry to clean
+     * @exception StandardException if an error occurs while cleaning
+     */
+    void cleanEntry(CacheEntry entry) throws StandardException {
+        // Fetch the cacheable while having exclusive access to the entry.
+        // Release the lock before cleaning to avoid blocking others.
+        Cacheable item;
+        entry.lock();
+        try {
+            item = entry.getCacheable();
+            if (item == null) {
+                // nothing to do
+                return;
+            }
+            entry.keep(false);
+        } finally {
+            entry.unlock();
+        }
+        cleanAndUnkeepEntry(entry, item);
+    }
+
+    /**
+     * Clean an entry in the cache and decrement its keep count. The entry must
+     * be kept before this method is called, and it must contain the specified
+     * <code>Cacheable</code>.
+     *
+     * @param entry the entry to clean
+     * @param item the cached object contained in the entry
+     * @exception StandardException if an error occurs while cleaning
+     */
+    void cleanAndUnkeepEntry(CacheEntry entry, Cacheable item)
+            throws StandardException {
+        try {
+            // Clean the cacheable while we're not holding
+            // the lock on its entry.
+            item.clean(false);
+        } finally {
+            // Re-obtain the lock on the entry, and reduce the keep count
+            // since the entry should not be kept by the cleaner any longer.
+            entry.lock();
+            try {
+                if (SanityManager.DEBUG) {
+                    // Since the entry is kept, the Cacheable shouldn't
+                    // have changed.
+                    SanityManager.ASSERT(entry.getCacheable() == item,
+                            "CacheEntry didn't contain the expected Cacheable");
+                }
+                entry.unkeep();
             } finally {
                 entry.unlock();
             }
@@ -438,14 +522,32 @@
      * Shut down the cache.
      */
     public void shutdown() throws StandardException {
-        // TODO - unsubscribe background writer
         stopped = true;
         cleanAll();
         ageOut();
+        if (cleaner != null) {
+            cleaner.unsubscribe();
+        }
     }
 
+    /**
+     * Specify a daemon service that can be used to perform operations in
+     * the background. Callers must provide enough synchronization so that
+     * they have exclusive access to the cache when this method is called.
+     *
+     * @param daemon the daemon service to use
+     */
     public void useDaemonService(DaemonService daemon) {
-        // TODO
+        if (cleaner != null) {
+            cleaner.unsubscribe();
+        }
+        // Create a background cleaner that can queue up 1/10 of the elements
+        // in the cache.
+        cleaner = new BackgroundCleaner(this, daemon, Math.max(maxSize/10, 1));
+    }
+
+    BackgroundCleaner getBackgroundCleaner() {
+        return cleaner;
     }
 
     /**



Mime
View raw message