@@ -22,8 +22,17 @@ public async Task<MySqlSession> GetSessionAsync(IOBehavior ioBehavior, Cancellat
22
22
23
23
try
24
24
{
25
- // check for a pooled session
26
- if ( m_sessions . TryDequeue ( out var session ) )
25
+ // check for a waiting session
26
+ MySqlSession session = null ;
27
+ lock ( m_sessions )
28
+ {
29
+ if ( m_sessions . Count > 0 )
30
+ {
31
+ session = m_sessions . First . Value ;
32
+ m_sessions . RemoveFirst ( ) ;
33
+ }
34
+ }
35
+ if ( session != null )
27
36
{
28
37
if ( session . PoolGeneration != m_generation || ! await session . TryPingAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) )
29
38
{
@@ -42,6 +51,7 @@ public async Task<MySqlSession> GetSessionAsync(IOBehavior ioBehavior, Cancellat
42
51
}
43
52
}
44
53
54
+ // create a new session
45
55
session = new MySqlSession ( this , m_generation ) ;
46
56
await session . ConnectAsync ( m_connectionSettings , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
47
57
return session ;
@@ -73,7 +83,8 @@ public void Return(MySqlSession session)
73
83
try
74
84
{
75
85
if ( SessionIsHealthy ( session ) )
76
- m_sessions . Enqueue ( session ) ;
86
+ lock ( m_sessions )
87
+ m_sessions . AddFirst ( session ) ;
77
88
else
78
89
session . DisposeAsync ( IOBehavior . Synchronous , CancellationToken . None ) . ConfigureAwait ( false ) ;
79
90
}
@@ -87,45 +98,85 @@ public async Task ClearAsync(IOBehavior ioBehavior, CancellationToken cancellati
87
98
{
88
99
// increment the generation of the connection pool
89
100
Interlocked . Increment ( ref m_generation ) ;
101
+ await CleanPoolAsync ( ioBehavior , session => session . PoolGeneration != m_generation , false , cancellationToken ) . ConfigureAwait ( false ) ;
102
+ }
90
103
91
- var waitTimeout = TimeSpan . FromMilliseconds ( 10 ) ;
92
- while ( true )
104
+ public async Task ReapAsync ( IOBehavior ioBehavior , CancellationToken cancellationToken )
105
+ {
106
+ if ( m_connectionSettings . ConnectionIdleTimeout == 0 )
107
+ return ;
108
+ await CleanPoolAsync ( ioBehavior , session => ( DateTime . UtcNow - session . LastReturnedUtc ) . TotalSeconds >= m_connectionSettings . ConnectionIdleTimeout , true , cancellationToken ) . ConfigureAwait ( false ) ;
109
+ }
110
+
111
+ private async Task CleanPoolAsync ( IOBehavior ioBehavior , Func < MySqlSession , bool > shouldCleanFn , bool respectMinPoolSize , CancellationToken cancellationToken )
112
+ {
113
+ // synchronize access to this method as only one clean routine should be run at a time
114
+ if ( ioBehavior == IOBehavior . Asynchronous )
115
+ await m_cleanSemaphore . WaitAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
116
+ else
117
+ m_cleanSemaphore . Wait ( cancellationToken ) ;
118
+
119
+ try
93
120
{
94
- // try to get an open slot; if this fails, connection pool is full and sessions will be disposed when returned to pool
95
- if ( ioBehavior == IOBehavior . Asynchronous )
96
- {
97
- if ( ! await m_sessionSemaphore . WaitAsync ( waitTimeout , cancellationToken ) . ConfigureAwait ( false ) )
98
- return ;
99
- }
100
- else
121
+ var waitTimeout = TimeSpan . FromMilliseconds ( 10 ) ;
122
+ while ( true )
101
123
{
102
- if ( ! m_sessionSemaphore . Wait ( waitTimeout , cancellationToken ) )
103
- return ;
104
- }
124
+ // if respectMinPoolSize is true, return if (leased sessions + waiting sessions <= minPoolSize)
125
+ if ( respectMinPoolSize )
126
+ lock ( m_sessions )
127
+ if ( m_connectionSettings . MaximumPoolSize - m_sessionSemaphore . CurrentCount + m_sessions . Count <= m_connectionSettings . MinimumPoolSize )
128
+ return ;
129
+
130
+ // try to get an open slot; if this fails, connection pool is full and sessions will be disposed when returned to pool
131
+ if ( ioBehavior == IOBehavior . Asynchronous )
132
+ {
133
+ if ( ! await m_sessionSemaphore . WaitAsync ( waitTimeout , cancellationToken ) . ConfigureAwait ( false ) )
134
+ return ;
135
+ }
136
+ else
137
+ {
138
+ if ( ! m_sessionSemaphore . Wait ( waitTimeout , cancellationToken ) )
139
+ return ;
140
+ }
105
141
106
- try
107
- {
108
- if ( m_sessions . TryDequeue ( out var session ) )
142
+ try
109
143
{
110
- if ( session . PoolGeneration != m_generation )
144
+ // check for a waiting session
145
+ MySqlSession session = null ;
146
+ lock ( m_sessions )
147
+ {
148
+ if ( m_sessions . Count > 0 )
149
+ {
150
+ session = m_sessions . Last . Value ;
151
+ m_sessions . RemoveLast ( ) ;
152
+ }
153
+ }
154
+ if ( session == null )
155
+ return ;
156
+
157
+ if ( shouldCleanFn ( session ) )
111
158
{
112
- // session generation does not match pool generation ; dispose of it and continue iterating
159
+ // session should be cleaned ; dispose it and keep iterating
113
160
await session . DisposeAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
114
- continue ;
115
161
}
116
162
else
117
163
{
118
- // session generation matches pool generation; put it back in the queue and stop iterating
119
- m_sessions . Enqueue ( session ) ;
164
+ // session should not be cleaned; put it back in the queue and stop iterating
165
+ lock ( m_sessions )
166
+ m_sessions . AddLast ( session ) ;
167
+ return ;
120
168
}
121
169
}
122
- return ;
123
- }
124
- finally
125
- {
126
- m_sessionSemaphore . Release ( ) ;
170
+ finally
171
+ {
172
+ m_sessionSemaphore . Release ( ) ;
173
+ }
127
174
}
128
175
}
176
+ finally
177
+ {
178
+ m_cleanSemaphore . Release ( ) ;
179
+ }
129
180
}
130
181
131
182
public static ConnectionPool GetPool ( ConnectionSettings cs )
@@ -144,25 +195,47 @@ public static ConnectionPool GetPool(ConnectionSettings cs)
144
195
145
196
public static async Task ClearPoolsAsync ( IOBehavior ioBehavior , CancellationToken cancellationToken )
146
197
{
147
- var pools = new List < ConnectionPool > ( s_pools . Values ) ;
148
-
149
- foreach ( var pool in pools )
198
+ foreach ( var pool in s_pools . Values )
150
199
await pool . ClearAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
151
200
}
152
201
202
+ public static async Task ReapPoolsAsync ( IOBehavior ioBehavior , CancellationToken cancellationToken )
203
+ {
204
+ foreach ( var pool in s_pools . Values )
205
+ await pool . ReapAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
206
+ }
207
+
153
208
private ConnectionPool ( ConnectionSettings cs )
154
209
{
155
210
m_connectionSettings = cs ;
156
211
m_generation = 0 ;
212
+ m_cleanSemaphore = new SemaphoreSlim ( 1 ) ;
157
213
m_sessionSemaphore = new SemaphoreSlim ( cs . MaximumPoolSize ) ;
158
- m_sessions = new ConcurrentQueue < MySqlSession > ( ) ;
214
+ m_sessions = new LinkedList < MySqlSession > ( ) ;
159
215
}
160
216
161
217
static readonly ConcurrentDictionary < string , ConnectionPool > s_pools = new ConcurrentDictionary < string , ConnectionPool > ( ) ;
218
+ static readonly TimeSpan ReaperInterval = TimeSpan . FromMinutes ( 1 ) ;
219
+ static readonly Task Reaper = Task . Run ( async ( ) => {
220
+ while ( true )
221
+ {
222
+ var task = Task . Delay ( ReaperInterval ) ;
223
+ try
224
+ {
225
+ await ReapPoolsAsync ( IOBehavior . Asynchronous , new CancellationTokenSource ( ReaperInterval ) . Token ) . ConfigureAwait ( false ) ;
226
+ }
227
+ catch
228
+ {
229
+ // do nothing; we'll try to reap again
230
+ }
231
+ await task . ConfigureAwait ( false ) ;
232
+ }
233
+ } ) ;
162
234
163
235
int m_generation ;
236
+ readonly SemaphoreSlim m_cleanSemaphore ;
164
237
readonly SemaphoreSlim m_sessionSemaphore ;
165
- readonly ConcurrentQueue < MySqlSession > m_sessions ;
238
+ readonly LinkedList < MySqlSession > m_sessions ;
166
239
readonly ConnectionSettings m_connectionSettings ;
167
240
}
168
241
}
0 commit comments