0

I have a VB.NET program using .NET Framework 3.5 and Oracle.DataAccess 19.1 that connects to a Oracle remote database server 11g (pooling=false) to query data.

The program running fine but once in a blue moon (not fix timing, can be as soon as 2 weeks or few month later), the program just hang. After few incidents (adding log after every incidents), we're able to nail down and identify the line of program which causing the hang, which is oracleDataAdapter.Fill(dataset) as shown below code snippet.

    Private Function FetchData(connection As OracleConnection, str_SQL As String, dataset As DataSet) As Integer
    Dim oracleDataAdapter As New OracleDataAdapter(connection.CreateCommand())

    Try
        If connection.State <> ConnectionState.Open Then
            connection.Open()
        End If

        oracleDataAdapter.SelectCommand.AddToStatementCache = False
        oracleDataAdapter.SelectCommand.NotificationAutoEnlist = False
        oracleDataAdapter.SelectCommand.CommandTimeout = 60
        oracleDataAdapter.SelectCommand.CommandText = str_SQL
        Dim int_Count As Integer = oracleDataAdapter.Fill(dataset)

        Return int_Count
    Catch ex As Exception
        LogError(ex)
    Finally
        oracleDataAdapter.Dispose()
    End Try
End Function

Even thought we're able to identify the code that hang, but we have no idea why is hang. We did try to break the query by invoking OracleCommand.Cancel but didn't helped.

We though of network issue, thus we try to simulate oracle client and db server communication packet loss by unplugging the network cable at the app server during executing of the query and plug it back few second later. In some cases, the program just hang at the line of data fetching infinitely (as highlighted above code snippet).

We did same test by pointing database to XE 18c. We can't simulate the above hang issue but rather ORA 03113 error was thrown when unplugging and plunging back of network cable during executing query (usually it happened within 60 second). This is still ok as the program able to continue instead of hanging and error handling can do a retry and resume gracefully.

ADDED Aug 7 We developed program (in .NET) to try to reproduce the hang issue. The program just keep fetching data from database. In normal execution, the query will take ~4 seconds to fetch ~140,000 records. The hang able to simulate 4 out of 15 times (~30%).

Below the steps to reproduce the hang

  1. Run the application. The app just keep querying data from the database server. The function as show below.
  2. Observe the query execution, at ~ 2nd or 3rd second (based on the log display on the screen), unplug the network cable
  3. Plug back the network cable at ~15th second
  4. The program occasionally just hang at the line oracleDataAdapter.Fill(dataset)

We tested against different version of unmanaged and managed Oracle Client 11, 12 and 19 and database server 11 XE and 18 XE, hang issue able to reproduced.

Is this the behavior or the Oracle Client? Any suggestion to break the execution? Or any suggestion to handle this behavior?

  • 2
    Start removing _throw ex_ (This will kill the stacktrace and an upper level catch block is unable to know where the error occurs). Leave just only _throw_ but before that add a logging to file for the full exception data. – Steve Jul 30 '21 at 13:27
  • If you're not going to do anything in the `Catch` block but rethrow then get rid of the `Catch` block altogether. You don't have to have it if you have a `Finally` block. As suggested though, if you are going to rethrow, do so implicitly, not explicitly. – jmcilhinney Jul 30 '21 at 14:16
  • pooling=false? are you certain that you aren't causing a deadlock by reusing a connection that is doing something else? – Caius Jard Jul 30 '21 at 17:24
  • Steve, in my example no exception has been catch at all. The program just hanged at oracleDataAdapter.Fill(dataset). If there is exception, application can handle and continue gracefully. – Fook Ming In Jul 31 '21 at 15:00
  • @jmcilhinney, I understand that. In actual app, there is an exception loggin instead of rethrow the exception. My problem is the program just hanged at **oracleDataAdapter.Fill(dataset)**, no exception thrown. Thus program cannot continue. – Fook Ming In Jul 31 '21 at 15:07
  • @CaiusJard, the program is windows app. The connection is open when the program started and close when program terminated. The program interlock the usage of the connection by mutex, thus we're sure no deadlock of using the connection. We do not have the hang issue when back-end database is sqlserver. This only happened in oracle database. – Fook Ming In Jul 31 '21 at 15:14
  • *The connection is open when the program started and close when program terminated* - why do you micro manage your connection in this way? I'm increasingly thinking that your DB is waiting on a deadlock; your mutex will be nothing to do with it and won't prevent it. You've gone to some effort to do things in a non standard way; it's perhaps unsurprising that odd behaviors are manifesting – Caius Jard Jul 31 '21 at 20:12
  • This is an back-end application where performing tasks in a very fast speed with high volume of db transactions, thus the connection maintain opened throughout the life-cycle of the application. The program hang issue didn't happened when connecting to sqlserver database, this only happened when oracle database is used. The open/close connection for every sql execution (with the help of connection pooling) is more benefit to web app kind of app. – Fook Ming In Aug 02 '21 at 02:00
  • Note sure why you have: `dataset.Dispose()` since you haven't created the DataSet here. – Tu deschizi eu inchid Aug 03 '21 at 14:51
  • You're right, the `dataset.Dispose()` should appear here. Code amended. – Fook Ming In Aug 04 '21 at 04:30

