From 11d56f868c528a3105384a1f58b5b7f755a10e1f Mon Sep 17 00:00:00 2001 From: Lau Date: Fri, 27 Mar 2020 07:24:39 +0100 Subject: [PATCH] Implement workaround for "To many tables" error: ODBC Error Code: -1311 [HY001] [Microsoft][ODBC Microsoft Access Driver] Cannot open any more tables. If too many commands get executed in short succession, ACE/Jet can run out of table handles. This can happen despite proper disposal of OdbcCommand and OdbcDataReader objects. Waiting for a couple of milliseconds will give ACE/Jet enough time to catch up. --- .../Internal/JetTransientExceptionDetector.cs | 125 ++++-------------- .../CreateNorthwindTest.cs | 29 +++- 2 files changed, 46 insertions(+), 108 deletions(-) diff --git a/src/EFCore.Jet/Storage/Internal/JetTransientExceptionDetector.cs b/src/EFCore.Jet/Storage/Internal/JetTransientExceptionDetector.cs index 3bd528e..1cc0c71 100644 --- a/src/EFCore.Jet/Storage/Internal/JetTransientExceptionDetector.cs +++ b/src/EFCore.Jet/Storage/Internal/JetTransientExceptionDetector.cs @@ -1,6 +1,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Data.Jet; using System.Data.OleDb; using JetBrains.Annotations; @@ -13,108 +14,30 @@ namespace EntityFrameworkCore.Jet.Storage.Internal { public static bool ShouldRetryOn([NotNull] Exception ex) { - var sqlException = ex as OleDbException; - if (sqlException != null) + DataAccessType dataAccessType; + + var exceptionFullName = ex.GetType().FullName; + + if (exceptionFullName == "System.Data.OleDb.OleDbException") + dataAccessType = DataAccessType.OleDb; + else if (exceptionFullName == "System.Data.Odbc.OdbcException") + dataAccessType = DataAccessType.Odbc; + else + return false; + + dynamic sqlException = ex; + foreach (var err in sqlException.Errors) { - foreach (OleDbError err in sqlException.Errors) + // TODO: Check additional ACE/Jet Errors + switch (err.NativeError) { - // TODO: Check proper Jet Errors - /* - switch (err.NativeError) - { - // SQL Error Code: 49920 - // Cannot process request. Too many operations in progress for subscription "%ld". - // The service is busy processing multiple requests for this subscription. - // Requests are currently blocked for resource optimization. Query sys.dm_operation_status for operation status. - // Wait until pending requests are complete or delete one of your pending requests and retry your request later. - case 49920: - // SQL Error Code: 49919 - // Cannot process create or update request. Too many create or update operations in progress for subscription "%ld". - // The service is busy processing multiple create or update requests for your subscription or server. - // Requests are currently blocked for resource optimization. Query sys.dm_operation_status for pending operations. - // Wait till pending create or update requests are complete or delete one of your pending requests and - // retry your request later. - case 49919: - // SQL Error Code: 49918 - // Cannot process request. Not enough resources to process request. - // The service is currently busy.Please retry the request later. - case 49918: - // SQL Error Code: 41839 - // Transaction exceeded the maximum number of commit dependencies. - case 41839: - // SQL Error Code: 41325 - // The current transaction failed to commit due to a serializable validation failure. - case 41325: - // SQL Error Code: 41305 - // The current transaction failed to commit due to a repeatable read validation failure. - case 41305: - // SQL Error Code: 41302 - // The current transaction attempted to update a record that has been updated since the transaction started. - case 41302: - // SQL Error Code: 41301 - // Dependency failure: a dependency was taken on another transaction that later failed to commit. - case 41301: - // SQL Error Code: 40613 - // Database XXXX on server YYYY is not currently available. Please retry the connection later. - // If the problem persists, contact customer support, and provide them the session tracing ID of ZZZZZ. - case 40613: - // SQL Error Code: 40501 - // The service is currently busy. Retry the request after 10 seconds. Code: (reason code to be decoded). - case 40501: - // SQL Error Code: 40197 - // The service has encountered an error processing your request. Please try again. - case 40197: - // SQL Error Code: 10929 - // Resource ID: %d. The %s minimum guarantee is %d, maximum limit is %d and the current usage for the database is %d. - // However, the server is currently too busy to support requests greater than %d for this database. - // For more information, see http://go.microsoft.com/fwlink/?LinkId=267637. Otherwise, please try again. - case 10929: - // SQL Error Code: 10928 - // Resource ID: %d. The %s limit for the database is %d and has been reached. For more information, - // see http://go.microsoft.com/fwlink/?LinkId=267637. - case 10928: - // SQL Error Code: 10060 - // A network-related or instance-specific error occurred while establishing a connection to Jet. - // The server was not found or was not accessible. Verify that the instance name is correct and that Jet - // is configured to allow remote connections. (provider: TCP Provider, error: 0 - A connection attempt failed - // because the connected party did not properly respond after a period of time, or established connection failed - // because connected host has failed to respond.)"} - case 10060: - // SQL Error Code: 10054 - // A transport-level error has occurred when sending the request to the server. - // (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by the remote host.) - case 10054: - // SQL Error Code: 10053 - // A transport-level error has occurred when receiving results from the server. - // An established connection was aborted by the software in your host machine. - case 10053: - // SQL Error Code: 1205 - // Deadlock - case 1205: - // SQL Error Code: 233 - // The client was unable to establish a connection because of an error during connection initialization process before login. - // Possible causes include the following: the client tried to connect to an unsupported version of Jet; - // the server was too busy to accept new connections; or there was a resource limitation (insufficient memory or maximum - // allowed connections) on the server. (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by - // the remote host.) - case 233: - // SQL Error Code: 121 - // The semaphore timeout period has expired - case 121: - // SQL Error Code: 64 - // A connection was successfully established with the server, but then an error occurred during the login process. - // (provider: TCP Provider, error: 0 - The specified network name is no longer available.) - case 64: - // DBNETLIB Error Code: 20 - // The instance of Jet you attempted to connect to does not support encryption. - case 20: - return true; - // This exception can be thrown even if the operation completed succesfully, so it's safer to let the application fail. - // DBNETLIB Error Code: -2 - // Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding. The statement has been terminated. - //case -2: - } - */ + // ODBC Error Code: -1311 [HY001] + // [Microsoft][ODBC Microsoft Access Driver] Cannot open any more tables. + // If too many commands get executed in short succession, ACE/Jet can run out of table handles. + // This can happen despite proper disposal of OdbcCommand and OdbcDataReader objects. + // Waiting for a couple of milliseconds will give ACE/Jet enough time to catch up. + case -1311 when dataAccessType == DataAccessType.Odbc: + return true; } return false; @@ -128,4 +51,4 @@ namespace EntityFrameworkCore.Jet.Storage.Internal return false; } } -} +} \ No newline at end of file diff --git a/test/System.Data.Jet.Test/CreateNorthwindTest.cs b/test/System.Data.Jet.Test/CreateNorthwindTest.cs index 8cbf023..6be3de8 100644 --- a/test/System.Data.Jet.Test/CreateNorthwindTest.cs +++ b/test/System.Data.Jet.Test/CreateNorthwindTest.cs @@ -2,6 +2,7 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; +using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace System.Data.Jet.Test @@ -42,21 +43,35 @@ namespace System.Data.Jet.Test using var command = _connection.CreateCommand(); var script = File.ReadAllText(_scriptPath); - foreach (var batch in - new Regex(@"^GO", RegexOptions.IgnoreCase | RegexOptions.Multiline, TimeSpan.FromMilliseconds(1000.0)) - .Split(script) - .Where(b => !string.IsNullOrEmpty(b))) + var batches = new Regex(@"\s*;\s*", RegexOptions.IgnoreCase | RegexOptions.Multiline, TimeSpan.FromMilliseconds(1000.0)) + .Split(script) + .Where(b => !string.IsNullOrEmpty(b)) + .ToList(); + + var retryWaitTime = TimeSpan.FromMilliseconds(250); + const int maxRetryCount = 6; + var retryCount = 0; + + foreach (var batch in batches) { command.CommandText = batch; + try { command.ExecuteNonQuery(); + retryCount = 0; } catch (Exception e) { - Console.WriteLine(e.Message); - Console.WriteLine(batch); - throw; + if (retryCount >= maxRetryCount) + { + Console.WriteLine(e.Message); + Console.WriteLine(batch); + throw; + } + + retryCount++; + Thread.Sleep(retryWaitTime); } } }