Skip to content

new driver version causes database to suffer memory leak and die... #2171

@skearns23

Description

@skearns23

Driver version

mssql-jdbc-12.2.0.jre11.jar

SQL Server version

Microsoft SQL Server 2019 (RTM-GDR) (KB5014356) - 15.0.2095.3 (X64) Apr 29 2022 18:00:13 Copyright (C) 2019 Microsoft Corporation Enterprise Edition (64-bit) on Windows Server 2022 Datacenter 10.0 (Build 20348: ) (Hypervisor)

Client Operating System

Edition Windows Server 2022 Datacenter
Version 21H2
Installed on ‎11/‎5/‎2021
OS build 20348.1129

JAVA/JVM version

Oracle Java SE SDK 17.0.7

Problem description

When using a CallableStatement on the client to call a stored procedure
that has a decimal(25,12) parameter, this driver triggers a memory leak
in the SERVER which causes the process managing the connection to the client
to use an increasing amount of memory with each call. Eventually
the database runs out of memory and dies a slow painful death.

This problem was not evident in previous versions of the jdbc driver.

Duplicating the problem

On the server, add the following simple stored procedure:

vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

CREATE PROCEDURE [dbo].[proc_DoNothing]
@lastname nvarchar(256),
@MiDName nvarchar(16),
@firstname nvarchar(256),
@Level int,
@special decimal(25, 12)
AS
BEGIN
SET NOCOUNT ON;
END

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

on the client, run this program:

vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv

package company.util.test2;

import java.math.BigDecimal;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;

import org.apache.commons.dbcp2.DriverManagerConnectionFactory;

public class FakeServer2
{

public static FakeServer2				inst;

private static final String				proc_DoNothing	=
	"{ call proc_DoNothing(?, ?, ?, ?, ?) }";


public DriverManagerConnectionFactory	connectionFactory;

public static void main(String args[])
{
	inst = new FakeServer2();

	inst.init();
	inst.testDoNothingStoredProc();
}


public void init()
{
	try
	{

		try
		{
			Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}

		String server = "10.10.10.10";

		String database = (((("mydb"))));
		String user = ("username");
		String password = "pwd";

		String url2 = "jdbc:sqlserver://" + server + ":1433;databaseName=" + database + ";user="
			+ user + ";password=" + password + ";applicationName=" + "FakeServer2"
			+ ";trustServerCertificate=true";

		connectionFactory = new DriverManagerConnectionFactory(url2, user, password);

	}
	catch (Exception e)
	{
		e.printStackTrace();
	}
	finally
	{
	}
}

public Connection getConnection()
{
	Connection c = null;
	try
	{
		c = connectionFactory.createConnection();
	}
	catch (SQLException e)
	{
		e.printStackTrace();
	}
	return c;

}

public void closeConnection(Connection conn)
{
	try
	{
		conn.close();
	}
	catch (Exception ee)
	{
		ee.printStackTrace();
	}

}

public void testDoNothingStoredProc()
{
	Connection dbconn = null;
	CallableStatement stmt = null;

	dbconn = getConnection();
	for (int j = 0; j < 10000000; j++)
	{
		try
		{
			System.out.println("iteration number " + j);
			stmt = dbconn.prepareCall(proc_DoNothing);
			int count = 20;
			for (int i = 0; i < count; ++i)
			{
				try
				{
					doNothingDB(stmt, 0, 0);
					doNothingDB(stmt, 1, 0);
					doNothingDB(stmt, 0, 1);
					doNothingDB(stmt, 1, 1);
				}
				finally
				{
				}
			}
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		finally
		{
			try
			{
				stmt.close();
				//dbconn.close();
			}
			catch (SQLException e)
			{
				e.printStackTrace();
			}
			stmt = null;
		} // end finally
	}

	try
	{
		dbconn.close();
	}
	catch (SQLException e)
	{
		e.printStackTrace();
	}
}

private void doNothingDB(CallableStatement stmt, int side, int book) throws SQLException
{
	try
	{
		for (int lev = 0; lev < 10; ++lev)
		{
			int ind = 1;
			stmt.setNString(ind++, "OK");
			stmt.setNString(ind++, "PHIL");
			stmt.setNString(ind++, "MORE");
			stmt.setLong(ind++, 1232);
			BigDecimal bd = new BigDecimal((long) (Math.random() * 100));
			stmt.setBigDecimal(ind++, bd);
			stmt.execute();
		}
	}
	catch (Exception e)
	{
		e.printStackTrace();
	}
	finally
	{
	}
}

}

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Using Sql Management Studio, select the database server and display the activity monitor.

Under processes, find the entry for FakeServer2.

Look at the Memory column, and you will see the value increase without bound.

Workaround

Closing the database connection will alleviate this problem on the server.
But when using a pooled datasource this is not feasible.

You can workaround this issue by passing the big decimal as a String:

BigDecimal bd = new BigDecimal((long) (Math.random() * 100));
if (bd.scale() > 12)
bd.setScale(12);
stmt.setNString(ind++, bd.toPlainString());

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugA bug in the driver. A high priority item that one can expect to be addressed quickly.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions