dotnet-db-samples icon indicating copy to clipboard operation
dotnet-db-samples copied to clipboard

Regression: watching for database change with OracleDependency stopped working with Oracle.ManagedDataAccess.Core version 23.8.0

Open 0xced opened this issue 7 months ago • 6 comments

I have written a basic sample code, using Testcontainers for .NET, which uses OracleDependency to watch for database changes. Note that the Oracle.ManagedDataAccess.Core version is 23.7.0.

watch.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>true</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Oracle.ManagedDataAccess.Core" Version="23.7.0" />
    <PackageReference Include="Testcontainers.Oracle" Version="4.4.0" />
  </ItemGroup>

</Project>

Program.cs

using Testcontainers.Oracle;

var resetEvent = new ManualResetEventSlim(false);

await using var container = new OracleBuilder().WithImage("gvenzl/oracle-free:23-slim-faststart").Build();
await container.StartAsync();
var connectionString = container.GetConnectionString();

using var executor = new OracleExecutor(connectionString, sysDba: false);
using (var sysDbaExecutor = new OracleExecutor(connectionString, sysDba: true))
{
    sysDbaExecutor.ExecuteNonQuery($"grant change notification to {executor.UserId}");
}

executor.ExecuteNonQuery("create table dept (deptno number(2,0), dname varchar2(14), loc varchar2(13), constraint pk_dept primary key (deptno))");
executor.Watch("select deptno from dept", onChange: (_, eventArgs) =>
{
    Console.WriteLine($"🪄 {eventArgs.Info} detected on {string.Join(',', eventArgs.ResourceNames)}");
    resetEvent.Set();
});
executor.ExecuteNonQuery("insert into dept (deptno, dname, loc) values(10, 'Accounting', 'New York')");

var success = resetEvent.Wait(TimeSpan.FromSeconds(20));
if (!success)
{
    Console.WriteLine("💥 change went undetected");
}

var (stdout, _) = await container.GetLogsAsync();
Console.Write(stdout);

return success ? 0 : 1;

OracleExecutor.cs

using Oracle.ManagedDataAccess.Client;

public sealed class OracleExecutor : IDisposable
{
    private readonly OracleConnection _connection;
    private readonly OracleConnectionStringBuilder _connectionString;

    public OracleExecutor(string connectionString, bool sysDba)
    {
        _connectionString = new OracleConnectionStringBuilder(connectionString);
        if (sysDba)
        {
            _connectionString.UserID = "SYS";
            _connectionString.DBAPrivilege = "SYSDBA";
        }
        _connection = new OracleConnection(_connectionString.ConnectionString);
        _connection.UseClientInitiatedCQN = true;
        _connection.Open();
    }

    public void Dispose()
    {
        _connection.Dispose();
    }

    public string UserId => _connectionString.UserID;

    public void ExecuteNonQuery(string sql)
    {
        using var command = new OracleCommand(sql, _connection);
        Console.Write($"▶️ {sql}");
        command.ExecuteNonQuery();
        Console.WriteLine(" ✅");
    }

    public void Watch(string sql, OnChangeEventHandler onChange)
    {
        var watchCommand = new OracleCommand(sql, _connection);

        var dependency = new OracleDependency(cmd: watchCommand, isNotifiedOnce: true, timeout: 300, isPersistent: false);
        dependency.OnChange += (sender, args) =>
        {
            watchCommand.Dispose();
            onChange(sender, args);
        };

        Console.Write($"👁️ {sql}");
        using var reader = watchCommand.ExecuteReader();
        while (reader.Read())
        {
        }
        Console.WriteLine(" ✅");
    }
}

Running this code with dotnet run works fine. The insert detection is instantaneous and the following logs are produced.

▶️ grant change notification to oracle ✅ ▶️ create table dept (deptno number(2,0), dname varchar2(14), loc varchar2(13), constraint pk_dept primary key (deptno)) ✅ 👁️ select deptno from dept ✅ ▶️ insert into dept (deptno, dname, loc) values(10, 'Accounting', 'New York') ✅ 🪄 Insert detected on ORACLE.DEPT

