jakarta-jcs-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Niall Gallagher <ni...@switchfire.com>
Subject JCSAdmin.jsp doesn't broadcast removes - patch to fix it
Date Tue, 04 Dec 2007 12:46:02 GMT
Hi,

I have confirmed that the JCSAdmin.jsp when running in a remote cache
server does not broadcast "removes" to client machines, because it's
calling the wrong API.

JCSAdmin.jsp delegates to JCSADminBean.

When JCSAdminBean is running in a client-side JCS instance it removes
elements from the cache using the regular cache API (as it should).

When it's running in a remote cache server however it uses the same API
to remove elements, and that API is not aware of client machines that
might be interested in receiving "remove" events.

The long and the short of this is that if you want to use the
JCSAdmin.jsp to centrally-manage / manually remove objects from your
distributed cache, it will not currently be notifying your client
machines about elements that you removed via the web interface, and
those clients will continue to use local copies of such objects until
they are restarted.

I've applied a fix in our in-house modified copy of JCS, and it works.
I've also fixed the getByteCount method such that the JCSAdmin.jsp will
now display the correct amount of memory that each cache region is
taking up on remote cache servers.

The only potential issue is that because we want to keep the existing
behaviour of the JSP when it's running in a client-side instance of JCS
I've used this statement as a test to see which environment it's running
in:

if (RemoteCacheServerFactory.getRemoteCacheServer() == null) {
    // Not running in a remote cache server (use the existing code).
}
else {
    // Running in a remote cache server (use new code).
}

Can anybody see any problems with the statement above?

The following are replacements for some methods in
org.apache.jcs.admin.JCSAdminBean. Can we get these changes integrated
into trunk?

