Six months in a leaky boat

Posted by on in Blogs
Yesterday I discovered a potential resource leak when using the dbExpress Ado.NET provider, which can cause connections to be created and never freed. This leak was discovered when I implemented and started consuming a helper method which allows me to easily determine whether a data reader contains a specified column.
class method DbUtils.ColumnExists(AReader: DbDataReader; AColumnName: String): Boolean;
begin
  var Table: DataTable := AReader.GetSchemaTable;
  Table.DefaultView.RowFilter := String.Format('ColumnName =''{0}''', AColumnName);
  Result := (table.DefaultView.Count > 0)
end;

The above code seemed pretty innocuous, but a bit of spelunking using Reflector discovered that the call to TAdoDbxDataReader.GetSchemaTable resulted in the associated TAdoDbxConnection cloning itself into a private variable, and this connection was never closed. So if this method is called multiple times using different connections, each call would result in an additional database connection being made which wasn’t being closed until the application exited. For the app that this bug was discovered in, this meant that an ever increasing number of connections were being established to an InterBase database, eventually resulting in stored procedure calls that never finished executing.

If you think you may be affected by this bug, please take a look at http://qc.embarcadero.com/wc/qcmain.aspx?d=82886, and vote/rate as you see fit. In addition to providing more details as to the cause of the leak, it also suggests the following possible workaround.
class method CloseConnection(AConnection: TAdoDbxConnection);
begin
  var Field: FieldInfo := AConnection.GetType.GetField('FClonedConnection',
(BindingFlags.GetField or (BindingFlags.NonPublic or BindingFlags.Instance)));
  if (Field <> nil) then
  begin
    var Con: TAdoDbxConnection := (Field.GetValue(AConnection) as TAdoDbxConnection);
    if (Con <> nil) then
    begin
      Con.Close;
      Field.SetValue(AConnection, nil)
    end;
  end;
  AConnection.Close;
end;

Now, obviously this workaround is pretty brittle, and could easily break if the DBX team change their internal implementation of TAdoDbxConnection (especially if said change involved renaming FClonedConnection), but I have verified that it works against the release of DBX that currently ships with Delphi Prism. The DBX team have been made aware of this bug, so hopefully it will be resolved in a not too distant release, and give the above kludge a short life expectancy.


About
Gold User, Rank: 74, Points: 20
Software developer, family man, All Blacks fan, Playstation junkie, and headbanger since ages ago.

Comments

Check out more tips and tricks in this development video: