Skip to content

Commit 33364e7

Browse files
authored
Add async snapshot continue capability for multipacket fields (#3161)
1 parent cb4f568 commit 33364e7

File tree

11 files changed

+1155
-205
lines changed

11 files changed

+1155
-205
lines changed

Diff for: src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs

+7-2
Original file line numberDiff line numberDiff line change
@@ -3766,7 +3766,7 @@ private TdsOperationStatus TryNextResult(out bool more)
37663766
if (result != TdsOperationStatus.Done)
37673767
{
37683768
more = false;
3769-
return TdsOperationStatus.Done;
3769+
return result;
37703770
}
37713771

37723772
// In the case of not closing the reader, null out the metadata AFTER
@@ -4525,7 +4525,12 @@ private TdsOperationStatus TryResetBlobState()
45254525
#if DEBUG
45264526
else
45274527
{
4528-
Debug.Assert((_sharedState._columnDataBytesRemaining == 0 || _sharedState._columnDataBytesRemaining == -1) && _stateObj._longlen == 0, "Haven't read header yet, but column is partially read?");
4528+
Debug.Assert(
4529+
(_sharedState._columnDataBytesRemaining == 0 || _sharedState._columnDataBytesRemaining == -1)
4530+
&&
4531+
(_stateObj._longlen == 0 || _stateObj.IsSnapshotContinuing()),
4532+
"Haven't read header yet, but column is partially read?"
4533+
);
45294534
}
45304535
#endif
45314536

Diff for: src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs

+282-78
Large diffs are not rendered by default.

Diff for: src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -4524,7 +4524,12 @@ private TdsOperationStatus TryResetBlobState()
45244524
#if DEBUG
45254525
else
45264526
{
4527-
Debug.Assert((_sharedState._columnDataBytesRemaining == 0 || _sharedState._columnDataBytesRemaining == -1) && _stateObj._longlen == 0, "Haven't read header yet, but column is partially read?");
4527+
Debug.Assert(
4528+
(_sharedState._columnDataBytesRemaining == 0 || _sharedState._columnDataBytesRemaining == -1)
4529+
&&
4530+
(_stateObj._longlen == 0 || _stateObj.IsSnapshotContinuing()),
4531+
"Haven't read header yet, but column is partially read?"
4532+
);
45284533
}
45294534
#endif
45304535

Diff for: src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs

+283-79
Large diffs are not rendered by default.

Diff for: src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs

+44
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ private enum Tristate : byte
2121
internal const string UseMinimumLoginTimeoutString = @"Switch.Microsoft.Data.SqlClient.UseOneSecFloorInTimeoutCalculationDuringLogin";
2222
internal const string LegacyVarTimeZeroScaleBehaviourString = @"Switch.Microsoft.Data.SqlClient.LegacyVarTimeZeroScaleBehaviour";
2323
internal const string UseCompatibilityProcessSniString = @"Switch.Microsoft.Data.SqlClient.UseCompatibilityProcessSni";
24+
internal const string UseCompatibilityAsyncBehaviourString = @"Switch.Microsoft.Data.SqlClient.UseCompatibilityAsyncBehaviour";
2425

2526
// this field is accessed through reflection in tests and should not be renamed or have the type changed without refactoring NullRow related tests
2627
private static Tristate s_legacyRowVersionNullBehavior;
@@ -30,6 +31,7 @@ private enum Tristate : byte
3031
// this field is accessed through reflection in Microsoft.Data.SqlClient.Tests.SqlParameterTests and should not be renamed or have the type changed without refactoring related tests
3132
private static Tristate s_legacyVarTimeZeroScaleBehaviour;
3233
private static Tristate s_useCompatProcessSni;
34+
private static Tristate s_useCompatAsyncBehaviour;
3335

3436
#if NET
3537
static LocalAppContextSwitches()
@@ -85,6 +87,12 @@ public static bool DisableTNIRByDefault
8587
}
8688
}
8789
#endif
90+
/// <summary>
91+
/// In TdsParser the ProcessSni function changed significantly when the packet
92+
/// multiplexing code needed for high speed multi-packet column values was added.
93+
/// In case of compatibility problems this switch will change TdsParser to use
94+
/// the previous version of the function.
95+
/// </summary>
8896
public static bool UseCompatibilityProcessSni
8997
{
9098
get
@@ -104,6 +112,42 @@ public static bool UseCompatibilityProcessSni
104112
}
105113
}
106114

