Problem with CacheLoader getChildrenNames
akropp Apr 6, 2005 10:27 AMI am using a custom CacheLoader to back a TreeCache with a filesystem-based cache -- directories correspond to parent nodes, and files correspond to leaf nodes.
There is a problem with how the CacheLoader is invoked to fetch the children. Let's say my file cache has, and my TreeCache is initially empty:
/a
/a/1
/a/2
/a/3
If I call cache.getChildrenNames("/a"), I will correctly get the child names (1, 2, and 3). However, if I first call cache.get("/a"), and then call cache.getChildrenNames("/a"), I will get an empty set.
I traced it down to this section of CacheLoaderInterceptor:
CacheLoaderInterceptor.java:80 ... if(fqn != null && (!cache.exists(fqn) || cache.exists(fqn, TreeCache.UNINITIALIZED))) { n=loadNode(fqn); if(load_attributes && n != null) loadAttributesFromCacheLoader(n); // n=loadNode(fqn, load_attributes); lock(fqn, Node.LOCK_TYPE_WRITE, false); // not recursive } // special case: node is loaded, but children nodes haven't: check with CacheLoader if children nodes are // present and load if yes if(meth.equals(TreeCache.getChildrenNamesMethodLocal) && n != null && fqn != null) { Map children=n.getChildren(); if(children == null) { Set children_names=null; try { children_names=loader.getChildrenNames(fqn); } ...
The logic in the last bit is that if the node's children haven't been loaded yet, it will get their names from the CacheLoader, and then create uninitialized nodes for each (so future calls to getChildrenNames work). Unfortunately, part of that if() statement checks that n != null, and n only gets set if it did not exist at all, or was uninitialized, in the first part. Thus if the parent node has previously been loaded, n will never get set, and the loader will not get called.
The simple solution is to add an else clause which fetches the already loaded node if it exists. Unfortunately, this just adds a new problem. If I were to first call cache.get("/a/1"), the node now has loaded children. Now if I call cache.getChildrenNames("/a"), it will return me the set (1), which is not consistent with my CacheLoader.
Two possible solutions I can see:
- Always stub all of the children of a node whenever the first child is loaded. This will guarantee that no matter what order calls are made to the cache, getChildrenNames will return results consistant with the CacheLoader. Unfortunately, this carries a (potentially large) performance hit, since it requires enumerating all children of a node before fetching one child.
- Use a separate flag to indicate whether all children have been loaded/stubbed, or just some (or no) children have been loaded. An additional attribute like TreeCache.UNINITIALIZED could be added, or Node could contain a allChildrenLoaded flag.