-
Notifications
You must be signed in to change notification settings - Fork 426
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix for Classloader leak issue #465
Conversation
@peterbae, |
Codecov Report
@@ Coverage Diff @@
## dev #465 +/- ##
============================================
- Coverage 46.27% 46.23% -0.05%
- Complexity 2201 2206 +5
============================================
Files 108 108
Lines 25260 25239 -21
Branches 4176 4175 -1
============================================
- Hits 11688 11668 -20
- Misses 11547 11556 +9
+ Partials 2025 2015 -10
Continue to review full report at Codecov.
|
@peterbae, thanks for signing the contribution license agreement. We will now validate the agreement and then the pull request. |
Don't you need proper synchronization when accessing a shared object (the map)? |
Hi @marschall, I thought about making the map synchronized - it's true that the map is shared by multiple threads, but since each entry in the map is only going to be modified by one thread (the thread can only access the entry in the map that shares the same key as its thread ID), I do not believe any two threads will ever try to access/modify the same entry in the map, and therefore this operation should be thread safe. As for the second point, I agree. I was thinking the map would get removed when the app server gets undeployed, but it may never get undeployed, in which case the map would keep holding entries that will never be used again. I've made changes so that a thread will remove its own entry in the map when its connection gets closed (which should be thread safe for the same reason above). |
That's not how thread safety in Java works at all. And it's not true since the same entry is modified in case of a hash collision. In addition the size, the modification count, the backing array and the pointer to the backing array are modified by all threads. For a quick introduction into the JMM I recommend the following articles https://shipilev.net/blog/2014/safe-public-construction/
You mean the application gets undeployed? That doesn't fix your issue because the database driver should not be part of the application deployment and therefore it does not become eligible for garbage collection.
Not in an application server with a connection pool. The worker threads calling methods on on driver objects and the threads opening and closing the connection can be different ones. |
Thanks @marschall for your insight. It was an oversight on my end to disregard the hash collision and misusing HashMap here. I've replaced the HashMap with a ConcurrentHashMap. For the issue where the static map that holds ActivityId may not be removing the unused entries due to the scenario where the threads that are using the driver object and closing the connection being different, I believe we need a way to track the threads that are alive and periodically remove entries from the map. However, this will likely introduce performance degradation and will need further input from the team, and consider if the potential memory being held by the map is worth the performance gain. Meanwhile, since I believe the classloader leak issue itself is fixed, I think it's still good to merge this in unless more problems are found with this solution. |
src/main/java/com/microsoft/sqlserver/jdbc/ActivityCorrelator.java
Outdated
Show resolved
Hide resolved
src/main/java/com/microsoft/sqlserver/jdbc/ActivityCorrelator.java
Outdated
Show resolved
Hide resolved
src/main/java/com/microsoft/sqlserver/jdbc/ActivityCorrelator.java
Outdated
Show resolved
Hide resolved
Thanks @rPraml, I've made the refactoring changes and also made some commenting changes. |
@peterbae I do not see any major problems in your fix, It's true that you have a memory leak when you start & stop a lot of threads AND Util.IsActivityTraceOn() = true. But I think this behaviour is better than the classloading issue. I also tried to find out what ActivityId is good for. I also found often this code block: if (loggerExternal.isLoggable(Level.FINER) && Util.IsActivityTraceOn()) {
loggerExternal.finer(toString() + " ActivityId: " + ActivityCorrelator.getNext().toString());
} If I interpret this right, the logger statement calls ActivityCorrelator.getNext() - which will increment the ID. This means, the ActivityId counts different if log level is FINER or not. This looks like a bug/mistake... Maybe some other people from DEV can take a look at the ActivityCorrelator. If this is really only for debugging (https://msdn.microsoft.com/en-us/library/hh269638.aspx) maybe you can use a static AtomicInteger and remove some complexity? |
@rPraml We call ActivityCorrelator.getNext so that we increment the sequence and then log it. If we were to call getCurrent instead here, we would always be logging the same sequence since we're never incrementing it (it also seems like we're only concerned with logging when the log level is FINER), so getNext is fine here. As for the ActivityCorrelator, it's also used in other parts of the driver to store if a message was sent to the server (in TDSWriter, for example), so it's not only used for debugging. |
@peterbae thanks for clarifying. |
@rPraml yes. As discussed above, the issue with this solution is that the ActivityId objects (which are relatively small) are potentially being held in the static map in ActivityCorrelator in an application server with a connection pool, but in order to clean up the ActivityId objects in such scenario, the driver would see some performance degradation. I would still like to merge this fix as an improvement. |
@marschall @rPraml please let me know if there's other issues with this fix, otherwise I will merge it in. Thanks. |
Fixes issue #314
I put a comment as to why I took this approach for solving this problem in that issue as well.