Skip to content

Commit 2f871a0

Browse files
authored
Merge pull request #422 from DataObjects-NET/7.1-recordsetreader-overlaps-sqlexception
RecordSetReader overlaps SQL exception when DbCommand execution failed
2 parents 5e0f02d + 49b1c1d commit 2f871a0

File tree

3 files changed

+68
-5
lines changed

3 files changed

+68
-5
lines changed

ChangeLog/7.1.5_dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[main] Addressed certain cases of overlaping server-side error by new one when temporary tables are used in query

Orm/Xtensive.Orm.Tests/Linq/LocalCollectionsTest.cs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using Xtensive.Orm.Tests.ObjectModel;
1616
using Xtensive.Orm.Tests.ObjectModel.ChinookDO;
1717
using System.Threading.Tasks;
18+
using Xtensive.Orm.Configuration;
1819

1920
namespace Xtensive.Orm.Tests.Linq.LocalCollectionsTest_Model
2021
{
@@ -910,6 +911,40 @@ public void ClosureCacheTest()
910911
});
911912
}
912913

914+
[Test]
915+
public void TempTableCleanupAfterTimeout()
916+
{
917+
Require.ProviderIs(StorageProvider.PostgreSql | StorageProvider.SqlServer, "Uses database-specific syntax.");
918+
919+
var localItems = GetLocalItems(100);
920+
921+
var sessionConfig = new SessionConfiguration(SessionOptions.Default | SessionOptions.AutoActivation);
922+
sessionConfig.BatchSize = 1;
923+
sessionConfig.DefaultCommandTimeout = 10;
924+
925+
using (var session = Domain.OpenSession(sessionConfig))
926+
using (var tx = session.OpenTransaction()) {
927+
session.Events.DbCommandExecuting += Events_DbCommandExecuting;
928+
929+
var storeQueryable = session.Query.Store(localItems);
930+
_ = Assert.Throws<OperationTimeoutException>(() => session.Query.All<Invoice>()
931+
.Where(invoice => invoice.Commission > storeQueryable.Max(poco => poco.Value2)).ToArray());
932+
933+
session.Events.DbCommandExecuting -= Events_DbCommandExecuting;
934+
}
935+
936+
static void Events_DbCommandExecuting(object sender, DbCommandEventArgs e)
937+
{
938+
// makes query to delay more than current setting of command timeout
939+
var originalCommandText = e.Command.CommandText;
940+
if (originalCommandText.Contains("Select", StringComparison.OrdinalIgnoreCase)) {
941+
var session = ((SessionEventAccessor) sender).Session;
942+
e.Command.CommandText = GetFakeDelayedCommandTest(session, originalCommandText);
943+
}
944+
}
945+
}
946+
947+
913948
[Test]
914949
[Ignore("Very long")]
915950
public void VeryLongTest()
@@ -935,7 +970,18 @@ private IEnumerable<Poco<int, decimal, string>> GetLocalItems(int count)
935970
Value3 = Guid.NewGuid().ToString()
936971
}
937972
)
938-
.ToList();
973+
.ToList(count);
974+
}
975+
976+
private static string GetFakeDelayedCommandTest(Session session, string originalCommandText)
977+
{
978+
var currentCommandTimeout = session.CommandTimeout.Value;
979+
980+
return StorageProviderInfo.Instance.Provider switch {
981+
StorageProvider.SqlServer => $"WAITFOR DELAY '{TimeSpan.FromSeconds(currentCommandTimeout + 2).ToString("hh:mm.ss")}'" + originalCommandText,
982+
StorageProvider.PostgreSql => $"SELECT pg_sleep({currentCommandTimeout + 2});" + originalCommandText,
983+
_ => throw new ArgumentOutOfRangeException()
984+
};
939985
}
940986
}
941987
}

Orm/Xtensive.Orm/Orm/Rse/RecordSetReader.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,13 @@ private async ValueTask Prepare(bool executeAsync)
139139
context.SetValue(provider, enumerationMarker, true);
140140
}
141141

142+
bool isOnEnumerateSuccessful = false;
142143
try {
143144
dataReader = executeAsync
144145
? await provider.OnEnumerateAsync(context, token).ConfigureAwait(false)
145146
: provider.OnEnumerate(context);
146147

148+
isOnEnumerateSuccessful = true;
147149
if (isGreedy && !dataReader.IsInMemory) {
148150
var tuples = new List<Tuple>();
149151
if (executeAsync) {
@@ -164,16 +166,30 @@ private async ValueTask Prepare(bool executeAsync)
164166
}
165167
}
166168
catch {
167-
FinishEnumeration(true);
169+
FinishEnumeration(true, !isOnEnumerateSuccessful);
168170
throw;
169171
}
170172
state = State.Prepared;
171173
}
172174

173-
private void FinishEnumeration(bool isError)
175+
private void FinishEnumeration(bool isError, bool isErrorOnServerSide = false)
174176
{
175-
if (!enumerated) {
176-
provider?.OnAfterEnumerate(context);
177+
if (isErrorOnServerSide) {
178+
// Possible connection closing because of server-side error, like operation timeout,
179+
// which makes finish of some providers imposible, like ones that work with temporary tables and require clean-up.
180+
// Exception may happen but we must prevent overlaping original exception with new one.
181+
if (!enumerated) {
182+
try {
183+
provider?.OnAfterEnumerate(context);
184+
}
185+
catch {
186+
}
187+
}
188+
}
189+
else {
190+
if (!enumerated) {
191+
provider?.OnAfterEnumerate(context);
192+
}
177193
}
178194

179195
if (!isError) {

0 commit comments

Comments
 (0)