115+
/// <summary>
116+
/// In TdsParser the async multi-packet column value fetch behaviour is capable of
117+
/// using a continue snapshot state in addition to the original replay from start
118+
/// logic.
119+
/// This switch disables use of the continue snapshot state. This switch will always
120+
/// return true if <see cref="UseCompatibilityProcessSni"/> is enabled because the
121+
/// continue state is not stable without the multiplexer.
122+
/// </summary>
123+
public static bool UseCompatibilityAsyncBehaviour
124+
{
125+
get
126+
{
127+
if (UseCompatibilityProcessSni)
128+
{
129+
// If ProcessSni compatibility mode has been enabled then the packet
130+
// multiplexer has been disabled. The new async behaviour using continue
131+
// point capture is only stable if the multiplexer is enabled so we must
132+
// return true to enable compatibility async behaviour using only restarts.
133+
return true;
134+
}
135+
136+
if (s_useCompatAsyncBehaviour == Tristate.NotInitialized)
137+
{
138+
if (AppContext.TryGetSwitch(UseCompatibilityAsyncBehaviourString, out bool returnedValue) && returnedValue)
139+
{
140+
s_useCompatAsyncBehaviour = Tristate.True;
141+
}
142+
else
143+
{
144+
s_useCompatAsyncBehaviour = Tristate.False;
145+
}
146+
}
147+
return s_useCompatAsyncBehaviour == Tristate.True;
148+
}
149+
}
150+
107151
/// <summary>
108152
/// When using Encrypt=false in the connection string, a security warning is output to the console if the TLS version is 1.2 or lower.
109153
/// This warning can be suppressed by enabling this AppContext switch.

Diff for: src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCachedBuffer.cs

+44-6
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,34 @@ private SqlCachedBuffer(List<byte[]> cachedBytes)
3737
/// </summary>
3838
internal static TdsOperationStatus TryCreate(SqlMetaDataPriv metadata, TdsParser parser, TdsParserStateObject stateObj, out SqlCachedBuffer buffer)
3939
{
40-
byte[] byteArr;
41-
42-
List<byte[]> cachedBytes = new();
4340
buffer = null;
4441

42+
(bool isAvailable, bool isStarting, bool isContinuing) = stateObj.GetSnapshotStatuses();
43+
44+
List<byte[]> cachedBytes = null;
45+
if (isAvailable)
46+
{
47+
cachedBytes = stateObj.TryTakeSnapshotStorage() as List<byte[]>;
48+
if (cachedBytes != null && !isStarting && !isContinuing)
49+
{
50+
stateObj.SetSnapshotStorage(null);
51+
}
52+
}
53+
54+
if (cachedBytes == null)
55+
{
56+
cachedBytes = new List<byte[]>();
57+
}
58+
59+
4560
// the very first length is already read.
4661
TdsOperationStatus result = parser.TryPlpBytesLeft(stateObj, out ulong plplength);
4762
if (result != TdsOperationStatus.Done)
4863
{
4964
return result;
5065
}
5166

67+
5268
// For now we only handle Plp data from the parser directly.
5369
Debug.Assert(metadata.metaType.IsPlp, "SqlCachedBuffer call on a non-plp data");
5470
do
@@ -59,13 +75,25 @@ internal static TdsOperationStatus TryCreate(SqlMetaDataPriv metadata, TdsParser
5975
}
6076
do
6177
{
78+
bool returnAfterAdd = false;
6279
int cb = (plplength > (ulong)MaxChunkSize) ? MaxChunkSize : (int)plplength;
63-
byteArr = new byte[cb];
64-
result = stateObj.TryReadPlpBytes(ref byteArr, 0, cb, out cb);
80+
byte[] byteArr = new byte[cb];
81+
// pass false for the writeDataSizeToSnapshot parameter because we want to only take data
82+
// from the current packet and not try to do a continue-capable multi packet read
83+
result = stateObj.TryReadPlpBytes(ref byteArr, 0, cb, out cb, writeDataSizeToSnapshot: false, compatibilityMode: false);
6584
if (result != TdsOperationStatus.Done)
6685
{
67-
return result;
86+
if (result == TdsOperationStatus.NeedMoreData && isAvailable && cb == byteArr.Length)
87+
{
88+
// succeeded in getting the data but failed to find the next plp length
89+
returnAfterAdd = true;
90+
}
91+
else
92+
{
93+
return result;
94+
}
6895
}
96+
6997
Debug.Assert(cb == byteArr.Length);
7098
if (cachedBytes.Count == 0)
7199
{
@@ -74,6 +102,16 @@ internal static TdsOperationStatus TryCreate(SqlMetaDataPriv metadata, TdsParser
74102
}
75103
cachedBytes.Add(byteArr);
76104
plplength -= (ulong)cb;
105+
106+
if (returnAfterAdd)
107+
{
108+
if (isStarting || isContinuing)
109+
{
110+
stateObj.SetSnapshotStorage(cachedBytes);
111+
}
112+
return result;
113+
}
114+
77115
} while (plplength > 0);
78116

79117
result = parser.TryPlpBytesLeft(stateObj, out plplength);

0 commit comments

Comments
 (0)