1 Answers1

0

According to OracleDataAdapter Class:

Thread Safety

All public static methods are thread-safe, although instance methods do not guarantee thread safety.

For a connection, it's recommended that a connection only be opened for as long as it's needed. If a connection is continuously being used, it may make sense to keep it open. Otherwise, open/close the connection each time. Of course if you have other operations that you need to perform using the connection, you can pass the connection to the method as a parameter. However, close the connection when you're finished with your operations.

The following assumes that the NuGet package Oracle.ManagedDataAccess has been installed.

Try the following one of the following:

Imports Oracle.ManagedDataAccess.Client

Public Module Helper

    Public Function FetchDataDS(connectionStr As String, str_SQL As String) As DataSet
        Dim int_Count As Integer = 0
        Dim ds As DataSet = New DataSet()

        Using connection As OracleConnection = New OracleConnection(connectionStr)
            'open
            connection.Open()

            Using oracleDataAdapter As OracleDataAdapter = New OracleDataAdapter With {
                .SelectCommand = New OracleCommand With {
                        .AddToStatementCache = False,
                        .NotificationAutoEnlist = False,
                        .CommandTimeout = 60,
                        .Connection = connection,
                        .CommandText = str_SQL
                    }}

                int_Count = oracleDataAdapter.Fill(ds)
            End Using
        End Using

        Return ds
    End Function

    Public Function FetchDataDS(connection As OracleConnection, str_SQL As String) As DataSet
        Dim int_Count As Integer = 0
        Dim ds As DataSet = New DataSet()

        If connection.State = ConnectionState.Closed Then
            'open
            connection.Open()
        End If

        If Not connection.State = ConnectionState.Open Then
            Throw New Exception("Error (FetchDataDS) - Connection state is: " & connection.State.ToString())
        End If

        Using oracleDataAdapter As OracleDataAdapter = New OracleDataAdapter With {
                .SelectCommand = New OracleCommand With {
                        .AddToStatementCache = False,
                        .NotificationAutoEnlist = False,
                        .CommandTimeout = 60,
                        .Connection = connection,
                        .CommandText = str_SQL
                    }}

            int_Count = oracleDataAdapter.Fill(ds)
        End Using

        Return ds
    End Function

    Public Function FetchDataDS(connectionStr As String, str_SQL As String, ds As DataSet) As Integer
        Dim int_Count As Integer = 0

        Using connection As OracleConnection = New OracleConnection(connectionStr)
            'open
            connection.Open()

            Using oracleDataAdapter As OracleDataAdapter = New OracleDataAdapter With {
                .SelectCommand = New OracleCommand With {
                        .AddToStatementCache = False,
                        .NotificationAutoEnlist = False,
                        .CommandTimeout = 60,
                        .Connection = connection,
                        .CommandText = str_SQL
                    }}

                int_Count = oracleDataAdapter.Fill(ds)
            End Using
        End Using

        Return int_Count
    End Function

    Public Function FetchDataDS(connection As OracleConnection, str_SQL As String, ds As DataSet) As Integer
        Dim int_Count As Integer = 0

        If connection.State = ConnectionState.Closed Then
            'open
            connection.Open()
        End If

        If Not connection.State = ConnectionState.Open Then
            Throw New Exception("Error (FetchDataDS) - Connection state is: " & connection.State.ToString())
        End If

        Using oracleDataAdapter As OracleDataAdapter = New OracleDataAdapter With {
                .SelectCommand = New OracleCommand With {
                        .AddToStatementCache = False,
                        .NotificationAutoEnlist = False,
                        .CommandTimeout = 60,
                        .Connection = connection,
                        .CommandText = str_SQL
                    }}

            int_Count = oracleDataAdapter.Fill(ds)
        End Using

        Return int_Count
    End Function

    Public Function FetchDataDT(connectionStr As String, str_SQL As String) As DataTable
        Dim int_Count As Integer = 0
        Dim dt As DataTable = New DataTable()

        Using connection As OracleConnection = New OracleConnection(connectionStr)
            'open
            connection.Open()

            Using oracleDataAdapter As OracleDataAdapter = New OracleDataAdapter With {
                .SelectCommand = New OracleCommand With {
                        .AddToStatementCache = False,
                        .NotificationAutoEnlist = False,
                        .CommandTimeout = 60,
                        .Connection = connection,
                        .CommandText = str_SQL
                    }}

                int_Count = oracleDataAdapter.Fill(dt)
            End Using
        End Using

        Return dt
    End Function

    Public Function FetchDataDT(connection As OracleConnection, str_SQL As String) As DataTable
        Dim int_Count As Integer = 0
        Dim dt As DataTable = New DataTable()

        If connection.State = ConnectionState.Closed Then
            'open
            connection.Open()
        End If

        If Not connection.State = ConnectionState.Open Then
            Throw New Exception("Error (FetchDataDT) - Connection state is: " & connection.State.ToString())
        End If

        Using oracleDataAdapter As OracleDataAdapter = New OracleDataAdapter With {
                .SelectCommand = New OracleCommand With {
                        .AddToStatementCache = False,
                        .NotificationAutoEnlist = False,
                        .CommandTimeout = 60,
                        .Connection = connection,
                        .CommandText = str_SQL
                    }}

            int_Count = oracleDataAdapter.Fill(dt)
        End Using

        Return dt
    End Function

    Public Function FetchDataDT(connection As OracleConnection, str_SQL As String, dt As DataTable) As Integer
        Dim int_Count As Integer = 0

        If connection.State = ConnectionState.Closed Then
            'open
            connection.Open()
        End If

        If Not connection.State = ConnectionState.Open Then
            Throw New Exception("Error (FetchDataDT) - Connection state is: " & connection.State.ToString())
        End If

        Using oracleDataAdapter As OracleDataAdapter = New OracleDataAdapter With {
                .SelectCommand = New OracleCommand With {
                        .AddToStatementCache = False,
                        .NotificationAutoEnlist = False,
                        .CommandTimeout = 60,
                        .Connection = connection,
                        .CommandText = str_SQL
                    }}

            int_Count = oracleDataAdapter.Fill(dt)
        End Using

        Return int_Count
    End Function
