Hi,
I noticed a strange behavior: we got an ArgumentNullException when trying to connect to an Oracle server:
System.ArgumentNullException: value cannot be null.
Parametername: key
bei System.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument)
bei System.Collections.Generic.Dictionary`2.FindEntry(TKey key)
bei OracleInternal.Common.SyncDictionary`2.ContainsKey(K k)
bei OracleInternal.ConnectionPool.PoolManager`3.CreateServiceCtx(PR pr)
bei OracleInternal.ConnectionPool.PoolManager`3.PutNewPR(PR pr, Boolean bForPoolPopulation)
bei OracleInternal.ConnectionPool.PoolManager`3.CreateNewPR(Int32 reqCount, Boolean bForPoolPopulation, ConnectionString csWithDiffOrNewPwd, CriteriaCtx criteriaCtx, String instanceName, List`1 switchFailedInstNames)
bei OracleInternal.ConnectionPool.PoolManager`3.Get(ConnectionString csWithDiffOrNewPwd, Boolean bGetForApp, CriteriaCtx criteriaCtx, String affinityInstanceName, Boolean bForceMatch)
bei OracleInternal.ConnectionPool.OraclePoolManager.Get(ConnectionString csWithNewPassword, Boolean bGetForApp, CriteriaCtx criteriaCtx, String affinityInstanceName, Boolean bForceMatch)
bei OracleInternal.ConnectionPool.OracleConnectionDispenser`3.Get(ConnectionString cs, PM conPM, ConnectionString pmCS, SecureString securedPassword, SecureString securedProxyPassword, CriteriaCtx criteriaCtx)
bei Oracle.ManagedDataAccess.Client.OracleConnection.Open()
I'm using Managed ODP.Net 4.122.1.20170524. After some digging via dotPeek the problematic code seems to be in OracleInternal.ConnectionPool.PoolManager<>.CreateServiceCtx():
private void CreateServiceCtx(PR pr){ string serviceName = pr.ServiceName; if (this.m_dictSvcCtx.ContainsKey(serviceName)) // <-- this fails with an ArgumentNullException because serviceName is null; pr is the OracleConnectionImpl instance return; lock (this.m_dictSvcCtx) { if (this.m_dictSvcCtx.ContainsKey(serviceName)) return; this.m_dictSvcCtx[serviceName] = new ServiceCtx(serviceName); this.m_dictSvcCtx[serviceName].m_databaseName = pr.m_databaseName; }}
The "PR" here is an instance of OracleInternal.ServiceObjects.OracleConnectionImpl which get's its "ServiceName" apparently from some session properties:
internal override string ServiceName{ get { string str = (string) null; if (this.m_sessionProperties != null) { if (this.m_sessionProperties.ContainsKey((object) "AUTH_SC_SERVICE_NAME")) str = ((string) this.m_sessionProperties[(object) "AUTH_SC_SERVICE_NAME"]).ToLowerInvariant(); } else if (this.m_proxySessionProperties != null && this.m_proxySessionProperties.ContainsKey((object) "AUTH_SC_SERVICE_NAME")) str = ((string) this.m_proxySessionProperties[(object) "AUTH_SC_SERVICE_NAME"]).ToLowerInvariant(); return str; }}
Turned out (after a really long search) that the archivelog ran out of space (alert.log showed "ORA-19815: WARNING: %s of %s bytes is %s%% used, and has %s remaining bytes available. Cause: DB_RECOVERY_FILE_DEST is running out of disk space." earlier and it seems that now the remaining space was now depleted.
Which means that apparently the Oracle server doesn't provide these "session properties" in this case and it seems the code in Oracle Managed ODP.Net is not prepared for that situation.
Despite this, SQL Developer was able to connect to the database (so at first it didn't seem like a server issue). But by default our application uses a connection pool, and it seems the "ServiceName" acts as a key for a connection pool dictionary and since no "ServiceName" was provided we got this exception.
I think this situation should be handled in some better way, not just by throwing an ArgumentNullException (which is actually thrown be the generic dictionary that managed the connection pool). Maybe the client (OracleConnectionImpl or some other class) can check whether it got the expected values like session properties from the Oracle server. And then throw a specific exception if not. Or maybe there's a better way to recognize that the Oracle server has an issue? I don't know...
Not sure if this forum is a good way to report this issue. Please let me now if I should file this somewhere else.
Thanks,
Markus