@@ -23,6 +23,7 @@ public MySqlSession()
23
23
24
24
public MySqlSession ( ConnectionPool pool , int poolGeneration )
25
25
{
26
+ m_lock = new object ( ) ;
26
27
CreatedUtc = DateTime . UtcNow ;
27
28
Pool = pool ;
28
29
PoolGeneration = poolGeneration ;
@@ -38,13 +39,59 @@ public MySqlSession(ConnectionPool pool, int poolGeneration)
38
39
39
40
public void ReturnToPool ( ) => Pool ? . Return ( this ) ;
40
41
41
- public bool IsConnected => m_state == State . Connected ;
42
+ public bool IsConnected
43
+ {
44
+ get
45
+ {
46
+ lock ( m_lock )
47
+ return m_state == State . Connected ;
48
+ }
49
+ }
50
+
51
+ public void StartQuerying ( )
52
+ {
53
+ lock ( m_lock )
54
+ {
55
+ if ( m_state == State . Querying )
56
+ throw new MySqlException ( "There is already an open DataReader associated with this Connection which must be closed first." ) ;
57
+
58
+ VerifyState ( State . Connected ) ;
59
+ m_state = State . Querying ;
60
+ }
61
+ }
62
+
63
+ public MySqlDataReader ActiveReader => m_activeReader ;
64
+
65
+ public void SetActiveReader ( MySqlDataReader dataReader )
66
+ {
67
+ VerifyState ( State . Querying ) ;
68
+ if ( dataReader == null )
69
+ throw new ArgumentNullException ( nameof ( dataReader ) ) ;
70
+ if ( m_activeReader != null )
71
+ throw new InvalidOperationException ( "Can't replace active reader." ) ;
72
+ m_activeReader = dataReader ;
73
+ }
74
+
75
+ public void FinishQuerying ( )
76
+ {
77
+ lock ( m_lock )
78
+ {
79
+ VerifyState ( State . Querying ) ;
80
+ m_state = State . Connected ;
81
+ m_activeReader = null ;
82
+ }
83
+ }
42
84
43
85
public async Task DisposeAsync ( IOBehavior ioBehavior , CancellationToken cancellationToken )
44
86
{
45
87
if ( m_payloadHandler != null )
46
88
{
47
89
// attempt to gracefully close the connection, ignoring any errors (it may have been closed already by the server, etc.)
90
+ lock ( m_lock )
91
+ {
92
+ VerifyState ( State . Connected , State . Failed ) ;
93
+ m_state = State . Closing ;
94
+ }
48
95
try
49
96
{
50
97
m_payloadHandler . StartNewConversation ( ) ;
@@ -59,18 +106,28 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella
59
106
}
60
107
}
61
108
ShutdownSocket ( ) ;
62
- m_state = State . Closed ;
109
+ lock ( m_lock )
110
+ m_state = State . Closed ;
63
111
}
64
112
65
113
public async Task ConnectAsync ( ConnectionSettings cs , IOBehavior ioBehavior , CancellationToken cancellationToken )
66
114
{
115
+ lock ( m_lock )
116
+ {
117
+ VerifyState ( State . Created ) ;
118
+ m_state = State . Connecting ;
119
+ }
67
120
var connected = false ;
68
121
if ( cs . ConnectionType == ConnectionType . Tcp )
69
122
connected = await OpenTcpSocketAsync ( cs , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
70
123
else if ( cs . ConnectionType == ConnectionType . Unix )
71
124
connected = await OpenUnixSocketAsync ( cs , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
72
125
if ( ! connected )
126
+ {
127
+ lock ( m_lock )
128
+ m_state = State . Failed ;
73
129
throw new MySqlException ( "Unable to connect to any of the specified MySQL hosts." ) ;
130
+ }
74
131
75
132
var socketByteHandler = new SocketByteHandler ( m_socket ) ;
76
133
m_payloadHandler = new StandardPayloadHandler ( socketByteHandler ) ;
@@ -114,6 +171,7 @@ public async Task ConnectAsync(ConnectionSettings cs, IOBehavior ioBehavior, Can
114
171
115
172
public async Task ResetConnectionAsync ( ConnectionSettings cs , IOBehavior ioBehavior , CancellationToken cancellationToken )
116
173
{
174
+ VerifyState ( State . Connected ) ;
117
175
if ( ServerVersion . Version . CompareTo ( ServerVersions . SupportsResetConnection ) >= 0 )
118
176
{
119
177
await SendAsync ( ResetConnectionPayload . Create ( ) , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
@@ -150,6 +208,8 @@ public async Task ResetConnectionAsync(ConnectionSettings cs, IOBehavior ioBehav
150
208
151
209
public async Task < bool > TryPingAsync ( IOBehavior ioBehavior , CancellationToken cancellationToken )
152
210
{
211
+ VerifyState ( State . Connected ) ;
212
+
153
213
// check if client socket is still connected
154
214
// http://stackoverflow.com/questions/2661764/how-to-check-if-a-socket-is-connected-disconnected-in-c
155
215
if ( m_socket . Poll ( 1 , SelectMode . SelectRead ) && m_socket . Available == 0 )
@@ -169,6 +229,7 @@ public async Task<bool> TryPingAsync(IOBehavior ioBehavior, CancellationToken ca
169
229
{
170
230
}
171
231
232
+ VerifyState ( State . Failed ) ;
172
233
return false ;
173
234
}
174
235
@@ -196,10 +257,13 @@ public ValueTask<int> SendReplyAsync(PayloadData payload, IOBehavior ioBehavior,
196
257
197
258
private void VerifyConnected ( )
198
259
{
199
- if ( m_state == State . Closed )
200
- throw new ObjectDisposedException ( nameof ( MySqlSession ) ) ;
201
- if ( m_state != State . Connected )
202
- throw new InvalidOperationException ( "MySqlSession is not connected." ) ;
260
+ lock ( m_lock )
261
+ {
262
+ if ( m_state == State . Closed )
263
+ throw new ObjectDisposedException ( nameof ( MySqlSession ) ) ;
264
+ if ( m_state != State . Connected && m_state != State . Querying && m_state != State . CancelingQuery && m_state != State . Closing )
265
+ throw new InvalidOperationException ( "MySqlSession is not connected." ) ;
266
+ }
203
267
}
204
268
205
269
private async Task < bool > OpenTcpSocketAsync ( ConnectionSettings cs , IOBehavior ioBehavior , CancellationToken cancellationToken )
@@ -270,7 +334,8 @@ private async Task<bool> OpenTcpSocketAsync(ConnectionSettings cs, IOBehavior io
270
334
m_socket = m_tcpClient . Client ;
271
335
m_networkStream = m_tcpClient . GetStream ( ) ;
272
336
SerializationUtility . SetKeepalive ( m_socket , cs . Keepalive ) ;
273
- m_state = State . Connected ;
337
+ lock ( m_lock )
338
+ m_state = State . Connected ;
274
339
return true ;
275
340
}
276
341
}
@@ -316,7 +381,8 @@ private async Task<bool> OpenUnixSocketAsync(ConnectionSettings cs, IOBehavior i
316
381
m_socket = socket ;
317
382
m_networkStream = new NetworkStream ( socket ) ;
318
383
319
- m_state = State . Connected ;
384
+ lock ( m_lock )
385
+ m_state = State . Connected ;
320
386
return true ;
321
387
}
322
388
@@ -398,7 +464,8 @@ private async Task InitSslAsync(ProtocolCapabilities serverCapabilities, Connect
398
464
{
399
465
ShutdownSocket ( ) ;
400
466
m_hostname = "" ;
401
- m_state = State . Failed ;
467
+ lock ( m_lock )
468
+ m_state = State . Failed ;
402
469
if ( ex is AuthenticationException )
403
470
throw new MySqlException ( "SSL Authentication Error" , ex ) ;
404
471
if ( ex is IOException && clientCertificates != null )
@@ -450,10 +517,10 @@ private void ShutdownSocket()
450
517
451
518
private ValueTask < int > TryAsync < TArg > ( Func < TArg , IOBehavior , ValueTask < int > > func , TArg arg , IOBehavior ioBehavior , CancellationToken cancellationToken )
452
519
{
453
- VerifyConnected ( ) ;
454
520
ValueTask < int > task ;
455
521
try
456
522
{
523
+ VerifyConnected ( ) ;
457
524
task = func ( arg , ioBehavior ) ;
458
525
}
459
526
catch ( Exception ex )
@@ -480,10 +547,10 @@ private int TryAsyncContinuation(Task<int> task)
480
547
481
548
private ValueTask < PayloadData > TryAsync ( Func < ProtocolErrorBehavior , IOBehavior , ValueTask < ArraySegment < byte > > > func , IOBehavior ioBehavior , CancellationToken cancellationToken )
482
549
{
483
- VerifyConnected ( ) ;
484
550
ValueTask < ArraySegment < byte > > task ;
485
551
try
486
552
{
553
+ VerifyConnected ( ) ;
487
554
task = func ( ProtocolErrorBehavior . Throw , ioBehavior ) ;
488
555
}
489
556
catch ( Exception ex )
@@ -516,22 +583,57 @@ private PayloadData TryAsyncContinuation(Task<ArraySegment<byte>> task)
516
583
517
584
private void SetFailed ( )
518
585
{
519
- m_state = State . Failed ;
586
+ lock ( m_lock )
587
+ m_state = State . Failed ;
588
+ }
589
+
590
+
591
+ private void VerifyState ( State state )
592
+ {
593
+ if ( m_state != state )
594
+ throw new InvalidOperationException ( "Expected state to be {0} but was {1}." . FormatInvariant ( state , m_state ) ) ;
595
+ }
596
+
597
+ private void VerifyState ( State state1 , State state2 )
598
+ {
599
+ if ( m_state != state1 && m_state != state2 )
600
+ throw new InvalidOperationException ( "Expected state to be ({0}|{1}) but was {2}." . FormatInvariant ( state1 , state2 , m_state ) ) ;
520
601
}
521
602
522
603
private enum State
523
604
{
605
+ // The session has been created; no connection has been made.
524
606
Created ,
607
+
608
+ // The session is attempting to connect to a server.
609
+ Connecting ,
610
+
611
+ // The session is connected to a server; there is no active query.
525
612
Connected ,
613
+
614
+ // The session is connected to a server and a query is being made.
615
+ Querying ,
616
+
617
+ // The session is connected to a server and the active query is being cancelled.
618
+ CancelingQuery ,
619
+
620
+ // The session is closing.
621
+ Closing ,
622
+
623
+ // The session is closed.
526
624
Closed ,
625
+
626
+ // An unexpected error occurred; the session is in an unusable state.
527
627
Failed ,
528
628
}
529
629
630
+ readonly object m_lock ;
530
631
State m_state ;
531
632
string m_hostname = "" ;
532
633
TcpClient m_tcpClient ;
533
634
Socket m_socket ;
534
635
NetworkStream m_networkStream ;
535
636
IPayloadHandler m_payloadHandler ;
637
+ MySqlDataReader m_activeReader ;
536
638
}
537
639
}
0 commit comments