-Niall

    /**
     * Clears all regions in the cache.
     * <p/>
     * If this class is running within a remote cache server, clears all
regions via the <code>RemoteCacheServer</code>
     * API, so that removes will be broadcast to client machines.
Otherwise clears all regions in the cache directly via
     * the usual cache API.
     */
    public void clearAllRegions() throws IOException {
        if (RemoteCacheServerFactory.getRemoteCacheServer() == null) {
            // Not running in a remote cache server.
            // Remove objects from the cache directly, as no need to
broadcast removes to client machines...

            String[] names = cacheHub.getCacheNames();

            for (int i = 0; i < names.length; i++) {
                cacheHub.getCache(names[i]).removeAll();
            }
        }
        else {
            // Running in a remote cache server.
            // Remove objects via the RemoteCacheServer API, so that
removes will be broadcast to client machines...
            try {
                String[] cacheNames =
CompositeCacheManager.getInstance().getCacheNames();

                // Call remoteCacheServer.removeAll(String) for each
cacheName...
                // Note: We must do this using reflection to bypass its
package-private access...
                Object remoteCacheServerObject =
RemoteCacheServerFactory.getRemoteCacheServer();
                Method removeAllMethod =
remoteCacheServerObject.getClass().getMethod("removeAll", new
Class[]{String.class});
                boolean previouslyAccessibility =
removeAllMethod.isAccessible();
                removeAllMethod.setAccessible(true);
                for (int i = 0; i < cacheNames.length; i++) {
                    String cacheName = cacheNames[i];
                    removeAllMethod.invoke(remoteCacheServerObject, new
Object[]{cacheName});
                }
                removeAllMethod.setAccessible(previouslyAccessibility);
            }
            catch (Exception e) {
                throw new IllegalStateException("Failed to remove all
elements from all cache regions: " + e, e);
            }
        }
    }


    /**
     * Clears a particular cache region.
     * <p/>
     * If this class is running within a remote cache server, clears the
region via the <code>RemoteCacheServer</code>
     * API, so that removes will be broadcast to client machines.
Otherwise clears the region directly via the usual
     * cache API.
     */
    public void clearRegion(String cacheName) throws IOException {
        if (cacheName == null) {
            throw new IllegalArgumentException("The cache name specified
was null.");
        }
        if (RemoteCacheServerFactory.getRemoteCacheServer() == null) {
            // Not running in a remote cache server.
            // Remove objects from the cache directly, as no need to
broadcast removes to client machines...
            cacheHub.getCache(cacheName).removeAll();
        }
        else {
            // Running in a remote cache server.
            // Remove objects via the RemoteCacheServer API, so that
removes will be broadcast to client machines...
            try {
                // Call remoteCacheServer.removeAll(String)...
                // Note: We must do this using reflection to bypass its
package-private access...
                Object remoteCacheServerObject =
RemoteCacheServerFactory.getRemoteCacheServer();
                Method removeAllMethod =
remoteCacheServerObject.getClass().getMethod("removeAll", new
Class[]{String.class});
                boolean previouslyAccessibility =
removeAllMethod.isAccessible();
                removeAllMethod.setAccessible(true);
                removeAllMethod.invoke(remoteCacheServerObject, new
Object[]{cacheName});
                removeAllMethod.setAccessible(previouslyAccessibility);
            }
            catch (Exception e) {
                throw new IllegalStateException("Failed to remove all
elements from cache region [" + cacheName + "]: " + e, e);
            }
        }
    }

    /**
     * Removes a particular item from a particular region.
     * <p/>
     * If this class is running within a remote cache server, removes
the item via the <code>RemoteCacheServer</code>
     * API, so that removes will be broadcast to client machines.
Otherwise clears the region directly via the usual
     * cache API.
     *
     * @param cacheName
     * @param key
     *
     * @throws IOException
     */
    public void removeItem(String cacheName, String key) throws
IOException {
        if (cacheName == null) {
            throw new IllegalArgumentException("The cache name specified
was null.");
        }
        if (key == null) {
            throw new IllegalArgumentException("The key specified was
null.");
        }
        if (RemoteCacheServerFactory.getRemoteCacheServer() == null) {
            // Not running in a remote cache server.
            // Remove objects from the cache directly, as no need to
broadcast removes to client machines...
            cacheHub.getCache(cacheName).remove(key);
        }
        else {
            // Running in a remote cache server.
            // Remove objects via the RemoteCacheServer API, so that
removes will be broadcast to client machines...
            try {
                Object keyToRemove = null;
                CompositeCache cache =
CompositeCacheManager.getInstance().getCache(cacheName);

                // A String key was supplied, but to remove elements via
the RemoteCacheServer API, we need the
                // actual key object as stored in the cache (i.e. a
Serializable object). To find the key in this form,
                // we iterate through all keys stored in the memory
cache until we find one whose toString matches
                // the string supplied...

                Object[] allKeysInCache =
cache.getMemoryCache().getKeyArray();
                for (int i = 0; i < allKeysInCache.length; i++) {
                    Object keyInCache = allKeysInCache[i];
                    if (keyInCache.toString().equals(key)) {
                        if (keyToRemove == null) {
                            keyToRemove = keyInCache;
                        }
                        else {
                            // A key matching the one specified was
already found...
                            throw new
IllegalStateException("Unexpectedly found duplicate keys in the cache
region matching the key specified.");
                        }
                    }
                }
                if (keyToRemove == null) {
                    throw new IllegalStateException("No match for this
key could be found in the set of keys retrieved from the memory
cache.");
                }
                if (!(keyToRemove instanceof Serializable)) {
                    throw new IllegalStateException("Found key [" +
keyToRemove + ", " + keyToRemove.getClass() + "] in cache matching key
specified, however key found in cache is unexpectedly not
serializable.");
                }
                // At this point, we have retrieved the matching
Serializable key.

                // Call remoteCacheServer.remove(String,
Serializable)...
                // Note: We must fo this using reflection to bypass its
package-private access...
                Object remoteCacheServerObject =
RemoteCacheServerFactory.getRemoteCacheServer();
                Method removeMethod =
remoteCacheServerObject.getClass().getMethod("remove", new
Class[]{String.class, Serializable.class});

                boolean previouslyAccessibility =
removeMethod.isAccessible();
                removeMethod.setAccessible(true);
                removeMethod.invoke(remoteCacheServerObject, new
Object[]{cacheName, keyToRemove});
                removeMethod.setAccessible(previouslyAccessibility);
            }
            catch (Exception e) {
                throw new IllegalStateException("Failed to remove
element with key [" + key + ", " + key.getClass() + "] from cache region
[" + cacheName + "]: " + e, e);
            }
        }
    }

    /**
     * Tries to estimate how much data is in a region. This is
expensive. If there are any non serializable objects in
     * the region or an error occurs, suppresses exceptions and returns
0.
     * <p/>
     *
     * @return int The size of the region in bytes.
     */
    public int getByteCount(CompositeCache cache) throws Exception {
        if (cache == null) {
            throw new IllegalArgumentException("The cache object
specified was null.");
        }
        long size = 0;
        try {
            MemoryCache memCache = cache.getMemoryCache();

            Iterator iter = memCache.getIterator();
            while (iter.hasNext()) {

                Map.Entry nextMapEntry = (Map.Entry) iter.next();
                ICacheElement ice = (ICacheElement)
nextMapEntry.getValue();

                if (ice instanceof CacheElementSerialized) {
                    size = size + ((CacheElementSerialized)
ice).getSerializedValue().length;
                }
                else {
                    Serializable element = ice.getVal();

                    //CountingOnlyOutputStream: Keeps track of the
number of bytes written to it, but doesn't write them anywhere.
                    CountingOnlyOutputStream counter = new
CountingOnlyOutputStream();
                    try {
                        ObjectOutputStream out = new
ObjectOutputStream(counter);
                        out.writeObject(element);
                    }
                    catch (IOException e) {
                        throw new RuntimeException("IOException while
trying to measure the size of the cached element", e);
                    }
                    // 4 bytes lost for the serialization header
                    size = size + counter.getCount() - 4;
                }
            }
            if (size > Integer.MAX_VALUE) {
                throw new IllegalStateException("The size of cache " +
cache.getCacheName() + " (" + size + " bytes) is too large to be
represented as an integer.");
            }
        }
        catch (Exception e) {
//            throw new RuntimeException("Failed to calculate the size
of cache region [" + cache.getCacheName() + "]:" + e, e);
            return 0;
        }
        return (int) size;
    }



On Wed, 2007-09-26 at 19:39 +0100, Niall Gallagher wrote:

> Hi,
> 
> Not sure if this has been asked before..
> 
> We have found that the JCSAdmin.jsp running on our central cache server,
> does not seem to broadcast removes to client machines when we clear
> regions via this JSP on the central server. Is this expected behaviour
> or a bug?
> 
> We have gotten around the problem by writing our own custom JSPs based
> on the JCSAdmin.jsp and JCSAdmin bean, and deploying them in the same
> webapp as the JCSAdmin.jsp and jcs jar file (let's call this the "jcs
> webapp"). Our custom JSPs don't actually clear regions themselves, they
> POST the requests to another webapp we have written.
> 
> The other webapp we have written (let's call it "jcs assistant")
> contains just a simple servlet which listens for HTTP POSTs asking it to
> clear certain regions or elements. This servlet clears the requested
> region using the normal JCS API as if it was a normal client machine
> (note that we only use string keys, so we can send them in the POST).
> 
> Both webapps are deployed to JBoss 4.2.1GA. We found that we had to
> configure Isolated -> true in the ear-deployer.xml file, to force JBoss
> to load a separate copy of JCS for both webapps.
> 
> Basically- we've found that when a client puts an object in the cache it
> gets sent to the central server and shows up on both our JSPs and the
> JCSAdmin.jsp.
> If we remove the object from the cache via JCSAdmin.jsp, it disappears
> from both sets of JSPs, but if the client then tries to retrieve the
> same object from the cache again it unexpectedly *succeeds* (this is
> without the client shutting down between runs).
> If we remove the object from the cache via our "JCS assistant" webapp,
> it disappears from both sets of JSPs, and if the client then tries to
> retrieve the same object from the cache again it gets null as expected.
> 
> Has anyone seen this behaviour before? Is there anything we can do to
> fix it? Obviously having to hack JSPs and deploy two webapps isn't a
> very good solution to this problem.
> 
> Thanks in advance,
> 
> Niall
> 



Mime
  • Unnamed multipart/alternative (inline, None, 0 bytes)
View raw message