End Module

Wherever you catch exceptions, also catch Oracle.ManagedDataAccess.Client.OracleException -

something like the following:

Try
    'ToDo: add desired code
       ...
Catch ex As OracleException
    'ToDo: log exception
Catch ex As Exception
    'ToDo: log exception
End Try

Resources

Tu deschizi eu inchid
  • 4,117
  • 3
  • 13
  • 24
  • In my example, the connection was passed from the outside of the function. It was opened before calling the function. The app using the same opened connection throughout the lifetime of the app. No closing of connection until the end of the program. Does this matter? – Fook Ming In Jul 31 '21 at 14:51
  • This is an back-end application where performing tasks in a fast speed with high volume of db transactions, thus the connection maintain opened throughout the life-cycle of the application. In the function, we did implement `If not connection.open = ConnectionState.Open Then connection.open`. But app sometime still hanged at `oracleDataAdapter.Fill(dataset)`. – Fook Ming In Aug 02 '21 at 03:05
  • Have you added the code that throws an error if the connection isn't open? Is your code in a class or a module? Log the value of both `ConnectionState` and `ConnectionTimeout` just before the fill statement. You may consider adding more code to your post, such as the code that calls FetchData including the SQL statement. – Tu deschizi eu inchid Aug 02 '21 at 12:41
  • You may also try running a version of `Oracle.ManagedDataAccess` that is closer to your database version (ie: 12.1) – Tu deschizi eu inchid Aug 02 '21 at 12:52
  • This code was added `If connection .State <> ConnectionState.Open Then connection.Open()`. If failed to open, it will be handled by the catch exception block and log down the error. The code is in a class. Based on the application log, no errors were observed. – Fook Ming In Aug 03 '21 at 10:36
  • What is the major different between `Oracle.ManagedDataAccess` and `Oracle.DataAccess` ? Basically, the program just using the simple function 'CRUD' of `Oracle.DataAccess` library. – Fook Ming In Aug 03 '21 at 10:40
  • The following may be helpful: https://stackoverflow.com/questions/17583289/oracle-odp-net-managed-vs-unmanaged-driver and https://blogs.oracle.com/oraclemagazine/an-easy-drive-to-net. – Tu deschizi eu inchid Aug 03 '21 at 14:48
  • Your issue seems to be similar to the following: https://stackoverflow.com/questions/44916076/oracledataadapter-fill-method-hangs-how-do-i-handle-connection-terminations-dr – Tu deschizi eu inchid Aug 03 '21 at 14:48
  • In that thread, it seems solved by adding `connection.open` before the `oracleDataAdapter.Fill(dataset)` – Fook Ming In Aug 04 '21 at 08:35
  • You may consider opening a case with Oracle who will be able to assist you in resolving your issue. If your team doesn't have a service contract with Oracle, your database administration team probably does. – Tu deschizi eu inchid Aug 09 '21 at 11:48
  • Thanks for your info. We do not have the service contract with Oracle, thus I'm not sure will Oracle entertain. Anyway, I'll just give a try. – Fook Ming In Aug 12 '21 at 01:52