-
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
Replace timeout handling to use SharedTimer #920
Replace timeout handling to use SharedTimer #920
Conversation
Codecov Report
@@ Coverage Diff @@
## dev #920 +/- ##
============================================
- Coverage 50.46% 50.36% -0.11%
+ Complexity 2932 2929 -3
============================================
Files 120 120
Lines 28117 28100 -17
Branches 4696 4694 -2
============================================
- Hits 14190 14152 -38
- Misses 11629 11645 +16
- Partials 2298 2303 +5
Continue to review full report at Codecov.
|
Replaces the timeout handling for basic and bulk TDS commands to use a new SharedTimer class. SharedTimer provides a static method for fetching an existing static object or creating one on demand. Usage is tracked through reference counting and callers are required to call removeRef() when they will no longer be using the SharedTimer. If the SharedTimer does not have any more references then its internal ScheduledThreadPoolExecutor will be shutdown. The SharedTimer is cached at the Connection level so that repeated invocations do not create new timers. Connections only create timers on first use so if no actions involve a timeout then no timer is fetched or created. If a Connection does create a timer then it will be released when the Connection closed. Properly written JDBC applications that always close their Connection objects when they are finished using them should not have any extra threads running after they are all closed. Applications that do not use query timeouts will not have any extra threads created as they are only done on demand. Applications that use timeouts and use a JDBC connection pool will have a single shared object across all JDBC connections as long as there are some open connections in the pool with timeouts enabled. Interrupt actions to handle a timeout are executed in their own thread. A handler thread is created when the timeout occurs with the thread name matching the connection id of the client connection that created the timeout. If the timeout is canceled prior to the interrupt action being executed, say because the command finished, then no handler thread is created. Note that the sharing of the timers happens across all Connections, not just Connections with the same JDBC URL and properties.
Centralize checks for SharedTimer thread state in TimeoutTest to happen before and after all test invocations in TimeoutTest via JUnit @before and @after annotations. Each test now requires that the SharedTimer thread not be running before the test starts and after the test completes. Also expands the thread name prefix in SharedTimer to package private so that TimeoutTest can reference it rather than having its own copy fo the string.
Centralizes constant timeout value used in TimeoutTest and reduces it from ten seconds down to two seconds to speed up tests.
Centralizes the SQL command for WAIT FOR DELAY in TimeoutTest and changes the format of the delay to use hour:minute:second for clarity.
Changes test in TimeoutTest to use assertThrows(...) rather than manually checking for the thrown exception. Also cleans up and adds internal helpers for creating a connection and changes internal runQuery(...) to be void rather than returning a boolean.
Adds additional tests to verify creation and shutdown of SharedTimer core thread when user commands specify a query timeout.
fd2f3d3
to
e76e819
Compare
Rebased atop latest dev branch and couple of additional commits for cleanup and tests added. The first commit is the same as before (rebased on dev). The rest are broken down into small chunks to simplify review. Here's a summary:
I've left in the |
Change waiting for SharedTimer thread to stop to happen in a loop with periodic checks to see if the thread has stopped up to a max amount of waiting (10 seconds).
Looks like Travis passed fine but AppVeyor. I'm thinking it's just AppVeyor running a tad slower so it didn't have enough time to complete and clean up the thread. I've pushed another commit that changes the wait logic to repeatedly check if the thread is stopped in a loop for up to 10-seconds (previously it was 500ms). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few changes required for standardization (Reported by Static Code Analysis)
🎉 |
PR to fix #909 following the reference counting model outlined in the comments. This is an alternative to #914. Was easier to put together a separate PR as the code changes were completely different.
This PR does not track the actual references or connection IDs, just a total count of how many times the SharedTimer has been requested (increment reference) and released (decrement reference). Connections that make use of the SharedTimer cache a reference to it and clean it up in
Connection.close()
. If the reference count drops to zero then the SharedTimer is shutdown and the core thread used for scheduling interrupt tasks is stopped.Timeout that are actually triggered, i.e. the timer expired and the action hasn't completed yet, create a new thread on the fly to execute the interrupt action. This is to ensure that they do not block the core timer thread. Timeouts that are cancelled prior to expiring, i.e. query completed prior to timeout, do not create a new handler thread.
All current tests, including timeout tests, pass with this PR but there are no new tests yet to check if things are being cleaned up correctly. Manual testing via jconsole shows that it is but it'd be nice to eventually include something in the core test suite to cover that. Something similar to cheenamalhotra#20 should work though we'd have to deal with the issue of the rest of the test suite cleaning up all its resources prior to running (probably a good idea anyway!).
Alternatively we could have the thread leak tests be in their own test suite that is only run when explicitly enabled. Travis / Appveyor could then run them in isolation as a secondary step via:
ENABLE_TIMEOUT_TESTS=true mvn -Dtest=com.microsoft.sqlserver.jdbc.timeouts.TimeoutThreadLeakTest test
I'll be taking a look at adding the tests when I have some more time but if someone else wants to do it in the meantime I can review.
Couple of notes on minor changes in behavior:
ScheduledThreadPoolExecutor
which internally sleeps until the next operation will execute. Previously they were being manually polled every 1-second so it was possible for a timeout to not execute for 2-seconds (ex: poller is running at t=0, t=1, t=2, ... and timeout is scheduled at t=.01). They will now execute as soon as they need to run.schedulerLock
, so in practice it'd be microseconds of difference.