google / gwtmockito

Better GWT unit testing
https://google.github.io/gwtmockito
Apache License 2.0
157 stars 50 forks source link

Fix memory leak when ThreadLocal are used inside tested code #79

Closed LudoPL closed 4 years ago

LudoPL commented 5 years ago

This is a proposal of a fix (or a beginning of a fix) for https://github.com/google/gwtmockito/issues/53

Explanation of the issue (sorry if it's not very clear): Mockito is using the ThreadLocal java feature to keep a context with instances of MockitoConfiguration and MockingProgressImpl between calls. GwtMockitoTestRunner set a specific ClassLoader in the TreadContext before running each test and restore the previous one at the end. The JVM ThreadLocal implementation stored the thread contextual information in a map in the thread where the key is a ThreadLocal instance and the value the object stored. These ThreadLocal instances are referenced statically by two mockito classes loaded through the GwtMockitoClassLoader. This mean these instances can not be garbage collected until the GwtMockitoClassLoader that loaded them is. It also mean, as there is several instances of the GwtMockitoClassLoader, a same static field definition in the source code will reference a different object in the different version of the classes loaded in the different class loaders. These instances can not be garbage collected until the GwtMockitoClassLoader that loaded them is. So as there is several instances of the GwtMockitoClassLoader, a same static field definition in the source code will reference a different object in the different version of the classes loaded in the different class loaders. The entries in the ThreadLocalMap won't be removed from the Map while the key is not garbage collected (a weak reference is used in the Map for the key so it's not the map itself that directly prevent the garbage collection of the key). So the DefaultMockitoConfiguration and MockingProgressImpl objects stored as value in the ThreadLocalMap won't be removed until the GwtMockitoClassLoader that created their associated map keys is garbage collected (the value of the ThreadLocalMap are note weakly referenced themselves). The probem is that these map value objects are them selves loaded by the GwtMockitoClassLoader. So they prevent the garbage collection of the GwtMockitoClassLoader instances that created. Thus there is a references loop and memory leak thak keeps all the GwtMockitoClassLoader created for each tests in the memory stack of the thread that run them. In order to break this references loop, the patch proposes to remove all values loaded by the gwtMockitoClassLoader and stored in the ThreadLocalMap at the end of the test run.

Note about the patch:

Modifications done in the patch:

googlebot commented 5 years ago

Thanks for your pull request. It looks like this may be your first contribution to a Google open source project (if not, look below for help). Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

:memo: Please visit https://cla.developers.google.com/ to sign.

Once you've signed (or fixed any issues), please reply here (e.g. I signed it!) and we'll verify it.


What to do if you already signed the CLA

Individual signers
Corporate signers

ℹ️ Googlers: Go here for more info.

LudoPL commented 5 years ago

I signed it!

googlebot commented 5 years ago

CLAs look good, thanks!

ℹ️ Googlers: Go here for more info.

ekuefler commented 5 years ago

This is some awesome investigation! Thanks so much for figuring out what was going on and putting together a fix. It's obviously a huge hack, but everything about GwtMockito is a huge hack, so that's not a dealbreaker :)

I think it makes sense to allow what you've defined here as an experimental opt-in feature. Like you said, tying all GwtMockito tests to the implementation details of Thread probably isn't the best idea, and it's probably only a very small percentage of users who are having problems with memory leaks. So here's what I'd suggest:

How does that sound? Thanks again for looking at it!

LudoPL commented 5 years ago

Thanks for the remarks, I have tried to implement them, hope it will match what you have in mind. You are right it is better to make this clean optional and to isolate the logic ! Do not hesitate to do or ask for other modifications. And also thanks for your project, it is very useful !

ekuefler commented 4 years ago

Thanks again for working on this!

cinlloc commented 4 years ago

Thanks for this PR!