/
TdsParserStateObject.cs
4327 lines (3767 loc) · 178 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.CompilerServices;
using System.Runtime.ConstrainedExecution;
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;
}
sealed internal class TdsParserStateObject
{
const int AttentionTimeoutSeconds = 5;
// 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;
private static int _objectTypeCount; // EventSource Counter
internal readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
internal int ObjectID
{
get
{
return _objectID;
}
}
private readonly TdsParser _parser; // TdsParser pointer
private SNIHandle _sessionHandle = null; // the SNI handle we're to work on
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
private byte[] _inBuff; // internal read buffer - initialize on login
internal int _inBytesUsed = 0; // number of bytes used in internal read buffer
internal int _inBytesRead = 0; // number of bytes read into internal read buffer
internal int _inBytesPacket = 0; // number of bytes left in packet
internal int _spid; // SPID of the current connection
// Packet state variables
internal byte _outputMessageType = 0; // 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 bool _pendingData = false;
internal volatile bool _fResetEventOwned = false; // ResetEvent serializing call to sp_reset_connection
internal volatile bool _fResetConnectionSent = false; // For multiple packet execute
internal bool _errorTokenReceived = false; // Keep track of whether an error was received for the result.
// This is reset upon each done token - there can be
internal bool _bulkCopyOpperationInProgress = false; // Set to true during bulk copy and used to turn toggle write timeouts.
internal bool _bulkCopyWriteTimeout = false; // Set to trun when _bulkCopyOpeperationInProgress is trun and write timeout happens
// SNI variables // multiple resultsets in one batch.
private SNIPacket _sniPacket = null; // Will have to re-vamp this for MARS
internal SNIPacket _sniAsyncAttnPacket = null; // Packet to use to send Attn
private WritePacketCache _writePacketCache = new WritePacketCache(); // Store write packets that are ready to be re-used
private Dictionary<IntPtr, SNIPacket> _pendingWritePackets = new Dictionary<IntPtr, SNIPacket>(); // Stores write packets that have been sent to SNI, but have not yet finished writing (i.e. we are waiting for SNI's callback)
private object _writePacketLockObject = new object(); // Used to synchronize access to _writePacketCache and _pendingWritePackets
// Async variables
private GCHandle _gcHandle; // keeps this object alive until we're closed.
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 = false; // true if we sent an Attention to the server
internal bool _attentionReceived = false; // NOTE: Received is not volatile as it is only ever accessed\modified by TryRun its callees (i.e. single threaded access)
internal volatile bool _attentionSending = false;
internal bool _internalTimeout = false; // 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, though I hope to limit that in the
// future. 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;
// This variable is used to prevent sending an attention by another thread that is not the
// current owner of the stateObj. I currently do not know how this can happen. Mark added
// the code but does not remember either. At some point, we need to research killing this
// logic.
private volatile int _allowObjectID;
internal bool _hasOpenResult = false;
// Cache the transaction for which this command was executed so upon completion we can
// decrement the appropriate result count.
internal SqlInternalTransaction _executedUnderTransaction = null;
// 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 = null; // scratch buffer for decimal/numeric data
internal byte[] _bTmp = new byte[TdsEnums.YUKON_HEADER_LEN]; // Scratch buffer for misc use
internal int _bTmpRead = 0; // Counter for number of temporary bytes read
internal Decoder _plpdecoder = null; // Decoder object to process plp character data
internal bool _accumulateInfoEvents = false; // TRUE - accumulate info messages during TdsParser.Run, FALSE - fire them
internal List<SqlError> _pendingInfoEvents = null;
internal byte[] _bLongBytes = null; // scratch buffer to serialize Long values (8 bytes).
internal byte[] _bIntBytes = null; // scratch buffer to serialize Int values (4 bytes).
internal byte[] _bShortBytes = null; // scratch buffer to serialize Short values (2 bytes).
internal byte[] _bDecimalBytes = null; // 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 = 0;
// UNDONE - temporary hack for case where pooled connection is returned to pool with data on wire -
// need to cache metadata on parser to read off wire
internal _SqlMetaDataSet _cleanupMetaData = null;
internal _SqlMetaDataSetCollection _cleanupAltMetaDataSetArray = null;
// Used for blanking out password in trace.
internal int _tracePasswordOffset = 0;
internal int _tracePasswordLength = 0;
internal int _traceChangePasswordOffset = 0;
internal int _traceChangePasswordLength = 0;
internal bool _receivedColMetaData; // Used to keep track of when to fire StatementCompleted event.
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 = false;
private StateSnapshot _snapshot;
internal ExecutionContext _executionContext;
internal bool _asyncReadWithoutSnapshot = false;
#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 = false;
// 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 = false;
#endif
// local exceptions to cache warnings and errors
internal SqlErrorCollection _errors;
internal SqlErrorCollection _warnings;
internal object _errorAndWarningsLock = new object();
private bool _hasErrorOrWarning = false;
// local exceptions to cache warnings and errors that occurred prior to sending attention
internal SqlErrorCollection _preAttentionErrors;
internal SqlErrorCollection _preAttentionWarnings;
volatile private TaskCompletionSource<object> _writeCompletionSource = null;
volatile private int _asyncWriteCount = 0;
volatile private Exception _delayedWriteAsyncCallbackException = null; // 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.
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;
internal TaskCompletionSource<object> _realNetworkPacketTaskSource = null;
// Set to true to enable checking the call stacks match when packet retry occurs.
internal static bool _checkNetworkPacketRetryStacks;
#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, SNIHandle 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);
SNINativeMethodWrapper.ConsumerInfo myInfo = CreateConsumerInfo(async);
SQLDNSInfo cachedDNSInfo;
bool ret = SQLFallbackDNSCache.Instance.GetDNSInfo(_parser.FQDNforDNSCahce, out cachedDNSInfo);
_sessionHandle = new SNIHandle(myInfo, physicalConnection, cachedDNSInfo);
if (_sessionHandle.Status != TdsEnums.SNI_SUCCESS)
{
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;
}
}
#endif
internal SNIHandle Handle
{
get
{
return _sessionHandle;
}
}
internal bool HasOpenResult
{
get
{
return _hasOpenResult;
}
}
#if DEBUG
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 bool HasOwner
{
get
{
return _owner.IsAlive;
}
}
internal TdsParser Parser
{
get
{
return _parser;
}
}
internal SniContext SniContext
{
get
{
return _sniContext;
}
set
{
_sniContext = value;
#if DEBUG
_debugOnlyCopyOfSniContext = value;
#endif
}
}
internal UInt32 Status
{
get
{
if (_sessionHandle != null)
{
return _sessionHandle.Status;
}
else
{ // SQL BU DT 395431.
return TdsEnums.SNI_UNINITIALIZED;
}
}
}
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)Int32.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, 0, _nullBitmap.Length))
{
return false;
}
SqlClientEventSource.Log.AdvancedTraceEvent("<sc.TdsParserStateObject.NullBitmap.Initialize|INFO|ADV> {0}, NBCROW bitmap received, column count = {1}", stateObj.ObjectID, columnsCount);
SqlClientEventSource.Log.AdvancedTraceBinEvent("<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 bitmat 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 reclaimation to work
int result = Interlocked.Increment(ref _activateCount); // must have non-zero activation count for reclaimation 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(int objectID)
{
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.
// At some point in the future, I hope to remove this.
// This lock is also protecting against concurrent close and async continuations
// don't allow objectID -1 since it is reserved for 'not associated with a command'
// yes, the 2^32-1 comand won't cancel - but it also won't cancel when we don't want it
if ((!_cancelled) && (objectID == _allowObjectID) && (objectID != -1))
{
_cancelled = true;
if (_pendingData && !_attentionSent)
{
bool hasParserLock = false;
// Keep looping until we have the parser lock (and so are allowed to write), or the conneciton 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
// VSDD#903514, 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(UInt32 error, CallbackType callbackType)
{
// Should only be called for MARS - that is the only time we need to take
// the ResetConnection lock!
// SQL BU DT 333026 - it was raised in a security review 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!
// UNDONE - is there a bug here?
// 1) Are there any cases where netlib write error does not mean reset was not executed?
// 2) Can we receive error on 2nd or beyond packet write but the server has already
// processed the reset??? I don't believe it is the case, but I have inserted a comment
// here as a thought workitem.
// 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 reclaimation code for cases where the
// DataReader is opened and then GC'ed.
lock (this)
{
// Reset cancel state.
_cancelled = false;
_allowObjectID = -1;
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.
#if DEBUG
TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
RuntimeHelpers.PrepareConstrainedRegions();
try
{
tdsReliabilitySection.Start();
#endif //DEBUG
Parser.ProcessPendingAck(this);
#if DEBUG
}
finally
{
tdsReliabilitySection.Stop();
}
#endif //DEBUG
}
_internalTimeout = false;
}
}
private SNINativeMethodWrapper.ConsumerInfo CreateConsumerInfo(bool async)
{
SNINativeMethodWrapper.ConsumerInfo myInfo = new SNINativeMethodWrapper.ConsumerInfo();
Debug.Assert(_outBuff.Length == _inBuff.Length, "Unexpected unequal buffers.");
myInfo.defaultBufferSize = _outBuff.Length; // Obtain packet size from outBuff size.
if (async)
{
myInfo.readDelegate = SNILoadHandle.SingletonInstance.ReadAsyncCallbackDispatcher;
myInfo.writeDelegate = SNILoadHandle.SingletonInstance.WriteAsyncCallbackDispatcher;
_gcHandle = GCHandle.Alloc(this, GCHandleType.Normal);
myInfo.key = (IntPtr)_gcHandle;
}
return myInfo;
}
internal void CreatePhysicalSNIHandle(string serverName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, byte[] spnBuffer, bool flushCache, bool async, bool fParallel, TransparentNetworkResolutionState transparentNetworkResolutionState, int totalTimeout, string cachedFQDN)
{
SNINativeMethodWrapper.ConsumerInfo myInfo = CreateConsumerInfo(async);
// Translate to SNI timeout values (Int32 milliseconds)
long timeout;
if (Int64.MaxValue == timerExpire)
{
timeout = Int32.MaxValue;
}
else
{
timeout = ADP.TimerRemainingMilliseconds(timerExpire);
if (timeout > Int32.MaxValue)
{
timeout = Int32.MaxValue;
}
else if (0 > timeout)
{
timeout = 0;
}
}
// serverName : serverInfo.ExtendedServerName
// may not use this serverName as key
SQLDNSInfo cachedDNSInfo;
bool ret = SQLFallbackDNSCache.Instance.GetDNSInfo(cachedFQDN, out cachedDNSInfo);
_sessionHandle = new SNIHandle(myInfo, serverName, spnBuffer, ignoreSniOpenTimeout, checked((int)timeout), out instanceName, flushCache, !async, fParallel, transparentNetworkResolutionState, totalTimeout, cachedDNSInfo);
}
internal bool Deactivate()
{
bool goodForReuse = false;
try
{
TdsParserState state = Parser.State;
if (state != TdsParserState.Broken && state != TdsParserState.Closed)
{
if (_pendingData)
{
Parser.DrainData(this); // This may throw - taking us to catch block.
}
if (HasOpenResult)
{ // SQL BU DT 383773 - need to decrement openResultCount for all pending operations.
DecrementOpenResultCount();
}
ResetCancelAndProcessAttention();
goodForReuse = true;
}
}
catch (Exception e)
{
if (!ADP.IsCatchableExceptionType(e))
{
throw;
}
ADP.TraceExceptionWithoutRethrow(e);
}
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 reclaimation 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;
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
internal int DecrementPendingCallbacks(bool release)
{
int remaining = Interlocked.Decrement(ref _pendingCallbacks);
SqlClientEventSource.Log.AdvancedTraceEvent("<sc.TdsParserStateObject.DecrementPendingCallbacks|ADV> {0}, after decrementing _pendingCallbacks: {1}", ObjectID, _pendingCallbacks);
if ((0 == remaining || release) && _gcHandle.IsAllocated)
{
SqlClientEventSource.Log.AdvancedTraceEvent("<sc.TdsParserStateObject.DecrementPendingCallbacks|ADV> {0}, FREEING HANDLE!", ObjectID);
_gcHandle.Free();
}
// 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 == null) || (0 <= remaining && remaining < 3), $"_pendingCallbacks values is invalid after decrementing: {remaining}");
return remaining;
}
internal void Dispose()
{
SafeHandle packetHandle = _sniPacket;
SafeHandle sessionHandle = _sessionHandle;
SafeHandle asyncAttnPacket = _sniAsyncAttnPacket;
_sniPacket = null;
_sessionHandle = null;
_sniAsyncAttnPacket = null;
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);
}
if (null != sessionHandle || null != packetHandle)
{
// Comment CloseMARSSession
// UNDONE - if there are pending reads or writes on logical connections, we need to block
// here for the callbacks!!! This only applies to async. Should be fixed by async fixes for
// AD unload/exit.
// TODO: Make this a BID trace point!
RuntimeHelpers.PrepareConstrainedRegions();
try
{ }
finally
{
if (packetHandle != null)
{
packetHandle.Dispose();
}
if (asyncAttnPacket != null)
{
asyncAttnPacket.Dispose();
}
if (sessionHandle != null)
{
sessionHandle.Dispose();
DecrementPendingCallbacks(true); // Will dispose of GC handle.
}
}
}
if (_writePacketCache != null)