Bug #10337
Possible memory leak with FastFindCache
100%
History
#1 Updated by Alexandru Lungu 12 months ago
- Status changed from New to WIP
- Assignee set to Artur Școlnic
Artur, I think I identified a reason for a memory leak for the changes. This is only based on static analysis.
The FFCache L2Key contains values, which can be handle or other types that may retain memory. I have seen this pattern in FqlPreprocessor and that is why I created BDT.getIndependentFromContext. The point is that you should cache only the values returned by BDT.getIndependentFromContext. If that is null, simply don't cache.
#3 Updated by Alexandru Lungu 12 months ago
PS: this is not caused directly by #9802, but rather a latent bug that is exposed by more "intensive" caching.
#4 Updated by Artur Școlnic 12 months ago
- Status changed from WIP to Review
- reviewer Alexandru Lungu added
I committed the fix to 10337a/16069.
#5 Updated by Alexandru Lungu 12 months ago
- The fix is almost complete. You are missing the cases where
resolved[i] = ((Resolvable) subst).resolve();.- In other words, the field reference that is resolved might be a handle/raw/etc.
#6 Updated by Artur Școlnic 12 months ago
I extended the check to resolvable also, the code is in rev 16070.
#7 Updated by Alexandru Lungu 12 months ago
+ if (isContextLocalBDT(subst))
+ {
+ return null;
+ }
This check is not correct. subst is known to be a FieldReference. You need a check for resolved[i].
#8 Updated by Artur Școlnic 12 months ago
I changed it.
#9 Updated by Alexandru Lungu 12 months ago
- Status changed from Review to Internal Test
- % Done changed from 0 to 100
I am OK with the changes. Please proceed with regression testing.
#10 Updated by Artur Școlnic 12 months ago
Harness and webui navigation passed.
#11 Updated by Alexandru Lungu 12 months ago
- Status changed from Internal Test to Merge Pending
Please merge to trunk.
#12 Updated by Artur Școlnic 12 months ago
10337a was merged to trunk as rev 16069.
#14 Updated by Alexandru Lungu 12 months ago
- Status changed from Test to WIP
- % Done changed from 100 to 90
I identified the culprit for the real memory leak:
- there is a FIND done in a
while true - first time is inserted into FF cache
- Something is invalidating the FF Cache entry but doesn't clear the reverse-lookup explicitly
- The while loops again and the same entry is added - and this repeats
Value.addNodeis executed and adds the new entry in the linked list associated with provided record identifier
In the heap dump I see 6GB of a single linked-list, mostly because there is a huge list associated with the record identifier. Note that this linked list is no longer bound. It was previously cleared because it was weakly referencing things from the ffCache. Now, while the reverse look-up is bound (LRU strategy), the linked-list entries are not bound.
PS: mind the getCanonicalRecordIdentifier function that explicitly attempts to preserve only one single record identifier that is valid, so there is no need to duplicate references.
I must admit that I oversaw this design flaw. The reverse-look-up is a 2 dimensional cache and we bounded it only on one dimension. Previously to the updates of #9802, the ffcache was LRU so that entries from this 2D cache were cherry-picked and cleared. More that this, simply removing things from the ffCache was implicitly removing from the ReverseLookup cache (because the map keys were weakly referenced).
To achieve the same behavior, I think we need two structures (LRU and WeakMap). The first will only keep track of the entries that shall be evicted (linked list nodes mostly) and the second will provide the old functionality. If a list node is evicted, then clear it from the list. Mind that the lists should now be doubly-linked. This way, we reintroduce the "cherry-picking" and weakly referencing of nodes.
Artur, there is no easy fix for this as it affects the core design. We need to rollback the changes on the FFCache. Please preserve 10337a in trunk as it is unrelated. Please assess ASAP.
Artur/Greg: even if #9802 shown an improvement, this is a functional concern that is Urgent. We need to buy time to fix it. Once we do, I expect to see the same performance improvement in the end for the scenarios in #9802. This case I am presenting here is mostly caused by batch processes that do the exact same query all over again. Systems that are more busy have a higher chance to evict these big chunks of memory due to LRU policy.
#15 Updated by Artur Școlnic 12 months ago
Reverted the changes from trunk rev 16051. I will create 10337b and use it to bring those back with a more robust memory management.
I do agree with Alexandru and am also positive that the new branch will not regress the performance gains 9802f brought.
#16 Updated by Artur Școlnic 12 months ago
- Status changed from WIP to Review
I overhauled the ReverseLookup and brought back the changes from 9802f, tested the code and it is as fast as 9802f, I also tried to test for memory leaks by running harness for one hour, it seems ok.
The code is in 10337b and is ready for review.
#17 Updated by Artur Școlnic 12 months ago
- reviewer Constantin Asofiei added
#18 Updated by Constantin Asofiei 12 months ago
Artur, please archive branches after you merge them - 10337a is still 'active'.
#19 Updated by Alexandru Lungu 12 months ago
Review of 10337b:
void put(RecordIdentifier<String> recID, Map<L2Key, RecordIdentifier<String>> l3Cache, L2Key l2Key)
+ ReverseCacheEntry val = reverseCache.get(recID);
+ if (val == null)
+ {
+ List<L2Key> l2Keys = new LinkedList<>();
+ l2Keys.add(l2Key);
+ val = new ReverseCacheEntry(l3Cache, l2Keys, recID);
+ reverseCache.put(recID, val);
+ }
+ else
+ {
+ val.l2Keys.add(l2Key);
+ }
This code from ReverseLookup.put is not correct. Mind that the l3Cache parameter you get is not honored when the reverseCache is hit. So you end up registering all FFcache entries of a specific DMO to the same l3Cache. This is wrong as the same DMO can be registered for same table, but different indexes (aka different L3 caches). First key is a combination of table id and multiplex, the second key is the index id and the third key is the complex one holding the FQL and parameters.
Value that implements a forward linked-list of entries from different L3 caches. The catch is that the LRU cache inside ReverseLookup should have a (Node) key, so that we bound the total number of ff cache entries, not the number of DMOs (considering a DMO can have multiple entries in the ff cache on different indexes). But because making a LRU cache have Node keys (with L2Key references), we can't use it for fast look-up anymore. Thus:
- beside the LRU cache, create a map
Map<RecordIdentifier<String>, Value>just like before. The keys can be weakly referenced just like in trunk. In fact, all "weak referencing" work can be reintroduced. - The LRU cache will only gather L2Keys. They can be weakly referenced.
- On expiry, just invalidate the entries from the reverse look-up map, which ultimately will invalidate records from ffCache.
- To invalidate entries from reverse look-up, you should:
- transform the forward linked list to a doubly linked list (instead of
next, storeprevandnext) - call
Node.delete. This will delete the Node of the linked list. This should be straight-forward.
- transform the forward linked list to a doubly linked list (instead of
You can start from scratch with trunk (but maybe add the emptyRecordsReverseCache logic). Next to the existing reverse look-up caches, create a separate LRU cache as described with their respective listeners. The only difference is that the lists will have a different implementation to allow expiry to call Node.delete.
#20 Updated by Alexandru Lungu 12 months ago
- Status changed from Review to WIP
#21 Updated by Alexandru Lungu 12 months ago
In fact, I think there is a small caveat. We still need a touch mechanism so that the LRU cache will mark the Node as "recently" used.
I think this can be added in FastFindCache.getImpl. When you find something with l3Cache.get(k.k2), you shall also touch that in reverse look-up.
This functionality was not present in #9802 implementation either.
#22 Updated by Artur Școlnic 12 months ago
I think that the memory leak in 9802f was caused mainly by the absence of a designated ExpiryListener for the emptyRecordsCache.
Adding the listener, the weak references in Value and Node, and touching the reverse lookup entries on L3 hit should be enough to solve the memory leak and get back the performance improvement.
#23 Updated by Alexandru Lungu 12 months ago
- File memory_leak_mat.png added
I think that the memory leak in 9802f was caused mainly by the absence of a designated ExpiryListener for the emptyRecordsCache.
This is a good catch.
However, in the heap dump I analyzed, emptyRecordsCache was (surprisingly) mostly empty.

#24 Updated by Artur Școlnic 12 months ago
- Status changed from WIP to Review
Alexandru Lungu wrote:
However, in the heap dump I analyzed,
emptyRecordsCachewas (surprisingly) mostly empty.
The other source of the memory leak is the L3 cache and RecordIdentifier strong references that caused them to linger in the memory when the main cache was invalidated.
Anyway, I addressed both these issues and a few others in 10337b/16073. Alexandru, please take a look at the changes.
#25 Updated by Alexandru Lungu 12 months ago
- Priority changed from Normal to Urgent
- Status changed from Review to WIP
Artur, please reassess #10337-14 issue and #10337-19 idea. ReverseLookup collections are a 2D caches. Introducing a LRU cache will only limit the growth on the first dimension (for reverseCache, it means the number of DMO and for emptyRecordsReverseCache it means the number of tables). IMHO, emptyRecordsReverseCache doesn't matter that it is a LRU cache of 100.000. There are less than 100.000 tables usually. The concern is that all cached FINDs on that table are linked in a Value list. That is not bound. The same applies to reverseCache. You can have billions of possible FINDs that identify a certain DMO. That is why the cache ended up leaking in #10337-14.
Previously to your changes, the weak references were allowing ReverseLookup to not retain anything. With your changes, reverseCache and emptyRecordsReverseCache are retaining lists and nodes. In your last commit, the nodes are no longer retaining the l3Cache reference, but it still retains the L2Key and some memory overhead.
I checked for memory leaks with 10337b/16073. There is no visible memory leak in 15 minutes. However, in the memory snapshot, I can see 1 million Node instances already. Each Node retains: 32 bytes for the reference + around 1000 bytes for the L2Key (that contains a character parameter value). All these 1 million Node instances are under the same record identifier. In other words, the LRU cache that is defined to have 100.000 slots has only 1 occupied (by a list of 1M elements). This is constantly growing. It won't take 1hr to crash like in #10337-14, but it will after a couple of hours. Nevertheless, the system will get slower and slower.
Bottomline, please avoid using DMO entity names or record identified as LRU cache keys, but Node instances instead. That is why I suggested having 2 structures per workflow. Considering you have reverseCache and emptyRecordsReverseCache, that means 4 data structures in total. 2 LRU and 2 Maps. More is described in #10337-19.
Please mark this as Urgent.
#26 Updated by Alexandru Lungu 12 months ago
- Priority changed from Urgent to High
#27 Updated by Artur Școlnic 12 months ago
Alexandru, what if we introduce a new type of LRU that has weak references as keys? This will take care of the situations when the L2 or L3 cachs are cleared entirely and reverse lookup entries become orphaned.
The explicit invalidation was already handled correctly.
I would like to avoid having multiple heavy structures, if possible.
#28 Updated by Artur Școlnic 12 months ago
Alternatively, we can disregard the ReveresLookup class in favor of a more light weight solution:
Map<RecordIdentifier<String>, List<L2Key>> reverseLookup = new WeakHashMap<>();
A structure that maps weak references of the record identifiers to the associated L2 keys. On record invalidation, we get the l3 cache from the main FF cache, iterate the associated list of L2Keys and partially invalidate as before.
We make the L3 a LRU cache again, but with a higher capacity, 1000 for example.
This way we have fast invalidation with no additional memory for object allocation, we use the existing references only.
#29 Updated by Artur Școlnic 12 months ago
Artur Școlnic wrote:
Alternatively, we can disregard the ReveresLookup class in favor of a more light weight solution:
A structure that maps weak references of the record identifiers to the associated L2 keys.
I implemented an early version, it looks promising, the only downside is that the L3 is bound again. I think that if we make the reverseLookup a LRU cache with weak referenced keys, we can unbound the L3 cache.
#30 Updated by Alexandru Lungu 12 months ago
I think that if we make the reverseLookup a LRU cache with weak referenced keys, we can unbound the L3 cache.
Technically, a LRU cache with weak keys might ameliorate this problem.
But I want to express my concern of the fact that we are still allowing the reverse-look-up to grow indefinitely on the second axis (linked list). We have no guarantee that the linked list is bound. A weak LRU will only ensure that the list will be evicted from memory more eagerly. But I think there is still a blind spot. In pseudo-code:
repeat i = 1 to 1000000000: find first tt where tt.f1 = i. end.
There is no invalidation in this code, but I think the FFCache will really cache 1 billion finds (especially for NO_RECORD case).
#31 Updated by Artur Școlnic 12 months ago
Alexandru Lungu wrote:
But I want to express my concern of the fact that we are still allowing the reverse-look-up to grow indefinitely on the second axis (linked list).
You are right. The obvious solution to this problem is the use of a custom LRU Linked List instead of a linked list. On main cache hit, we also touch the entry from that list, so the relevant entries are kept at the head of the list. This way we eliminate the need for ReverseLookup as a designated class, we only use something like:
Map<WeakReference<RecordIdentifier<String>>, LRUList<L2Key>> reverseLookup;
#32 Updated by Artur Școlnic 12 months ago
Well, I guess a map with weak references as keys is a WeakHashMap :)
#33 Updated by Alexandru Lungu 12 months ago
Map<RecordIdentifier<String>, LRUList<L2Key>> reverseLookup = new WeakHashMap<>();
Considering the L3 cache for FFCache is unbound, we are now missing a bound on the first axis. The record identifier has the same order of magnitude as the number of records in the database. They will always be retained by the Fast Find Cache in an unbound fashion.
#34 Updated by Artur Școlnic 12 months ago
How about
LRUCache<WeakReference<RecordIdentifier<String>>, LRUList<L2Key>> reverseLookup;?
#35 Updated by Artur Școlnic 12 months ago
Although, invalidation of the L3 caches on revereseLookup/LRUList expiry would be a nightmare. We would need to keep the reference to the Key, not just the L2Key, in order to identify exactly which L3 cache needs invalidation.
#36 Updated by Alexandru Lungu 12 months ago
LRUCache<WeakReference<RecordIdentifier<String>>, LRUList<L2Key>> reverseLookup;
This is something that might work, of course. But this will provide two levels or orthogonal caching. In other words, there will be at most X DMOs cached. Each DMOs will have at most Y entries cached. The total amount of memory required is X * Y. Having it 1000 and 1000 will mean having at most 1000 queries cached per DMO and at most 1000 DMOs with some caching.
Such technique brings us back (more or less) to the OG problem where only 1000 queries were cached per (table, index) pair. We chose to have a consistent caching against all entries in the FFCache by moving the LRU logic in ReverseLookup. In fact, the initial requirement from #9802 was to define in directory.xml that I want X entries and the FastFindCache will have at most X entries at any time (not to starve more intensively used tables or DMOs). Right now we are unsatisfying that requirement by having unreasonable bounds set per number of DMOs or per number of L2Keys for a DMO.
Although, invalidation of the L3 caches on revereseLookup/LRUList expiry would be a nightmare. We would need to keep the reference to the Key, not just the L2Key, in order to identify exactly which L3 cache needs invalidation.
Artur, please follow the idea of #10337-19. I do not understand why are we holding back from having two structures implemented. If you favor code clarity, mind embedding them in a ReverseLookupCache class to abstract away the fact that there are two structures being used.
#37 Updated by Artur Școlnic 12 months ago
- Status changed from WIP to Review
I implemented the idea from #10337-19 (with a few changes) in 10337b/16074.
Alexandru, please review.
#38 Updated by Alexandru Lungu 12 months ago
Review of 10337b/16074:
- rewrite
reverseLookup.lruCache.get(new ReverseLookup.Node(l3Cache, k.k2));toreverseLookup.touch(l3Cache, k.k2). This will avoid exposing Node and lruCache outsideReverseLookup - open/close curly braces for
ReverseLookupconstructor - the expiry listener should invalidate only the expired node, not the whole list (
Value)- this means that something like
node.remove()should be called.
- this means that something like
- avoid putting a node in
valueandlruCacheif already exists. In other words, iflruCachecontains the node, simply skip the whole put process. In the current implementation, nodes are eagely added toValue, but onlyputIfAbsentinlruCache. - add javadoc to equals and hashcode.
return l2Key.hash == node.l2Key.hash;forequalscheck is not correct. Please usel2Key.equals(node.l2Key).- The invalidate should abstract the doubly linked list delete as
node.remove()to avoid duplicate code with expiry of LRU cache. - Important: the
Nodeshould now storeKeyinstead ofL2Key. The LRUCache will not be able to distinguish between queries on different tables / multiplexes / indexes without more context. So an equals and hashcode should be implemented for key as well.
#39 Updated by Artur Școlnic 12 months ago
I addressed the review in rev 16075.
#40 Updated by Alexandru Lungu 12 months ago
- add braces to the lambda inside
ReverseLookupc'tor - Indent
lruCachejavadoc (/** shouldn't be followed by doc; next two lines should be * ..., last line should be only one */) - please move
node = nextNodeas the third clause of the for loop to avoid duplicating it in the conditional and at the end of the loop. - please pre-cache the hash of
Keyto avoid computing it on eachhashCodecall. Node.removeshouldn't have any parameter.- If you remove an expired node from the list, you should no longer try to remove it again. The remove operation is redundant.
- If you forcefully remove a node, then you should also treat the cache eviction there.
reverseCacheis unbound - which is completely fine. But mind that this means removing keys manually from it when the list becomes empty.- If a node expires and it was the last node of the list, then the
reverseCacheshould evict the corresponding record identifier. This can be done in the expiry listener as the values of the LRU cache are the actualValuelists. - If you do
computeIfAbsentinreverseCache, you risk adding a new empty list that may not be updated due to!lruCache.containsKey(node).reverseCacheupdate should be conditioned.
- If a node expires and it was the last node of the list, then the
- are the operations thread-safe considering that some FFCaches are cross-session? Is the expiry, touching, etc. operations thread-safe?
- I still want to understand if
getCanonicalRecordIdentifierstill fits in the current implementation and how.
#41 Updated by Artur Școlnic 12 months ago
Alexandru Lungu wrote:
- If a node expires and it was the last node of the list, then the reverseCache should evict the corresponding record identifier. This can be done in the expiry listener as the values of the LRU cache are the actual Value lists.
On expiry or removal of a node, the corresponding L3 cache entries are removed, which means that the weak references in reverseCache will be garbage collected.
- are the operations thread-safe considering that some FFCaches are cross-session? Is the expiry, touching, etc. operations thread-safe?
All the explicit put/get operations on the caches are synchronized in the persistent tables case. The only questionable thing is entry expiry, which is not synchronized in the LRUCache implementation, nor externally. Just to be sure I enclosed the expiry listener code in a synchronized block.
- I still want to understand if
getCanonicalRecordIdentifierstill fits in the current implementation and how.
It is used to get the true reference to a RecordIdentifier since we are working with weak references, so it is still useful.
I addressed the other issues in rev 16076.
#42 Updated by Alexandru Lungu 12 months ago
- Please make
Nodeclass static again. - short-circuit
equalsso thatthis == oreturns true directly. - Important: I think
value.headis inconsistent after removal. If you remove a node, thenvalue.headwill still reference it, right? So when you add a new node, it will bring it back to life as it will be linked asnode.next = head.- This also means that
value.head == nullwon't ever be hit after invalidation?
- This also means that
- The synchronization in ReverseLookup c'tor is done on
ReverseLookup, while other synchronization is done onFastFindCacheinstance. These locks are unrelated. I incline to say that the expiry is done eagerly and synchronous onensureCapacity(so onput). That means that ifputis synchronized, the expiry listener is also called in a thread-safe manner. I think the synchronize can be removed. hashclass member is not read.
If a node expires and it was the last node of the list, then the reverseCache should evict the corresponding record identifier. This can be done in the expiry listener as the values of the LRU cache are the actual Value lists.
- This is reasonable, but requires testing to confirm.
#43 Updated by Artur Școlnic 12 months ago
Alexandru, I think we have an issue with the Key objects retention.
I did a memory profiling and Keys are accumulating at a steady rate, this causes Nodes to also stay in memory. Not sure what is causing this, maybe the fact that we use references to a Key to put objects in the ff cache, but primitive values to remove from it?.. Anyway, it is not entirely clear to me when a Key should be removed or GC`ed.
#44 Updated by Artur Școlnic 12 months ago
Alexandru Lungu wrote:
- Important: I think
value.headis inconsistent after removal. If you remove a node, thenvalue.headwill still reference it, right? So when you add a new node, it will bring it back to life as it will be linked asnode.next = head.
You are right, this also caused the issue from note 43. After fixing this problem, the Nodes are kept at a reasonable number, still more than the lruCache limit, but this is due to using weak references everywhere, AFAIK weak references do not guarantee a speedy garbage collection, the objects can linger in memory until the GC is forced to clear some memory.
I addressed the other issues, please review.
#45 Updated by Alexandru Lungu 12 months ago
You are right, this also caused the issue from note 43. After fixing this problem, the Nodes are kept at a reasonable number, still more than the lruCache limit, but this is due to using weak references everywhere, AFAIK weak references do not guarantee a speedy garbage collection, the objects can linger in memory until the GC is forced to clear some memory.
I would rather be more eager with this then. This will put less pressure on the GC, especially in tight loops that use FIND. Please add eager removes of empty lists from the reverse look-up cache.
#46 Updated by Artur Școlnic 12 months ago
This actually is implemented for the explicit invalidation, but it needed to be added for the expiry event also.
I committed the changes.
#47 Updated by Alexandru Lungu 12 months ago
- Status changed from Review to Internal Test
Review of 10337b:
hash = hashCode();andpublic int hashCode() { return hash; }is not correct.- there is a 4 space ident in
ReverseLookupc'tor
I am ok with the changes overall. Please fix the last two points and continue with testing.
#48 Updated by Artur Școlnic 12 months ago
I think we are still leaking memory, after one hour or running Harness on loop, there are 500k 'live' Node objects, after a force GC, 300k remained, which is way above the limit of 100k. They are consuming an insignificant amount of memory, but that usage has a linear growth. I think clearing L1/2 caches does not clean up the associated Nodes as well. Will investigate further.
#49 Updated by Alexandru Lungu 12 months ago
I think we are still leaking memory, after one hour or running Harness on loop, there are 500k 'live' Node objects, after a force GC, 300k remained, which is way above the limit of 100k. They are consuming an insignificant amount of memory, but that usage has a linear growth. I think clearing L1/2 caches does not clean up the associated Nodes as well. Will investigate further.
You can decrease the cache size to something like 10 for more straight-forward testing. Or maybe even to 1 or 2 :)
#50 Updated by Artur Școlnic 12 months ago
The same thing happens in trunk, the number of leaked Node instances grows slowly, but surely. After 20 mins of harness, the number of live Nodes went from 400 to 7000.
I am assuming that the 'live objects' in VisualVm means objects that are still stored in memory and can't be GC'ed.
#51 Updated by Artur Școlnic 12 months ago
Alexandru Lungu wrote:
You can decrease the cache size to something like 10 for more straight-forward testing. Or maybe even to
1or2:)
The limit of the lruCache does not matter, it is honored anyway, the problem is that we end up having Nodes that are not referenced in the lruCache, but are somewhere else.
#52 Updated by Artur Școlnic 12 months ago
I think the issue is that bulk invalidation where entire caches are cleared or removed do not clean up the associated Value/Nodes, in order to properly clean those, we would need to go through all L3 entries and invalidate one by one.
#53 Updated by Alexandru Lungu 12 months ago
Artur Școlnic wrote:
I think the issue is that bulk invalidation where entire caches are cleared or removed do not clean up the associated Value/Nodes, in order to properly clean those, we would need to go through all L3 entries and invalidate one by one.
No. The LRU cache policy should have taken care of this, without the need to manually seek and destroy.
#54 Updated by Artur Școlnic 12 months ago
I may have misunderstood things a bit :) Apparently, there are multiple instances of ReverseLookup, which means multiple instances of lruCaches. For the application I am testing with, there are 7 instances, i set the limit to 100 records, the number of nodes does climb above 700, but after a GC, the total number never exceeds 700, so I guess this was a false alarm.
There is one change I made:
=== modified file 'src/com/goldencode/p2j/persist/FastFindCache.java'
--- old/src/com/goldencode/p2j/persist/FastFindCache.java 2025-08-06 11:51:43 +0000
+++ new/src/com/goldencode/p2j/persist/FastFindCache.java 2025-08-07 10:35:02 +0000
@@ -1027,7 +1027,7 @@
}
value.addNode(node);
reverseCache.put(recID, value);
- lruCache.put(node, value);
+ lruCache.put(node, null);
}
else
{
I just realized that we don't actually need the values in the lruCache, it acts more like a queue, so only the nodes are needed.
#55 Updated by Alexandru Lungu 12 months ago
I just realized that we don't actually need the values in the lruCache, it acts more like a queue, so only the nodes are needed.
The value is used in the expiry listener.
#56 Updated by Artur Școlnic 12 months ago
Forgot about that, I'll uncommit.
#57 Updated by Artur Școlnic 12 months ago
Testing status:
Harness and webui navigation - passed
Large customer application FWDTests and unit tests - passed
Large gui application unit tests - passed
I also monitored memory allocation over time, it seems ok. There are still an excessive amount of nodes created and alive in memory, but on GC they are erased.
#58 Updated by Alexandru Lungu 12 months ago
- % Done changed from 90 to 100
- Status changed from Internal Test to Merge Pending
I also monitored memory allocation over time, it seems ok. There are still an excessive amount of nodes created and alive in memory, but on GC they are erased.
Please merge 10337b to trunk after 10353a.
#59 Updated by Alexandru Lungu 11 months ago
Please merge 10337b to trunk after 10004a.
#60 Updated by Artur Școlnic 11 months ago
10337b was merged as rev 16106.
#61 Updated by Alexandru Lungu 11 months ago
- Status changed from Merge Pending to Test