First time working with Npgsql, although I've done other PostgreSQL programming. Npgsql 3.1.6, PostgreSQL 9.5. Visual Studio 2015 with .Net 4.5.1.
I have a connection string that looks like this (same string is used every time, unmodified, for every DB connection attempt):
Server=dbserver;SearchPath=network;Database=netinfo;User ID=netuser;Password=netpassword;Port=19491;CommandTimeout=300;Pooling=true;MaxPoolSize=75
This is a C# multi-threaded data collector for our network. Up to 32 threads running on the collector. Each thread opens a connection when needed but only uses one connection at a time. A connection will be closed and re-opened by the thread if necessary. I had written my own connection pool of sorts and it was working; open a bunch of connections at the start, pull an open connection out of a ConcurrentQueue
, use it, then return it to the queue. Queue held 16 open connections. Fraught with danger if not done right but it worked. During a code review colleague turned me on to Npgsql connection pooling so I'm trying to use it.
I'm getting System.InvalidOperationException
, "The connection is not open"
when I reach an UPDATE command (dbCommand.ExecuteNonQuery());
I don't believe the command itself is significant -- this is just the one it's hitting. I notice on the DB server netstat shows MinPoolSize+1 open sockets. In the connect string above, it's not defined, so MinPoolSize is 1. I'll see two sockets in netstat. If I set MinPoolSize to 16, I'll see 17 sockets. It looks like Npgsql opens the minimum pool size, then throws the InvalidOperationException with the next connection. But it ought to have up to 75 connections available and block when not available. It's not.
Maximum connections on my PostgreSQL server is 100; I'm nowhere near that threshold.
It works when I use my self-written pool sharing method so there's no problem with the code that executes the queries. It's just how I get a current connection.
Thoughts? Insight? Thank you!
Edit: As per request . . . it's a little long, but it shows a couple of adjacent conditionals for an idea of the flow.
lock (ArpTableLocks.GetLock(nd.IdNd + " " + idMi + " " + idIf))
{
if (_arpTableList.Get(nd.IdNd, idMi, idIf) == null)
{
// No device/MAC/IP/intf entry in DB, add one
var dbConn = new NpgsqlConnection(DBStrings.connectionString);
var dbCommand = new NpgsqlCommand(DBStrings.sqlARPTableInsert, dbConn);
dbCommand.Parameters.Add(new NpgsqlParameter("@id_mi", NpgsqlTypes.NpgsqlDbType.Integer, 4));
dbCommand.Parameters.Add(new NpgsqlParameter("@id_nd", NpgsqlTypes.NpgsqlDbType.Integer, 4));
dbCommand.Parameters.Add(new NpgsqlParameter("@id_if", NpgsqlTypes.NpgsqlDbType.Integer, 4));
dbCommand.Parameters["@id_mi"].Value = idMi.ToString();
dbCommand.Parameters["@id_nd"].Value = nd.IdNd.ToString();
dbCommand.Parameters["@id_if"].Value = idIf.ToString();
var dbRead = dbCommand.ExecuteReader();
dbRead.Read();
idAt = dbRead.GetInt32(0);
var arpTableListEntry = new ArpTableEntry
{
Id = idAt,
IdNd = nd.IdNd,
IdMi = idMi,
IdIf = idIf
};
_arpTableList.Add(arpTableListEntry);
dbRead.Close();
dbConn.Close();
}
}
idAt = (_arpTableList.Get(nd.IdNd, idMi, idIf).Id);
lock (ActiveArpTableLocks.GetLock(nd.IdV + " " + idAt))
{
if (_activeArpTableList.Get(nd.IdV, idAt) == null)
{
var dbConn = new NpgsqlConnection(DBStrings.connectionString);
NpgsqlCommand dbCommand = new NpgsqlCommand(DBStrings.sqlActiveARPTableInsert, dbConn);
dbCommand.Parameters.Add(new NpgsqlParameter("@id_v", NpgsqlTypes.NpgsqlDbType.Integer, 4));
dbCommand.Parameters.Add(new NpgsqlParameter("@id_at", NpgsqlTypes.NpgsqlDbType.Integer, 4));
dbCommand.Parameters.Add(new NpgsqlParameter("@seen", NpgsqlTypes.NpgsqlDbType.Timestamp));
dbCommand.Parameters["@id_v"].Value = nd.IdV.ToString();
dbCommand.Parameters["@id_at"].Value = idAt.ToString();
dbCommand.Parameters["@seen"].Value = ae.TimeSeen;
dbCommand.ExecuteNonQuery();
// Insert a history record
dbCommand = new NpgsqlCommand(DBStrings.sqlHistoryARPTableInsert, dbConn);
dbCommand.Parameters.Add(new NpgsqlParameter("@id_v", NpgsqlTypes.NpgsqlDbType.Integer, 4));
dbCommand.Parameters.Add(new NpgsqlParameter("@id_at", NpgsqlTypes.NpgsqlDbType.Integer, 4));
dbCommand.Parameters.Add(new NpgsqlParameter("@firstseen", NpgsqlTypes.NpgsqlDbType.Timestamp));
dbCommand.Parameters["@id_v"].Value = nd.IdV.ToString();
dbCommand.Parameters["@id_at"].Value = idAt.ToString();
dbCommand.Parameters["@firstseen"].Value = ae.TimeSeen;
dbCommand.ExecuteNonQuery(); // Exception thrown here
dbConn.Close();
Interlocked.Increment(ref _arpEntryAdded);
}
else
{
// DB has an active ARP table entry, so update its seen time.
var dbConn = new NpgsqlConnection(DBStrings.connectionString);
NpgsqlCommand dbCommand = new NpgsqlCommand(DBStrings.sqlActiveARPTableUpdateSeen, dbConn);
dbCommand.Parameters.Add(new NpgsqlParameter("@seen", NpgsqlTypes.NpgsqlDbType.Timestamp));
dbCommand.Parameters.Add(new NpgsqlParameter("@id_v", NpgsqlTypes.NpgsqlDbType.Integer, 4));
dbCommand.Parameters.Add(new NpgsqlParameter("@id_at", NpgsqlTypes.NpgsqlDbType.Integer, 4));
dbCommand.Parameters["@seen"].Value = ae.TimeSeen;
dbCommand.Parameters["@id_v"].Value = nd.IdV.ToString();
dbCommand.Parameters["@id_at"].Value = idAt.ToString();
dbCommand.ExecuteNonQuery();
dbConn.Close();
// Remove from activearptable hashtable
_activeArpTableList.Delete(nd.IdV, idAt);
Interlocked.Increment(ref _arpEntryUpdated);
}
}