forked from dotnet/SqlClient
/
TdsParserStateObject.cs
4250 lines (3685 loc) · 170 KB
/
TdsParserStateObject.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.Common;
namespace Microsoft.Data.SqlClient
{
sealed internal class LastIOTimer
{
internal long _value;
}
internal abstract class TdsParserStateObject
{
private static int _objectTypeCount; // EventSource counter
internal readonly int _objectID = Interlocked.Increment(ref _objectTypeCount);
[Flags]
internal enum SnapshottedStateFlags : byte
{
None = 0,
PendingData = 1 << 1,
OpenResult = 1 << 2,
ErrorTokenReceived = 1 << 3, // Keep track of whether an error was received for the result. This is reset upon each done token
ColMetaDataReceived = 1 << 4, // Used to keep track of when to fire StatementCompleted event.
AttentionReceived = 1 << 5 // NOTE: Received is not volatile as it is only ever accessed\modified by TryRun its callees (i.e. single threaded access)
}
private const int AttentionTimeoutSeconds = 5;
private static readonly ContextCallback s_readAdyncCallbackComplete = ReadAsyncCallbackComplete;
// Ticks to consider a connection "good" after a successful I/O (10,000 ticks = 1 ms)
// The resolution of the timer is typically in the range 10 to 16 milliseconds according to msdn.
// We choose a value that is smaller than the likely timer resolution, but
// large enough to ensure that check connection execution will be 0.1% or less
// of very small open, query, close loops.
private const long CheckConnectionWindow = 50000;
protected readonly TdsParser _parser; // TdsParser pointer
private readonly WeakReference _owner = new WeakReference(null); // the owner of this session, used to track when it's been orphaned
internal SqlDataReader.SharedState _readerState; // susbset of SqlDataReader state (if it is the owner) necessary for parsing abandoned results in TDS
private int _activateCount; // 0 when we're in the pool, 1 when we're not, all others are an error
// Two buffers exist in tdsparser, an in buffer and an out buffer. For the out buffer, only
// one bookkeeping variable is needed, the number of bytes used in the buffer. For the in buffer,
// three variables are actually needed. First, we need to record from the netlib how many bytes it
// read from the netlib, this variable is _inBytesRead. Then, we need to also keep track of how many
// bytes we have used as we consume the bytes from the buffer, that variable is _inBytesUsed. Third,
// we need to keep track of how many bytes are left in the packet, so that we know when we have reached
// the end of the packet and so we need to consume the next header. That variable is _inBytesPacket.
// Header length constants
internal readonly int _inputHeaderLen = TdsEnums.HEADER_LEN;
internal readonly int _outputHeaderLen = TdsEnums.HEADER_LEN;
// Out buffer variables
internal byte[] _outBuff; // internal write buffer - initialize on login
internal int _outBytesUsed = TdsEnums.HEADER_LEN; // number of bytes used in internal write buffer -
// - initialize past header
// In buffer variables
/// <summary>
/// internal read buffer - initialize on login
/// </summary>
protected byte[] _inBuff;
/// <summary>
/// number of bytes used in internal read buffer
/// </summary>
internal int _inBytesUsed;
/// <summary>
/// number of bytes read into internal read buffer
/// </summary>
internal int _inBytesRead;
/// <summary>
/// number of bytes left in packet
/// </summary>
internal int _inBytesPacket;
internal int _spid; // SPID of the current connection
// Packet state variables
internal byte _outputMessageType; // tds header type
internal byte _messageStatus; // tds header status
internal byte _outputPacketNumber = 1; // number of packets sent to server in message - start at 1 per ramas
internal uint _outputPacketCount;
internal volatile bool _fResetEventOwned; // ResetEvent serializing call to sp_reset_connection
internal volatile bool _fResetConnectionSent; // For multiple packet execute
internal bool _bulkCopyOpperationInProgress; // Set to true during bulk copy and used to turn toggle write timeouts.
internal bool _bulkCopyWriteTimeout; // Set to trun when _bulkCopyOperationInProgress is trun and write timeout happens
// SNI variables
/// <summary>
/// Used to synchronize access to _writePacketCache and _pendingWritePackets
/// </summary>
protected readonly object _writePacketLockObject = new object();
// Async variables
private int _pendingCallbacks; // we increment this before each async read/write call and decrement it in the callback. We use this to determine when to release the GcHandle...
// Timeout variables
private long _timeoutMilliseconds;
private long _timeoutTime; // variable used for timeout computations, holds the value of the hi-res performance counter at which this request should expire
internal volatile bool _attentionSent; // true if we sent an Attention to the server
internal volatile bool _attentionSending;
internal bool _internalTimeout; // an internal timeout occurred
private readonly LastIOTimer _lastSuccessfulIOTimer;
// secure password information to be stored
// At maximum number of secure string that need to be stored is two; one for login password and the other for new change password
private SecureString[] _securePasswords = new SecureString[2] { null, null };
private int[] _securePasswordOffsetsInBuffer = new int[2];
// This variable is used to track whether another thread has requested a cancel. The
// synchronization points are
// On the user's execute thread:
// 1) the first packet write
// 2) session close - return this stateObj to the session pool
// On cancel thread we only have the cancel call.
// Currently all access to this variable is inside a lock, The state diagram is:
// 1) pre first packet write, if cancel is requested, set variable so exception is triggered
// on user thread when first packet write is attempted
// 2) post first packet write, but before session return - a call to cancel will send an
// attention to the server
// 3) post session close - no attention is allowed
private bool _cancelled;
private const int _waitForCancellationLockPollTimeout = 100;
private WeakReference _cancellationOwner = new WeakReference(null);
// Cache the transaction for which this command was executed so upon completion we can
// decrement the appropriate result count.
internal SqlInternalTransaction _executedUnderTransaction;
// TDS stream processing variables
internal ulong _longlen; // plp data length indicator
internal ulong _longlenleft; // Length of data left to read (64 bit lengths)
internal int[] _decimalBits; // scratch buffer for decimal/numeric data
internal byte[] _bTmp = new byte[TdsEnums.YUKON_HEADER_LEN]; // Scratch buffer for misc use
internal int _bTmpRead; // Counter for number of temporary bytes read
internal Decoder _plpdecoder; // Decoder object to process plp character data
internal bool _accumulateInfoEvents; // TRUE - accumulate info messages during TdsParser.Run, FALSE - fire them
internal List<SqlError> _pendingInfoEvents;
internal byte[] _bLongBytes; // scratch buffer to serialize Long values (8 bytes).
internal byte[] _bIntBytes; // scratch buffer to serialize Int values (4 bytes).
internal byte[] _bShortBytes; // scratch buffer to serialize Short values (2 bytes).
internal byte[] _bDecimalBytes; // scratch buffer to serialize decimal values (17 bytes).
//
// DO NOT USE THIS BUFFER FOR OTHER THINGS.
// ProcessHeader can be called ANYTIME while doing network reads.
private byte[] _partialHeaderBuffer = new byte[TdsEnums.HEADER_LEN]; // Scratch buffer for ProcessHeader
internal int _partialHeaderBytesRead;
internal _SqlMetaDataSet _cleanupMetaData;
internal _SqlMetaDataSetCollection _cleanupAltMetaDataSetArray;
private SniContext _sniContext = SniContext.Undefined;
#if DEBUG
private SniContext _debugOnlyCopyOfSniContext = SniContext.Undefined;
#endif
private bool _bcpLock = false;
// Null bitmap compression (NBC) information for the current row
private NullBitmap _nullBitmapInfo;
// Async
internal TaskCompletionSource<object> _networkPacketTaskSource;
private Timer _networkPacketTimeout;
internal bool _syncOverAsync = true;
private bool _snapshotReplay;
private StateSnapshot _snapshot;
private StateSnapshot _cachedSnapshot;
private SnapshottedStateFlags _snapshottedState;
internal ExecutionContext _executionContext;
internal bool _asyncReadWithoutSnapshot;
#if DEBUG
// Used to override the assert than ensures that the stacktraces on subsequent replays are the same
// This is useful is you are purposefully running the replay from a different thread (e.g. during SqlDataReader.Close)
internal bool _permitReplayStackTraceToDiffer;
// Used to indicate that the higher level object believes that this stateObj has enough data to complete an operation
// If this stateObj has to read, then it will raise an assert
internal bool _shouldHaveEnoughData;
#endif
// local exceptions to cache warnings and errors
internal SqlErrorCollection _errors;
internal SqlErrorCollection _warnings;
internal object _errorAndWarningsLock = new object();
private bool _hasErrorOrWarning;
// local exceptions to cache warnings and errors that occurred prior to sending attention
internal SqlErrorCollection _preAttentionErrors;
internal SqlErrorCollection _preAttentionWarnings;
private volatile TaskCompletionSource<object> _writeCompletionSource;
protected volatile int _asyncWriteCount;
private volatile Exception _delayedWriteAsyncCallbackException; // set by write async callback if completion source is not yet created
// _readingcount is incremented when we are about to read.
// We check the parser state afterwards.
// When the read is completed, we decrement it before handling errors
// as the error handling may end up calling Dispose.
private int _readingCount;
// Test hooks
#if DEBUG
// This is a test hook to enable testing of the retry paths.
// When set to true, almost every possible retry point will be attempted.
// This will drastically impact performance.
//
// Sample code to enable:
//
// Type type = typeof(SqlDataReader).Assembly.GetType("Microsoft.Data.SqlClient.TdsParserStateObject");
// System.Reflection.FieldInfo field = type.GetField("_forceAllPends", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
// if (field != null) {
// field.SetValue(null, true);
// }
//
internal static bool _forceAllPends = false;
// set this while making a call that should not block.
// instead of blocking it will fail.
internal static bool _failAsyncPends = false;
// If this is set and an async read is made, then
// we will switch to syncOverAsync mode for the
// remainder of the async operation.
internal static bool _forceSyncOverAsyncAfterFirstPend = false;
// Requests to send attention will be ignored when _skipSendAttention is true.
// This is useful to simulate circumstances where timeouts do not recover.
internal static bool _skipSendAttention = false;
// Prevents any pending read from completing until the user signals it using
// CompletePendingReadWithSuccess() or CompletePendingReadWithFailure(int errorCode) in SqlCommand\SqlDataReader
internal static bool _forcePendingReadsToWaitForUser = false;
internal TaskCompletionSource<object> _realNetworkPacketTaskSource;
// Field is never assigned to, and will always have its default value
#pragma warning disable 0649
// Set to true to enable checking the call stacks match when packet retry occurs.
internal static bool _checkNetworkPacketRetryStacks = false;
#pragma warning restore 0649
#endif
//////////////////
// Constructors //
//////////////////
internal TdsParserStateObject(TdsParser parser)
{
// Construct a physical connection
Debug.Assert(null != parser, "no parser?");
_parser = parser;
// For physical connection, initialize to default login packet size.
SetPacketSize(TdsEnums.DEFAULT_LOGIN_PACKET_SIZE);
// we post a callback that represents the call to dispose; once the
// object is disposed, the next callback will cause the GC Handle to
// be released.
IncrementPendingCallbacks();
_lastSuccessfulIOTimer = new LastIOTimer();
}
internal TdsParserStateObject(TdsParser parser, TdsParserStateObject physicalConnection, bool async)
{
// Construct a MARS session
Debug.Assert(null != parser, "no parser?");
_parser = parser;
SniContext = SniContext.Snix_GetMarsSession;
Debug.Assert(null != _parser._physicalStateObj, "no physical session?");
Debug.Assert(null != _parser._physicalStateObj._inBuff, "no in buffer?");
Debug.Assert(null != _parser._physicalStateObj._outBuff, "no out buffer?");
Debug.Assert(_parser._physicalStateObj._outBuff.Length ==
_parser._physicalStateObj._inBuff.Length, "Unexpected unequal buffers.");
// Determine packet size based on physical connection buffer lengths.
SetPacketSize(_parser._physicalStateObj._outBuff.Length);
CreateSessionHandle(physicalConnection, async);
if (IsFailedHandle())
{
AddError(parser.ProcessSNIError(this));
ThrowExceptionAndWarning();
}
// we post a callback that represents the call to dispose; once the
// object is disposed, the next callback will cause the GC Handle to
// be released.
IncrementPendingCallbacks();
_lastSuccessfulIOTimer = parser._physicalStateObj._lastSuccessfulIOTimer;
}
////////////////
// Properties //
////////////////
// BcpLock - use to lock this object if there is a potential risk of using this object
// between tds packets
internal bool BcpLock
{
get
{
return _bcpLock;
}
set
{
_bcpLock = value;
}
}
#if DEBUG
internal SniContext DebugOnlyCopyOfSniContext
{
get
{
return _debugOnlyCopyOfSniContext;
}
}
internal void InvalidateDebugOnlyCopyOfSniContext()
{
_debugOnlyCopyOfSniContext = SniContext.Undefined;
}
#endif
internal bool IsOrphaned
{
get
{
Debug.Assert((0 == _activateCount && !_owner.IsAlive) // in pool
|| (1 == _activateCount && _owner.IsAlive && _owner.Target != null)
|| (1 == _activateCount && !_owner.IsAlive), "Unknown state on TdsParserStateObject.IsOrphaned!");
return (0 != _activateCount && !_owner.IsAlive);
}
}
internal object Owner
{
set
{
Debug.Assert(value == null || !_owner.IsAlive || ((value is SqlDataReader) && (((SqlDataReader)value).Command == _owner.Target)), "Should not be changing the owner of an owned stateObj");
SqlDataReader reader = value as SqlDataReader;
if (reader == null)
{
_readerState = null;
}
else
{
_readerState = reader._sharedState;
}
_owner.Target = value;
}
}
internal abstract uint DisableSsl();
internal bool HasOwner
{
get
{
return _owner.IsAlive;
}
}
internal TdsParser Parser
{
get
{
return _parser;
}
}
internal abstract uint EnableMars(ref uint info);
internal SniContext SniContext
{
get
{
return _sniContext;
}
set
{
_sniContext = value;
#if DEBUG
_debugOnlyCopyOfSniContext = value;
#endif
}
}
internal abstract uint Status
{
get;
}
internal abstract SessionHandle SessionHandle
{
get;
}
internal bool TimeoutHasExpired
{
get
{
Debug.Assert(0 == _timeoutMilliseconds || 0 == _timeoutTime, "_timeoutTime hasn't been reset");
return TdsParserStaticMethods.TimeoutHasExpired(_timeoutTime);
}
}
internal long TimeoutTime
{
get
{
if (0 != _timeoutMilliseconds)
{
_timeoutTime = TdsParserStaticMethods.GetTimeout(_timeoutMilliseconds);
_timeoutMilliseconds = 0;
}
return _timeoutTime;
}
set
{
_timeoutMilliseconds = 0;
_timeoutTime = value;
}
}
internal int GetTimeoutRemaining()
{
int remaining;
if (0 != _timeoutMilliseconds)
{
remaining = (int)Math.Min((long)int.MaxValue, _timeoutMilliseconds);
_timeoutTime = TdsParserStaticMethods.GetTimeout(_timeoutMilliseconds);
_timeoutMilliseconds = 0;
}
else
{
remaining = TdsParserStaticMethods.GetTimeoutMilliseconds(_timeoutTime);
}
return remaining;
}
internal bool TryStartNewRow(bool isNullCompressed, int nullBitmapColumnsCount = 0)
{
Debug.Assert(!isNullCompressed || nullBitmapColumnsCount > 0, "Null-Compressed row requires columns count");
if (_snapshot != null)
{
_snapshot.CloneNullBitmapInfo();
}
// initialize or unset null bitmap information for the current row
if (isNullCompressed)
{
// assert that NBCROW is not in use by Yukon or before
Debug.Assert(_parser.IsKatmaiOrNewer, "NBCROW is sent by pre-Katmai server");
if (!_nullBitmapInfo.TryInitialize(this, nullBitmapColumnsCount))
{
return false;
}
}
else
{
_nullBitmapInfo.Clean();
}
return true;
}
internal bool IsRowTokenReady()
{
// Removing one byte since TryReadByteArray\TryReadByte will aggressively read the next packet if there is no data left - so we need to ensure there is a spare byte
int bytesRemaining = Math.Min(_inBytesPacket, _inBytesRead - _inBytesUsed) - 1;
if (bytesRemaining > 0)
{
if (_inBuff[_inBytesUsed] == TdsEnums.SQLROW)
{
// At a row token, so we're ready
return true;
}
else if (_inBuff[_inBytesUsed] == TdsEnums.SQLNBCROW)
{
// NBC row token, ensure that we have enough data for the bitmap
// SQLNBCROW + Null Bitmap (copied from NullBitmap.TryInitialize)
int bytesToRead = 1 + (_cleanupMetaData.Length + 7) / 8;
return (bytesToRead <= bytesRemaining);
}
}
// No data left, or not at a row token
return false;
}
internal bool IsNullCompressionBitSet(int columnOrdinal)
{
return _nullBitmapInfo.IsGuaranteedNull(columnOrdinal);
}
private struct NullBitmap
{
private byte[] _nullBitmap;
private int _columnsCount; // set to 0 if not used or > 0 for NBC rows
internal bool TryInitialize(TdsParserStateObject stateObj, int columnsCount)
{
_columnsCount = columnsCount;
// 1-8 columns need 1 byte
// 9-16: 2 bytes, and so on
int bitmapArrayLength = (columnsCount + 7) / 8;
// allow reuse of previously allocated bitmap
if (_nullBitmap == null || _nullBitmap.Length != bitmapArrayLength)
{
_nullBitmap = new byte[bitmapArrayLength];
}
// read the null bitmap compression information from TDS
if (!stateObj.TryReadByteArray(_nullBitmap, _nullBitmap.Length))
{
return false;
}
SqlClientEventSource.Log.TryAdvancedTraceEvent("<sc.TdsParserStateObject.NullBitmap.Initialize|INFO|ADV> {0}, NBCROW bitmap received, column count = {1}", stateObj.ObjectID, columnsCount);
SqlClientEventSource.Log.TryAdvancedTraceBinEvent("<sc.TdsParserStateObject.NullBitmap.Initialize|INFO|ADV> NBCROW bitmap data: ", _nullBitmap, (ushort)_nullBitmap.Length);
return true;
}
internal bool ReferenceEquals(NullBitmap obj)
{
return object.ReferenceEquals(_nullBitmap, obj._nullBitmap);
}
internal NullBitmap Clone()
{
NullBitmap newBitmap = new NullBitmap();
newBitmap._nullBitmap = _nullBitmap == null ? null : (byte[])_nullBitmap.Clone();
newBitmap._columnsCount = _columnsCount;
return newBitmap;
}
internal void Clean()
{
_columnsCount = 0;
// no need to free _nullBitmap array - it is cached for the next row
}
/// <summary>
/// If this method returns true, the value is guaranteed to be null. This is not true vice versa:
/// if the bitmap value is false (if this method returns false), the value can be either null or non-null - no guarantee in this case.
/// To determine whether it is null or not, read it from the TDS (per NBCROW design spec, for IMAGE/TEXT/NTEXT columns server might send
/// bitmap = 0, when the actual value is null).
/// </summary>
internal bool IsGuaranteedNull(int columnOrdinal)
{
if (_columnsCount == 0)
{
// not an NBC row
return false;
}
Debug.Assert(columnOrdinal >= 0 && columnOrdinal < _columnsCount, "Invalid column ordinal");
byte testBit = (byte)(1 << (columnOrdinal & 0x7)); // columnOrdinal & 0x7 == columnOrdinal MOD 0x7
byte testByte = _nullBitmap[columnOrdinal >> 3];
return (testBit & testByte) != 0;
}
}
/////////////////////
// General methods //
/////////////////////
// If this object is part of a TdsParserSessionPool, then this *must* be called inside the pool's lock
internal void Activate(object owner)
{
Debug.Assert(_parser.MARSOn, "Can not activate a non-MARS connection");
Owner = owner; // must assign an owner for reclamation to work
int result = Interlocked.Increment(ref _activateCount); // must have non-zero activation count for reclamation to work too.
Debug.Assert(result == 1, "invalid deactivate count");
}
// This method is only called by the command or datareader as a result of a user initiated
// cancel request.
internal void Cancel(object caller)
{
Debug.Assert(caller != null, "Null caller for Cancel!");
Debug.Assert(caller is SqlCommand || caller is SqlDataReader, "Calling API with invalid caller type: " + caller.GetType());
bool hasLock = false;
try
{
// Keep looping until we either grabbed the lock (and therefore sent attention) or the connection closes\breaks
while ((!hasLock) && (_parser.State != TdsParserState.Closed) && (_parser.State != TdsParserState.Broken))
{
Monitor.TryEnter(this, _waitForCancellationLockPollTimeout, ref hasLock);
if (hasLock)
{ // Lock for the time being - since we need to synchronize the attention send.
// This lock is also protecting against concurrent close and async continuations
// Ensure that, once we have the lock, that we are still the owner
if ((!_cancelled) && (_cancellationOwner.Target == caller))
{
_cancelled = true;
if (HasPendingData && !_attentionSent)
{
bool hasParserLock = false;
// Keep looping until we have the parser lock (and so are allowed to write), or the connection closes\breaks
while ((!hasParserLock) && (_parser.State != TdsParserState.Closed) && (_parser.State != TdsParserState.Broken))
{
try
{
_parser.Connection._parserLock.Wait(canReleaseFromAnyThread: false, timeout: _waitForCancellationLockPollTimeout, lockTaken: ref hasParserLock);
if (hasParserLock)
{
_parser.Connection.ThreadHasParserLockForClose = true;
SendAttention();
}
}
finally
{
if (hasParserLock)
{
if (_parser.Connection.ThreadHasParserLockForClose)
{
_parser.Connection.ThreadHasParserLockForClose = false;
}
_parser.Connection._parserLock.Release();
}
}
}
}
}
}
}
}
finally
{
if (hasLock)
{
Monitor.Exit(this);
}
}
}
// CancelRequest - use to cancel while writing a request to the server
//
// o none of the request might have been sent to the server, simply reset the buffer,
// sending attention does not hurt
// o the request was partially written. Send an ignore header to the server. attention is
// required if the server was waiting for data (e.g. insert bulk rows)
// o the request was completely written out and the server started to process the request.
// attention is required to have the server stop processing.
//
internal void CancelRequest()
{
ResetBuffer(); // clear out unsent buffer
// If the first sqlbulkcopy timeout, _outputPacketNumber may not be 1,
// the next sqlbulkcopy (same connection string) requires this to be 1, hence reset
// it here when exception happens in the first sqlbulkcopy
ResetPacketCounters();
// VSDD#907507, if bulkcopy write timeout happens, it already sent the attention,
// so no need to send it again
if (!_bulkCopyWriteTimeout)
{
SendAttention();
Parser.ProcessPendingAck(this);
}
}
public void CheckSetResetConnectionState(uint error, CallbackType callbackType)
{
// Should only be called for MARS - that is the only time we need to take
// the ResetConnection lock!
// It was raised in a security review by Microsoft questioning whether
// we need to actually process the resulting packet (sp_reset ack or error) to know if the
// reset actually succeeded. There was a concern that if the reset failed and we proceeded
// there might be a security issue present. We have been assured by the server that if
// sp_reset fails, they guarantee they will kill the resulting connection. So - it is
// safe for us to simply receive the packet and then consume the pre-login later.
Debug.Assert(_parser.MARSOn, "Should not be calling CheckSetResetConnectionState on non MARS connection");
if (_fResetEventOwned)
{
if (callbackType == CallbackType.Read && TdsEnums.SNI_SUCCESS == error)
{
// RESET SUCCEEDED!
// If we are on read callback and no error occurred (and we own reset event) -
// then we sent the sp_reset_connection and so we need to reset sp_reset_connection
// flag to false, and then release the ResetEvent.
_parser._fResetConnection = false;
_fResetConnectionSent = false;
_fResetEventOwned = !_parser._resetConnectionEvent.Set();
Debug.Assert(!_fResetEventOwned, "Invalid AutoResetEvent state!");
}
if (TdsEnums.SNI_SUCCESS != error)
{
// RESET FAILED!
// If write or read failed with reset, we need to clear event but not mark connection
// as reset.
_fResetConnectionSent = false;
_fResetEventOwned = !_parser._resetConnectionEvent.Set();
Debug.Assert(!_fResetEventOwned, "Invalid AutoResetEvent state!");
}
}
}
internal void CloseSession()
{
ResetCancelAndProcessAttention();
#if DEBUG
InvalidateDebugOnlyCopyOfSniContext();
#endif
Parser.PutSession(this);
}
private void ResetCancelAndProcessAttention()
{
// This method is shared by CloseSession initiated by DataReader.Close or completed
// command execution, as well as the session reclamation code for cases where the
// DataReader is opened and then GC'ed.
lock (this)
{
// Reset cancel state.
_cancelled = false;
_cancellationOwner.Target = null;
if (_attentionSent)
{
// Make sure we're cleaning up the AttentionAck if Cancel happened before taking the lock.
// We serialize Cancel/CloseSession to prevent a race condition between these two states.
// The problem is that both sending and receiving attentions are time taking
// operations.
Parser.ProcessPendingAck(this);
}
_internalTimeout = false;
}
}
internal abstract void CreatePhysicalSNIHandle(string serverName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[] spnBuffer, bool flushCache, bool async, bool fParallel, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo, bool isIntegratedSecurity = false);
internal abstract void AssignPendingDNSInfo(string userProtocol, string DNSCacheKey, ref SQLDNSInfo pendingDNSInfo);
internal abstract uint SniGetConnectionId(ref Guid clientConnectionId);
internal abstract bool IsFailedHandle();
protected abstract void CreateSessionHandle(TdsParserStateObject physicalConnection, bool async);
protected abstract void FreeGcHandle(int remaining, bool release);
internal abstract uint EnableSsl(ref uint info);
internal abstract uint WaitForSSLHandShakeToComplete(out int protocolVersion);
internal abstract void Dispose();
internal abstract void DisposePacketCache();
internal abstract bool IsPacketEmpty(PacketHandle readPacket);
internal abstract PacketHandle ReadSyncOverAsync(int timeoutRemaining, out uint error);
internal abstract PacketHandle ReadAsync(SessionHandle handle, out uint error);
internal abstract uint CheckConnection();
internal abstract uint SetConnectionBufferSize(ref uint unsignedPacketSize);
internal abstract void ReleasePacket(PacketHandle syncReadPacket);
protected abstract uint SNIPacketGetData(PacketHandle packet, byte[] _inBuff, ref uint dataSize);
internal abstract PacketHandle GetResetWritePacket(int dataSize);
internal abstract void ClearAllWritePackets();
internal abstract PacketHandle AddPacketToPendingList(PacketHandle packet);
protected abstract void RemovePacketFromPendingList(PacketHandle pointer);
internal abstract uint GenerateSspiClientContext(byte[] receivedBuff, uint receivedLength, ref byte[] sendBuff, ref uint sendLength, byte[] _sniSpnBuffer);
internal bool Deactivate()
{
bool goodForReuse = false;
try
{
TdsParserState state = Parser.State;
if (state != TdsParserState.Broken && state != TdsParserState.Closed)
{
if (HasPendingData)
{
Parser.DrainData(this); // This may throw - taking us to catch block.c
}
if (HasOpenResult)
{
DecrementOpenResultCount();
}
ResetCancelAndProcessAttention();
goodForReuse = true;
}
}
catch (Exception e)
{
if (!ADP.IsCatchableExceptionType(e))
{
throw;
}
}
return goodForReuse;
}
// If this object is part of a TdsParserSessionPool, then this *must* be called inside the pool's lock
internal void RemoveOwner()
{
if (_parser.MARSOn)
{
// We only care about the activation count for MARS connections
int result = Interlocked.Decrement(ref _activateCount); // must have non-zero activation count for reclamation to work too.
Debug.Assert(result == 0, "invalid deactivate count");
}
Owner = null;
}
internal void DecrementOpenResultCount()
{
if (_executedUnderTransaction == null)
{
// If we were not executed under a transaction - decrement the global count
// on the parser.
_parser.DecrementNonTransactedOpenResultCount();
}
else
{
// If we were executed under a transaction - decrement the count on the transaction.
_executedUnderTransaction.DecrementAndObtainOpenResultCount();
_executedUnderTransaction = null;
}
HasOpenResult = false;
}
internal int DecrementPendingCallbacks(bool release)
{
int remaining = Interlocked.Decrement(ref _pendingCallbacks);
SqlClientEventSource.Log.TryAdvancedTraceEvent("<sc.TdsParserStateObject.DecrementPendingCallbacks|ADV> {0}, after decrementing _pendingCallbacks: {1}", ObjectID, _pendingCallbacks);
FreeGcHandle(remaining, release);
// NOTE: TdsParserSessionPool may call DecrementPendingCallbacks on a TdsParserStateObject which is already disposed
// This is not dangerous (since the stateObj is no longer in use), but we need to add a workaround in the assert for it
Debug.Assert((remaining == -1 && SessionHandle.IsNull) || (0 <= remaining && remaining < 3), $"_pendingCallbacks values is invalid after decrementing: {remaining}");
return remaining;
}
internal void DisposeCounters()
{
Timer networkPacketTimeout = _networkPacketTimeout;
if (networkPacketTimeout != null)
{
_networkPacketTimeout = null;
networkPacketTimeout.Dispose();
}
Debug.Assert(Volatile.Read(ref _readingCount) >= 0, "_readingCount is negative");
if (Volatile.Read(ref _readingCount) > 0)
{
// if _reading is true, we need to wait for it to complete
// if _reading is false, then future read attempts will
// already see the null _sessionHandle and abort.
// We block after nulling _sessionHandle but before disposing it
// to give a chance for a read that has already grabbed the
// handle to complete.
SpinWait.SpinUntil(() => Volatile.Read(ref _readingCount) == 0);
}
}
internal int IncrementAndObtainOpenResultCount(SqlInternalTransaction transaction)
{
HasOpenResult = true;
if (transaction == null)
{
// If we are not passed a transaction, we are not executing under a transaction
// and thus we should increment the global connection result count.
return _parser.IncrementNonTransactedOpenResultCount();
}
else
{
// If we are passed a transaction, we are executing under a transaction
// and thus we should increment the transaction's result count.
_executedUnderTransaction = transaction;
return transaction.IncrementAndObtainOpenResultCount();
}
}
internal int IncrementPendingCallbacks()
{
int remaining = Interlocked.Increment(ref _pendingCallbacks);
SqlClientEventSource.Log.TryAdvancedTraceEvent("<sc.TdsParserStateObject.IncrementPendingCallbacks|ADV> {0}, after incrementing _pendingCallbacks: {1}", ObjectID, _pendingCallbacks);
Debug.Assert(0 < remaining && remaining <= 3, $"_pendingCallbacks values is invalid after incrementing: {remaining}");
return remaining;
}
internal void SetTimeoutSeconds(int timeout)
{
SetTimeoutMilliseconds((long)timeout * 1000L);
}
internal void SetTimeoutMilliseconds(long timeout)
{
if (timeout <= 0)
{
// 0 or less (i.e. Timespan.Infinite) == infinite (which is represented by Int64.MaxValue)
_timeoutMilliseconds = 0;
_timeoutTime = long.MaxValue;
}
else
{
_timeoutMilliseconds = timeout;
_timeoutTime = 0;
}
}
internal void StartSession(object cancellationOwner)
{
_cancellationOwner.Target = cancellationOwner;
}
internal void ThrowExceptionAndWarning(bool callerHasConnectionLock = false, bool asyncClose = false)
{
_parser.ThrowExceptionAndWarning(this, callerHasConnectionLock, asyncClose);
}
////////////////////////////////////////////
// TDS Packet/buffer manipulation methods //
////////////////////////////////////////////
internal Task ExecuteFlush()
{
lock (this)
{
if (_cancelled && 1 == _outputPacketNumber)
{
ResetBuffer();
_cancelled = false;
throw SQL.OperationCancelled();
}
else
{
Task writePacketTask = WritePacket(TdsEnums.HARDFLUSH);
if (writePacketTask == null)
{
HasPendingData = true;
_messageStatus = 0;
return null;
}
else
{
return AsyncHelper.CreateContinuationTask(writePacketTask, () => { HasPendingData = true; _messageStatus = 0; });
}
}
}
}
// Processes the tds header that is present in the buffer
internal bool TryProcessHeader()
{
Debug.Assert(_inBytesPacket == 0, "there should not be any bytes left in packet when ReadHeader is called");
// if the header splits buffer reads - special case!