We have a Datasnap application in which we use UniDAC components to connecto to Firebird database. Because of the design of our application, each connected client gets two database connections. I decided to try Connection Pooling available in TUniConnection for both of the connections.
When testing application with Apache jMeter, I experienced some strange behaviour. Initial properties were the defaults - MaxPoolSize 100 and MinPoolSize 0.
In the first test I simulated 100 clients logging in at the same time. In this test everything was as expected, all the clients were successufully logged in.
In the second test I simulated 110 clients logging in. This number is a bit above MaxPoolSize. In that test case the unusual result was that all the threads were blocked when 101. thread was waiting for the connection to become available. Only after 30 seconds other threads could continue.
I made a simple test with only two threads, each with two database connections with MaxPoolSize set to one connection.
Thread Execute method looks like this:
Code: Select all
FConn1 := TUniConnection.Create(nil);
InitDB(FConn1, 'employee.fdb');
FConn2 := TUniConnection.Create(nil);
InitDB(FConn2, 'employee2.fdb');
// First pool
FConn1.Open();
// Second pool
FConn2.Open();
So, what happens when the threads are started?
1. T1 opens successfully FConn1 using the connection from P1
- switch to T2
2. T2 tries to open FConn1 but it can't because there are not available connections in the pool P1.
Under the hood T2 did the following:
a. Entered the critical section FLockGet at
CRConnectionPool.pas TCRConnectionPoolManager.InternalGetConnection() method.
b. Waits for TEvent hBusy at
CRConnectionPool.pas TCRLocalConnectionPool.GetConnection() method
Observe that T2 still holds FLockGet critical section locked.
- switch to T1
3. T1 tries to open FConn2 but it gets stuck on FLockGet.Enter at the same code location in step 2.a.
Now, T1 and T2 are both suspended. T1 waits for FLockGet critical section and T2 for hBusy event.
Because nothing is happening, only thing that can happen is timeout on hBusy (after 30 seconds).
- after 30 seconds T2 continues
4. Exception is raised because of the timeout (SMaxConnectionsReached)
FLockGet is unlocked.
- switch to T1
5. T1 can now continue. It calls GetConnectionPool() but it will not get P1, but just create P2 (because ConnectionParameters.Equals() fails on P1 - different Database property).
My conclusion is that the T1 and T2 in the above example were not in race condition. T1 did not need to wait on FLockGet because it would eventually use different pool.
The bigger problem is on our Datasnap application because of unnecessary lag.
From the documentation I thought that this should work.
Am I doing something wrong here?
Is there any way around the problem?
Delphi version: XE6 (Update 1) 20.0.16277.1276
UniDAC version is 6.1.3.
I know there is newer version, but there is nothing in changelog that states something has changed with behaviour of connection pools.
Kind regards,
Bero