Now, if you update the Oracle.ManagedDataAccess.Core package to version 23.8.0 the insert detection stops working and the following logs are produced.

▶️ grant change notification to oracle ✅ ▶️ create table dept (deptno number(2,0), dname varchar2(14), loc varchar2(13), constraint pk_dept primary key (deptno)) ✅ 👁️ select deptno from dept ✅ ▶️ insert into dept (deptno, dname, loc) values(10, 'Accounting', 'New York') ✅ 💥 change went undetected

If we look at the Docker container logs we can see some errors that were not happening when using version 23.7.0 of ODP.NET.

Errors in file /opt/oracle/diag/rdbms/free/FREE/trace/FREE_ora_260.trc (incident=729) (PDBNAME=FREEPDB1): ORA-03137: malformed TTC packet from client rejected: [24377] [187] [] [] [] [] [] [] FREEPDB1(3):Incident details in: /opt/oracle/diag/rdbms/free/FREE/incident/incdir_729/FREE_ora_260_i729.trc 2025-05-21T21:46:42.862206+00:00 FREEPDB1(3):Session (200,32786): Bad TTC Packet Detected: Inbound connection from client FREEPDB1(3):Session (200,32786): Bad TTC Packet Detected: DB Logon User: ORACLE, Remote Machine: Asterix\Asterix, Program: watch.exe, OS User: cedric FREEPDB1(3):Session (200,32786): Bad TTC Packet Detected: Client IP Address: 192.168.215.1

Since the only difference is the version of ODP.NET driver, it looks like a regression was introduced in version 23.8.0.

0xced avatar May 21 '25 22:05 0xced

@0xced Can you turn on ODP.NET tracing to level 7 and share your trace? You can upload it here or email it to dotnet_us(at)oracle.com.

After you register the change notification with ODP.NET 23.8 and before you execute the SQL with the DB change, run the query: SELECT REGID, TABLE_NAME FROM USER_CHANGE_NOTIFICATION_REGS;

If the registration is not there, it tells us the problem is with the registration step. Otherwise, the problem is with the ability to receive the DB notification.

alexkeh avatar May 22 '25 14:05 alexkeh

I updated the code to print the registrations. I can confirm that a registration exists (with REGID = 302) in the USER_CHANGE_NOTIFICATION_REGS table. The registration exists with both version 23.7.0 and 23.8.0 of the driver.

Note that you should be able to reproduce this issue on any machine with Docker Desktop running (or an equivalent such as Orbstack). Yay Testcontainers!

Anyway, here's the trace at level 7: WATCH.EXE_PID_11730_DATE_2025_05_22_TIME_23_25_37_580446.trc

0xced avatar May 22 '25 21:05 0xced

@0xced Thanks for the trace file. It confirms the logs in the Docker container. The DB is killing the connection after getting unexpected data from the client. I opened up a bug (37985867) to have the ODP.NET dev team review the trace and diagnose the root cause.

alexkeh avatar May 23 '25 01:05 alexkeh

The dev team notes a very similar bug (37529604) that was fixed a couple of weeks ago. The next ODP.NET quarterly release will have the fix.

alexkeh avatar May 26 '25 16:05 alexkeh

Good to hear this will be fixed in the next release.

Out of curiosity, are the bug numbers you mentioned publicly accessible or are they internal Oracle bug numbers?

0xced avatar May 26 '25 16:05 0xced

Oracle bugs can be made accessible to Oracle My Oracle Support (MOS) users, but not to the public in general. If your organization has a MOS account, I can make the bugs viewable.

alexkeh avatar May 26 '25 16:05 alexkeh

For the record: this issue is fixed with Oracle.ManagedDataAccess.Core 23.9.0 onwards.

0xced avatar Sep 09 '25 15:09 0xced