-
Notifications
You must be signed in to change notification settings - Fork 427
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
Executor service instead of thread #162
Executor service instead of thread #162
Conversation
Codecov Report
@@ Coverage Diff @@
## dev #162 +/- ##
===========================================
+ Coverage 29.7% 31.52% +1.82%
- Complexity 1248 1361 +113
===========================================
Files 97 97
Lines 23305 23312 +7
Branches 3871 3872 +1
===========================================
+ Hits 6923 7350 +427
+ Misses 15028 14530 -498
- Partials 1354 1432 +78
Continue to review full report at Codecov.
|
I'm sorry but this was not the point of my request. A shared SCHEDULED executor service creates tasks that might be executed in the future instead of starting threads immediately (i.e. threads and tasks are separated, this pull request does not change that). That has the potential to make a real difference in performance. To be really effective, the shared scheduled executor service should be created when the first connection is created and shutdown when the last connection is closed, and is shared/re-used among all connections that need to schedule tasks. |
timerThread.start(); | ||
//if ExecutorService is shutdown already, reset it | ||
if(executor.isShutdown()){ | ||
executor = Executors.newCachedThreadPool(); |
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.
The executor does not create daemon Threads by default, thus the result may be a little different.
I would probably use a thread factory as a parameter that use daemon threads.
@pierresouchay Thank you very much! Thank you for pointing this out. I don't need to worry about shutting down executor manually if I set them as daemon threads. |
Yes, this will improve the situation: threads will get re-used which takes away the relative expensive creating and starting of threads each time. I also had a re-think about my comment on shutdown of the executor service. I think using daemon-threads is sufficient: it will not prevent an application from shutting down and in the case of war-files / redeployable web applications the general advice is to always put the JDBC-drivers in the container (the "common library directory") and not package the JDBC drivers with the war-files - if that advice is followed there will be no memory leak problem either. |
@fwi Thank you for getting back to us with your thoughts. Again, we really appreciate your suggestions on this improvement. Right now, we are running more tests for this implementation, and will merge the PR after those tests are done. Thank you. |
private static final ExecutorService executor = Executors.newCachedThreadPool(new ThreadFactory() { | ||
@Override | ||
public Thread newThread(Runnable r) { | ||
Thread t = Executors.defaultThreadFactory().newThread(r); |
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.
Another way would be to use a dedicated ThreadGroup (for instance with name SQLServerDriver) and to create threads regarding this behaviour.
For instance:
private static final ExecutorService executor = Executors.newCachedThreadPool(new ThreadFactory() {
final ThreadGroup tg = new ThreadGroup("SQLServerDriver");
final AtomicInteger counter = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(tg, "sqlserverdriver-"+counter.incrementAndGet()
t.setDaemon(true);
return t;
}
}
Having a dedicated ThreadGroup and well named threads would ease debug in complex software, such as application servers when there are many threads and would help identifying the ones created by the driver.
(While ThreadGroup may be overkill, at least having a good name for threads help a lot)
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.
Good idea. I will talk to the team and see what name we can give to the thread group. Thank you.
} | ||
|
||
final void stop() { | ||
task.cancel(false); |
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.
This must be cancel(true)
like in the original implementation (timerThread.interrupt was called). Else new threads will be started. E.g. if 10 queries are fired with a time-out within one second (which is the sleep-interval in the run-method), 10 threads will be created.
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.
Thank you, good catch. I agree. Going to change to true now.
@v-xiangs : Can you add Connection Pool test cases on this PR? |
con = ds.getConnection(); | ||
con.isValid(5); | ||
|
||
pst = con.prepareStatement("SELECT SUSER_SNAME()"); |
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.
For testing timeout: pst.setQueryTimeout(5);
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.
hello @fwi , could you please inform me what the benefits are by testing setQueryTimeout() instead of isValid()? from my understanding, both create a new thread for the timeout timer
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.
Currently "isValid" ends up using the timeout timer, but this is an implementation choice (other JDBC drivers do it differently for speed and this JDBC driver might one day too) and might change in the future. Also "isValid" is already called by the connection pool before returing a connection from the pool (see for example "validationTimeout" in HikariCP).
Besides that, the original issue was about "queries with a time-out", so why not test that explicitly and make sure to use a query with a time-out?
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.
that makes sense, I will change it.
config.setJdbcUrl(connectionString); | ||
HikariDataSource ds = new HikariDataSource(config); | ||
|
||
connect(ds); |
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.
Testing performance with 'trivial queries', so add a loop:
try {
int ttStartCount = countTimeoutThreads();
for (int i = 0; i < 100; i++) {
connect(ds);
}
int ttEndCount = countTimeoutThreads();
if (ttEndCount - ttStartCount > 2) {
throw new AssertionError("Too many timeout threads started.");
}
} finally {
// always close the datasource
ds.close();
}
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.
@fwi : Thanks for suggestions. We are trying out Java MicroBenchmark Harness (JMH)
test cases for performance testing. We are figuring out how & when we can open performance test cases. At the same time we want our Travis-CI & Appveyour builds should not be long running.
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.
Bad choice of words on my part: this is actually a functional test. Remember when we changed a little bit of code to prevent creating lots of threads when many queries are executed rapidly? How do you know there is not another mistake like that still in the code? This test simply measures the amount of threads before and after executing many queries rapidly. Measuring is knowing.
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.
On a side note: the datasource backed by the pool implementation does need to be closed when the test is finished. If not, connections remain open and the pool implementation itself starts threads as well that are shutdown when the datasource is closed.
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.
Final note: verify ttEndCount
is at least 1 to ensure the test is working (if ttEndCount
is zero, the test did not do its intended job).
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.
instead of doing if (ttEndCount - ttStartCount > 2) { throw exception}
, I guess only verify ttEndCount
is at least 1 is sufficient. In the for loop, if we increase the termination (e.g. to 100000), it may create more than 2 timeout threads.
|
||
connect(ds); | ||
} | ||
|
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.
Example for thread-counts (untested):
public static int countTimeoutThreads() {
ThreadInfo[] tinfos = ManagementFactory.getThreadMXBean().getThreadInfo(ManagementFactory.getThreadMXBean().getAllThreadIds(), 0);
// IOBuffer.TimeoutTimer.threadGroupName should be publicly accessible ...
final String toname = "mssql-jdbc-TimeoutTimer";
int count = 0;
for (ThreadInfo ti : tinfos) {
if (ti.getThreadName().startsWith(toname) && !ti.getThreadState().equals(java.lang.Thread.State.TERMINATED)) {
count++;
}
}
return count;
}
for issue #150