Switching to the dev list: I've had some time this week to revisit this issue with memory leaks, especially when using the Local Query Cache. There are two separate issues to address:
1) The lifetime of entries in the Local Query Cache exceeds their availability, which is the life of their ObjectContext. Any cache that is not expiring entries (or limiting them) will just leak this memory. 2) Cached query results will retain the ObjectContext they were fetched into, which in turn may retain a much larger number of objects than intended. For example. If you use a single ObjectContext to fetch 1 million uncached objects along with 1 cached object, you will retain 1 million and 1 objects in memory rather than just 1. This is potentially an issue with both the Shared and Local Query Caches. Also, because the cached objects still reference the ObjectContext, it appears that the context will not be garbage collected. So a simple attempt to solve issue #1 by invalidating the Local Query Cache when an ObjectContext is finalized doesn't work, because the context will never be finalized. Possible Solutions: One solution is to null out the ObjectContext on any objects that are inserted into the Query Cache. This solves both problems above, and it seems logical since when the objects are retrieved from the cache they will be placed into a new context anyway. This should work, but the implementation has been tricky. Handling the deep object graphs due to prefetching makes this a bit more complicated. This is what I've be working on most recently. A different solution that fixes the issues with the Local Cache (but not the Shared Cache) would be to separate the storage for these two caches and use a new instance of the Local Cache for each ObjectContext so that their lifetimes correspond exactly. This could be done inside the Query Cache itself so that users wouldn't need to know. I'm imagining a CombinedCache or something that combines two caches together into one, where the Shared Cache entries go to a long-lived cache and the Local Cache entries go to a small short-lived cache. This solution is easier to implement (less invasive) and seems more natural to me since it ties these lifetimes together. These two solutions are not mutually exclusive - they both could be done. What Now? I've taken a first stab at implementing both of these solutions and have had some concerns raised about each of them [1] [2]. I'd like to implement something to fix this problem directly in Cayenne rather than fixing it only for myself. I'd love to hear any feedback or suggestions on this before I go further down what might be the wrong road. Thanks, John [1] https://github.com/apache/cayenne/pull/292 (My local copy of this code now differs fairly significantly from what is currently in the pull request) [2] https://github.com/apache/cayenne/pull/388 (I would change this code to make the separate caches live inside a single CombinedCache now) On Sat, Nov 30, 2019 at 2:28 AM Andrus Adamchik <and...@objectstyle.org> wrote: > Hi, > > Wanted to mention a scenario when cache misconfiguration can lead to a > hard-to-detect memory leak. More details are available in this Jira [1], > but the short story is that when you are using JCache/EhCache, make sure > that *all* the cache groups used in queries are *explicitly* configured > with desired size limits in ehcache.xml [2], including an entry for a > no-cache group scenario that corresponds to "cayenne.default.cache" cache > name. > > Andrus > > [1] https://issues.apache.org/jira/browse/CAY-2642 > > [2] <config > xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' > xmlns='http://www.ehcache.org/v3' > xsi:schemaLocation=" > http://www.ehcache.org/v3 > http://www.ehcache.org/schema/ehcache-core-3.0.xsd"> > > <!-- Used by Cayenne for queries without explicit cache groups. --> > <cache alias="cayenne.default.cache"> > <expiry> > <ttl unit="minutes">1</ttl> > </expiry> > <heap>100</heap> > </cache> > > <cache alias="my-cache-group"> > <expiry> > <ttl unit="minutes">1</ttl> > </expiry> > <heap>10</heap> > </cache> > </config> > >