X Tutup
// Copyright (C) 2002 The Npgsql Development Team // npgsql-general@gborg.postgresql.org // http://gborg.postgresql.org/project/npgsql/projdisplay.php // // Permission to use, copy, modify, and distribute this software and its // documentation for any purpose, without fee, and without a written // agreement is hereby granted, provided that the above copyright notice // and this paragraph and the following two paragraphs appear in all copies. // // IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY // FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, // INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS // DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF // THE POSSIBILITY OF SUCH DAMAGE. // // THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES, // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY // AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS // ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS // TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. // // Connector.cs // ------------------------------------------------------------------ // Project // Npgsql // Status // 0.00.0000 - 06/17/2002 - ulrich sprick - created // - 06/??/2004 - Glen Parker rewritten using System; using System.Collections.Generic; using System.Data; using System.IO; using System.Net.Security; using System.Net.Sockets; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Threading; using Mono.Security.Protocol.Tls; using NpgsqlTypes; using System.Text; namespace Npgsql { /// /// Represents the method that allows the application to provide a certificate collection to be used for SSL clien authentication /// /// A X509CertificateCollection to be filled with one or more client certificates. public delegate void ProvideClientCertificatesCallback(X509CertificateCollection certificates); /// /// Represents the method that is called to validate the certificate provided by the server during an SSL handshake /// /// The server's certificate /// The certificate chain containing the certificate's CA and any intermediate authorities /// Any errors that were detected public delegate bool ValidateRemoteCertificateCallback(X509Certificate cert, X509Chain chain, SslPolicyErrors errors); /// /// !!! Helper class, for compilation only. /// Connector implements the logic for the Connection Objects to /// access the physical connection to the database, and isolate /// the application developer from connection pooling internals. /// internal class NpgsqlConnector { // Immutable. private readonly NpgsqlConnectionStringBuilder settings; /// /// Occurs on NoticeResponses from the PostgreSQL backend. /// internal event NoticeEventHandler Notice; /// /// Occurs on NotificationResponses from the PostgreSQL backend. /// internal event NotificationEventHandler Notification; /// /// Called to provide client certificates for SSL handshake. /// internal event ProvideClientCertificatesCallback ProvideClientCertificatesCallback; /// /// Mono.Security.Protocol.Tls.CertificateSelectionCallback delegate. /// internal event CertificateSelectionCallback CertificateSelectionCallback; /// /// Mono.Security.Protocol.Tls.CertificateValidationCallback delegate. /// internal event CertificateValidationCallback CertificateValidationCallback; /// /// Mono.Security.Protocol.Tls.PrivateKeySelectionCallback delegate. /// internal event PrivateKeySelectionCallback PrivateKeySelectionCallback; /// /// Called to validate server's certificate during SSL handshake /// internal event ValidateRemoteCertificateCallback ValidateRemoteCertificateCallback; private ConnectionState _connection_state; // The physical network connection socket and stream to the backend. private Socket _socket; private NpgsqlNetworkStream _baseStream; // The top level stream to the backend. // This is a BufferedStream. // With SSL, this stream sits on top of the SSL stream, which sits on top of _baseStream. // Otherwise, this stream sits directly on top of _baseStream. private BufferedStream _stream; // Mediator which will hold data generated from backend. private readonly NpgsqlMediator _mediator; private ProtocolVersion _backendProtocolVersion; private Version _serverVersion; // Values for possible CancelRequest messages. private NpgsqlBackEndKeyData _backend_keydata; // Flag for transaction status. // private Boolean _inTransaction = false; private NpgsqlTransaction _transaction = null; private Boolean _supportsPrepare = false; private Boolean _supportsSavepoint = false; private Boolean _supportsDiscard = false; private Boolean _supportsApplicationName = false; private Boolean _supportsExtraFloatDigits3 = false; private Boolean _supportsExtraFloatDigits = false; private Boolean _supportsSslRenegotiationLimit = false; private Boolean _isInitialized; private readonly Boolean _pooled; private readonly Boolean _shared; private NpgsqlState _state; private Int32 _planIndex; private Int32 _portalIndex; private const String _planNamePrefix = "s"; private const String _portalNamePrefix = "p"; private NativeToBackendTypeConverterOptions _NativeToBackendTypeConverterOptions; private Thread _notificationThread; // Counter of notification thread start/stop requests in order to internal Int16 _notificationThreadStopCount; private Exception _notificationException; internal ForwardsOnlyDataReader CurrentReader; // Some kinds of messages only get one response, and do not // expect a ready_for_query response. private bool _requireReadyForQuery = true; private readonly Dictionary _serverParameters = new Dictionary(StringComparer.InvariantCultureIgnoreCase); // For IsValid test private readonly RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); private string initQueries; #if WINDOWS && UNMANAGED private SSPIHandler _sspi; internal SSPIHandler SSPI { get { return _sspi; } set { _sspi = value; } } #endif public NpgsqlConnector(NpgsqlConnection Connection) : this(Connection.CopyConnectionStringBuilder(), Connection.Pooling, false) {} /// /// Constructor. /// /// Connection string. /// Pooled /// Controls whether the connector can be shared. public NpgsqlConnector(NpgsqlConnectionStringBuilder ConnectionString, bool Pooled, bool Shared) { this.settings = ConnectionString; _connection_state = ConnectionState.Closed; _pooled = Pooled; _shared = Shared; _isInitialized = false; _state = NpgsqlClosedState.Instance; _mediator = new NpgsqlMediator(); _NativeToBackendTypeConverterOptions = NativeToBackendTypeConverterOptions.Default.Clone(new NpgsqlBackendTypeMapping()); _planIndex = 0; _portalIndex = 0; _notificationThreadStopCount = 1; } //Finalizer should never be used, but if some incident has left to a connector being abandoned (most likely //case being a user not cleaning up a connection properly) then this way we can at least reduce the damage. //~NpgsqlConnector() //{ // Close(); //} internal String Host { get { return settings.Host; } } internal Int32 Port { get { return settings.Port; } } internal String Database { get { return settings.ContainsKey(Keywords.Database) ? settings.Database : settings.UserName; } } internal String UserName { get { return settings.UserName; } } internal byte[] Password { get { return settings.PasswordAsByteArray; } } internal Boolean SSL { get { return settings.SSL; } } internal SslMode SslMode { get { return settings.SslMode; } } internal Boolean UseMonoSsl { get { return ValidateRemoteCertificateCallback == null; } } internal Int32 ConnectionTimeout { get { return settings.Timeout; } } internal Int32 DefaultCommandTimeout { get { return settings.CommandTimeout; } } internal Boolean Enlist { get { return settings.Enlist; } } public bool UseExtendedTypes { get { return settings.UseExtendedTypes; } } internal Boolean IntegratedSecurity { get { return settings.IntegratedSecurity; } } internal Boolean AlwaysPrepare { get { return SupportsPrepare && settings.AlwaysPrepare; } } /// /// Gets the current state of the connection. /// internal ConnectionState State { get { if (_connection_state != ConnectionState.Closed && CurrentReader != null && !CurrentReader._cleanedUp) { return ConnectionState.Open | ConnectionState.Fetching; } return _connection_state; } } /// /// Return Connection String. /// internal string ConnectionString { get { return settings.ConnectionString; } } internal void Query(NpgsqlQuery query) { CurrentState.Query(this, query); } internal void Authenticate(byte[] password) { CurrentState.Authenticate(this, password); } internal void Parse(NpgsqlParse parse) { CurrentState.Parse(this, parse); } internal void TestConnector() { CurrentState.TestConnector(this); } internal void Sync() { CurrentState.Sync(this); } internal void Bind(NpgsqlBind bind) { CurrentState.Bind(this, bind); } internal void Describe(NpgsqlDescribe describe) { CurrentState.Describe(this, describe); } internal void Execute(NpgsqlExecute execute) { CurrentState.Execute(this, execute); } internal void ProcessAndDiscardBackendResponses() { CurrentState.ProcessAndDiscardBackendResponses(this); } internal IEnumerable ProcessBackendResponsesEnum() { return CurrentState.ProcessBackendResponsesEnum(this); } /// /// This method checks if the connector is still ok. /// We try to send a simple query text, select 1 as ConnectionTest; /// internal Boolean IsValid() { try { // Here we use a fake NpgsqlCommand, just to send the test query string. // Get random test value. Byte[] testBytes = new Byte[2]; rng.GetNonZeroBytes(testBytes); String testValue = String.Format("Npgsql{0}{1}", testBytes[0], testBytes[1]); //Query(new NpgsqlCommand("select 1 as ConnectionTest", this)); string compareValue = string.Empty; string sql = "select '" + testValue + "'"; // restore initial connection parameters resetted by "Discard ALL" if (SupportsDiscard) { sql = this.initQueries + sql; } using(NpgsqlCommand cmd = new NpgsqlCommand(sql, this)) { compareValue = (string) cmd.ExecuteScalar(); } if (compareValue != testValue) { return false; } this.RequireReadyForQuery = true; } catch { return false; } return true; } /// /// This method is responsible for releasing all resources associated with this Connector. /// internal void ReleaseResources() { if (_connection_state != ConnectionState.Closed) { if (SupportsDiscard) { ReleaseWithDiscard(); } else { ReleasePlansPortals(); ReleaseRegisteredListen(); } } } internal void ReleaseWithDiscard() { NpgsqlCommand.ExecuteBlind(this, "DISCARD ALL", 60); // The initial connection parameters will be restored via IsValid() when get connector from pool later } internal void ReleaseRegisteredListen() { NpgsqlCommand.ExecuteBlind(this, "UNLISTEN *"); } /// /// This method is responsible to release all portals used by this Connector. /// internal void ReleasePlansPortals() { Int32 i = 0; if (_planIndex > 0) { for (i = 1; i <= _planIndex; i++) { try { NpgsqlCommand.ExecuteBlind(this, String.Format("DEALLOCATE \"{0}{1}\";", _planNamePrefix, i), -1); } // Ignore any error which may occur when releasing portals as this portal name may not be valid anymore. i.e.: the portal name was used on a prepared query which had errors. catch {} } } _portalIndex = 0; _planIndex = 0; } /// /// Modify the backend statement_timeout value if needed. /// /// New timeout internal void SetBackendCommandTimeout(int timeout) { if (Mediator.BackendCommandTimeout == -1 || Mediator.BackendCommandTimeout != timeout) { NpgsqlCommand.ExecuteSetStatementTimeoutBlind(this, timeout); Mediator.BackendCommandTimeout = timeout; } } internal void FireNotice(NpgsqlError e) { if (Notice != null) { try { Notice(this, new NpgsqlNoticeEventArgs(e)); } catch { } //Eat exceptions from user code. } } internal void FireNotification(NpgsqlNotificationEventArgs e) { if (Notification != null) { try { Notification(this, e); } catch { } //Eat exceptions from user code. } } /// /// Default SSL CertificateSelectionCallback implementation. /// internal X509Certificate DefaultCertificateSelectionCallback(X509CertificateCollection clientCertificates, X509Certificate serverCertificate, string targetHost, X509CertificateCollection serverRequestedCertificates) { if (CertificateSelectionCallback != null) { return CertificateSelectionCallback(clientCertificates, serverCertificate, targetHost, serverRequestedCertificates); } else { return null; } } /// /// Default SSL CertificateValidationCallback implementation. /// internal bool DefaultCertificateValidationCallback(X509Certificate certificate, int[] certificateErrors) { if (CertificateValidationCallback != null) { return CertificateValidationCallback(certificate, certificateErrors); } else { return true; } } /// /// Default SSL PrivateKeySelectionCallback implementation. /// internal AsymmetricAlgorithm DefaultPrivateKeySelectionCallback(X509Certificate certificate, string targetHost) { if (PrivateKeySelectionCallback != null) { return PrivateKeySelectionCallback(certificate, targetHost); } else { return null; } } /// /// Default SSL ProvideClientCertificatesCallback implementation. /// internal void DefaultProvideClientCertificatesCallback(X509CertificateCollection certificates) { if (ProvideClientCertificatesCallback != null) { ProvideClientCertificatesCallback(certificates); } } /// /// Default SSL ValidateRemoteCertificateCallback implementation. /// internal bool DefaultValidateRemoteCertificateCallback(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors errors) { if (ValidateRemoteCertificateCallback != null) { return ValidateRemoteCertificateCallback(cert, chain, errors); } else { return false; } } /// /// Version of backend server this connector is connected to. /// internal Version ServerVersion { get { return _serverVersion; } set { _serverVersion = value; } } /// /// Backend protocol version in use by this connector. /// internal ProtocolVersion BackendProtocolVersion { get { return _backendProtocolVersion; } set { _backendProtocolVersion = value; } } /// /// The physical connection socket to the backend. /// internal Socket Socket { get { return _socket; } set { _socket = value; } } /// /// The physical connection stream to the backend. /// internal NpgsqlNetworkStream BaseStream { get { return _baseStream; } set { _baseStream = value; } } /// /// The top level stream to the backend. /// internal BufferedStream Stream { get { return _stream; } set { _stream = value; } } /// /// Reports if this connector is fully connected. /// internal Boolean IsInitialized { get { return _isInitialized; } set { _isInitialized = value; } } internal NpgsqlState CurrentState { get { return _state; } set { _state = value; } } internal bool Pooled { get { return _pooled; } } internal bool Shared { get { return _shared; } } internal NpgsqlBackEndKeyData BackEndKeyData { get { return _backend_keydata; } set { _backend_keydata = value; } } internal NpgsqlBackendTypeMapping OidToNameMapping { get { return _NativeToBackendTypeConverterOptions.OidToNameMapping; } } internal Version CompatVersion { get { return settings.Compatible; } } /// /// The connection mediator. /// internal NpgsqlMediator Mediator { get { return _mediator; } } /// /// Report if the connection is in a transaction. /// internal NpgsqlTransaction Transaction { get { return _transaction; } set { _transaction = value; } } internal Boolean SupportsApplicationName { get { return _supportsApplicationName; } } internal Boolean SupportsExtraFloatDigits3 { get { return _supportsExtraFloatDigits3; } } internal Boolean SupportsExtraFloatDigits { get { return _supportsExtraFloatDigits; } } internal Boolean SupportsSslRenegotiationLimit { get { return _supportsSslRenegotiationLimit; } } /// /// Report whether the current connection can support prepare functionality. /// internal Boolean SupportsPrepare { get { return _supportsPrepare; } set { _supportsPrepare = value; } } internal Boolean SupportsSavepoint { get { return _supportsSavepoint; } set { _supportsSavepoint = value; } } internal Boolean SupportsDiscard { get { return _supportsDiscard; } } /// /// Options that control certain aspects of native to backend conversions that depend /// on backend version and status. /// public NativeToBackendTypeConverterOptions NativeToBackendTypeConverterOptions { get { return _NativeToBackendTypeConverterOptions; } } /// /// This method is required to set all the version dependent features flags. /// SupportsPrepare means the server can use prepared query plans (7.3+) /// private void ProcessServerVersion() { this._supportsPrepare = (ServerVersion >= new Version(7, 3, 0)); this._supportsSavepoint = (ServerVersion >= new Version(8, 0, 0)); this._supportsDiscard = (ServerVersion >= new Version(8, 3, 0)); this._supportsApplicationName = (ServerVersion >= new Version(9, 0, 0)); this._supportsExtraFloatDigits3 =(ServerVersion >= new Version(9, 0, 0)); this._supportsExtraFloatDigits = (ServerVersion >= new Version(7, 4, 0)); this._supportsSslRenegotiationLimit = ((ServerVersion >= new Version(8, 4, 3)) || (ServerVersion >= new Version(8, 3, 10) && ServerVersion < new Version(8, 4, 0)) || (ServerVersion >= new Version(8, 2, 16) && ServerVersion < new Version(8, 3, 0)) || (ServerVersion >= new Version(8, 1, 20) && ServerVersion < new Version(8, 2, 0)) || (ServerVersion >= new Version(8, 0, 24) && ServerVersion < new Version(8, 1, 0)) || (ServerVersion >= new Version(7, 4, 28) && ServerVersion < new Version(8, 0, 0)) ); // Per the PG documentation, E string literal prefix support appeared in PG version 8.1. // Note that it is possible that support for this prefix will vanish in some future version // of Postgres, in which case this test will need to be revised. // At that time it may also be necessary to set UseConformantStrings = true here. NativeToBackendTypeConverterOptions.Supports_E_StringPrefix = (ServerVersion >= new Version(8, 1, 0)); // Per the PG documentation, hex string encoding format support appeared in PG version 9.0. NativeToBackendTypeConverterOptions.SupportsHexByteFormat = (ServerVersion >= new Version(9, 0, 0)); } /*/// Counts the numbers of Connections that share /// this Connector. Used in Release() to decide wether this /// connector is to be moved to the PooledConnectors list. // internal int mShareCount;*/ /// /// Opens the physical connection to the server. /// /// Usually called by the RequestConnector /// Method of the connection pool manager. internal void Open() { ServerVersion = null; // If Connection.ConnectionString specifies a protocol version, we will // not try to fall back to version 2 on failure. _backendProtocolVersion = (settings.Protocol == ProtocolVersion.Unknown) ? ProtocolVersion.Version3 : settings.Protocol; // Reset state to initialize new connector in pool. CurrentState = NpgsqlClosedState.Instance; // Keep track of time remaining; Even though there may be multiple timeout-able calls, // this allows us to still respect the caller's timeout expectation. int connectTimeRemaining = this.ConnectionTimeout * 1000; DateTime attemptStart = DateTime.Now; // Get a raw connection, possibly SSL... CurrentState.Open(this, connectTimeRemaining); try { // Establish protocol communication and handle authentication... CurrentState.Startup(this,settings); } catch (NpgsqlException ne) { if (_stream != null) { try { _stream.Dispose(); } catch { } } connectTimeRemaining -= Convert.ToInt32((DateTime.Now - attemptStart).TotalMilliseconds); // Check for protocol not supported. If we have been told what protocol to use, // we will not try this step. if (settings.Protocol != ProtocolVersion.Unknown) { throw; } // If we attempted protocol version 3, it may be possible to drop back to version 2. if (BackendProtocolVersion != ProtocolVersion.Version3) { throw; } NpgsqlError Error0 = (NpgsqlError) ne.Errors[0]; // If NpgsqlError..ctor() encounters a version 2 error, // it will set its own protocol version to version 2. That way, we can tell // easily if the error was a FATAL: protocol error. if (Error0.BackendProtocolVersion != ProtocolVersion.Version2) { throw; } // Try using the 2.0 protocol. BackendProtocolVersion = ProtocolVersion.Version2; CurrentState = NpgsqlClosedState.Instance; // Get a raw connection, possibly SSL... CurrentState.Open(this, connectTimeRemaining); // Establish protocol communication and handle authentication... CurrentState.Startup(this,this.settings); } // Change the state of connection to open and ready. _connection_state = ConnectionState.Open; CurrentState = NpgsqlReadyState.Instance; // After attachment, the stream will close the connector (this) when the stream gets disposed. _baseStream.AttachConnector(this); // Fall back to the old way, SELECT VERSION(). // This should not happen for protocol version 3+. if (ServerVersion == null) { //NpgsqlCommand command = new NpgsqlCommand("set DATESTYLE TO ISO;select version();", this); //ServerVersion = new Version(PGUtil.ExtractServerVersion((string) command.ExecuteScalar())); using (NpgsqlCommand command = new NpgsqlCommand("set DATESTYLE TO ISO;select version();", this)) { ServerVersion = new Version(PGUtil.ExtractServerVersion((string)command.ExecuteScalar())); } } ProcessServerVersion(); StringWriter sbInitQueries = new StringWriter(); if (BackendProtocolVersion == ProtocolVersion.Version2) { sbInitQueries.WriteLine("SET DATESTYLE TO ISO;"); // Adjust client encoding. NpgsqlParameterStatus clientEncodingParam = null; if ( !ServerParameters.TryGetValue("client_encoding", out clientEncodingParam) || (!string.Equals(clientEncodingParam.ParameterValue, "UTF8", StringComparison.OrdinalIgnoreCase) && !string.Equals(clientEncodingParam.ParameterValue, "UNICODE", StringComparison.OrdinalIgnoreCase)) ) { sbInitQueries.WriteLine("SET CLIENT_ENCODING TO UTF8;"); } if (!string.IsNullOrEmpty(settings.SearchPath)) { // TODO: Add proper message when finding a semicolon in search_path. // This semicolon could lead to a sql injection security hole as someone could write in connection string: // searchpath=public;delete from table; and it would be executed. if (settings.SearchPath.Contains(";")) { throw new InvalidOperationException(); } // This is using string concatenation because set search_path doesn't allow type casting. ::text sbInitQueries.WriteLine("SET SEARCH_PATH = {0};", settings.SearchPath); } if (!string.IsNullOrEmpty(settings.ApplicationName)) { if (!SupportsApplicationName) { //TODO //throw new InvalidOperationException(resman.GetString("Exception_ApplicationNameNotSupported")); throw new InvalidOperationException("ApplicationName not supported."); } if (settings.ApplicationName.Contains(";")) { throw new InvalidOperationException(); } sbInitQueries.WriteLine("SET APPLICATION_NAME='{0}';", settings.ApplicationName); } /* * Try to set SSL negotiation to 0. As of 2010-03-29, recent problems in SSL library implementations made * postgresql to add a parameter to set a value when to do this renegotiation or 0 to disable it. * Currently, Npgsql has a problem with renegotiation so, we are trying to disable it here. * This only works on postgresql servers where the ssl renegotiation settings is supported of course. * See http://lists.pgfoundry.org/pipermail/npgsql-devel/2010-February/001065.html for more information. */ if (SupportsSslRenegotiationLimit) { sbInitQueries.WriteLine("SET ssl_renegotiation_limit=0;"); } /* * Set precision digits to maximum value possible. For postgresql before 9 it was 2, after that, it is 3. * Check bug report #1010992 for more information. */ if (SupportsExtraFloatDigits3) { sbInitQueries.WriteLine("SET extra_float_digits=3;"); } else if (SupportsExtraFloatDigits) { sbInitQueries.WriteLine("SET extra_float_digits=2;"); } /* * Set lc_monetary format to 'C' in order to get a culture agnostic representation of money. * I noticed that on Windows, even when the lc_monetary is English_United States.UTF-8, negative * money is formatted as ($value) with parentheses to indicate negative value. * By going with a culture agnostic format, we get a consistent behavior. */ sbInitQueries.WriteLine("SET lc_monetary='C';"); } else { // Some connection parameters for protocol 3 had been sent in the startup packet. // The rest will be setted here. if (SupportsExtraFloatDigits3) { sbInitQueries.WriteLine("SET extra_float_digits=3;"); } if (SupportsSslRenegotiationLimit) { sbInitQueries.WriteLine("SET ssl_renegotiation_limit=0;"); } } initQueries = sbInitQueries.ToString(); NpgsqlCommand.ExecuteBlind(this, initQueries, 60); // Make a shallow copy of the type mapping that the connector will own. // It is possible that the connector may add types to its private // mapping that will not be valid to another connector, even // if connected to the same backend version. NativeToBackendTypeConverterOptions.OidToNameMapping = NpgsqlTypesHelper.CreateAndLoadInitialTypesMapping(this).Clone(); // The connector is now fully initialized. Beyond this point, it is // safe to release it back to the pool rather than closing it. IsInitialized = true; } /// /// Closes the physical connection to the server. /// internal void Close() { try { if (_connection_state != ConnectionState.Closed) { _connection_state = ConnectionState.Closed; this.CurrentState.Close(this); _serverParameters.Clear(); ServerVersion = null; } } catch { } } internal void CancelRequest() { NpgsqlConnector cancelConnector = new NpgsqlConnector(settings, false, false); cancelConnector._backend_keydata = BackEndKeyData; try { // Get a raw connection, possibly SSL... cancelConnector.CurrentState.Open(cancelConnector, cancelConnector.ConnectionTimeout * 1000); // Cancel current request. cancelConnector.CurrentState.CancelRequest(cancelConnector); } finally { cancelConnector.CurrentState.Close(cancelConnector); } } /// /// Returns next portal index. /// internal String NextPortalName() { return _portalNamePrefix + (++_portalIndex).ToString(); } /// /// Returns next plan index. /// internal String NextPlanName() { return _planNamePrefix + (++_planIndex).ToString(); } internal void RemoveNotificationThread() { // Wait notification thread finish its work. lock (_socket) { // Kill notification thread. _notificationThread.Abort(); _notificationThread = null; // Special case in order to not get problems with thread synchronization. // It will be turned to 0 when synch thread is created. _notificationThreadStopCount = 1; } } internal void AddNotificationThread() { _notificationThreadStopCount = 0; NpgsqlContextHolder contextHolder = new NpgsqlContextHolder(this, CurrentState); _notificationThread = new Thread(new ThreadStart(contextHolder.ProcessServerMessages)); _notificationThread.Start(); } //Use with using(){} to perform the sentry pattern //on stopping and starting notification thread //(The sentry pattern is a generalisation of RAII where we //have a pair of actions - one "undoing" the previous //and we want to execute the first and second around other code, //then we treat it much like resource mangement in RAII. //try{}finally{} also does execute-around, but sentry classes //have some extra flexibility (e.g. they can be "owned" by //another object and then cleaned up when that object is //cleaned up), and can act as the sole gate-way //to the code in question, guaranteeing that using code can't be written //so that the "undoing" is forgotten. internal class NotificationThreadBlock : IDisposable { private NpgsqlConnector _connector; public NotificationThreadBlock(NpgsqlConnector connector) { (_connector = connector).StopNotificationThread(); } public void Dispose() { if (_connector != null) { _connector.ResumeNotificationThread(); } _connector = null; } } internal NotificationThreadBlock BlockNotificationThread() { return new NotificationThreadBlock(this); } private void StopNotificationThread() { // first check to see if an exception has // been thrown by the notification thread. if (_notificationException != null) { throw _notificationException; } _notificationThreadStopCount++; if (_notificationThreadStopCount == 1) // If this call was the first to increment. { Monitor.Enter(_socket); } } private void ResumeNotificationThread() { _notificationThreadStopCount--; if (_notificationThreadStopCount == 0) { // Release the synchronization handle. Monitor.Exit(_socket); } } internal Boolean IsNotificationThreadRunning { get { return _notificationThreadStopCount <= 0; } } internal class NpgsqlContextHolder { private readonly NpgsqlConnector connector; private readonly NpgsqlState state; internal NpgsqlContextHolder(NpgsqlConnector connector, NpgsqlState state) { this.connector = connector; this.state = state; } internal void ProcessServerMessages() { try { while (true) { // Mono's implementation of System.Threading.Monitor does not appear to give threads // priority on a first come/first serve basis, as does Microsoft's. As a result, // under mono, this loop may execute many times even after another thread has attempted // to lock on _socket. A short Sleep() seems to solve the problem effectively. // Note that Sleep(0) does not work. Thread.Sleep(1); lock (connector._socket) { // 20 millisecond timeout if (this.connector.Socket.Poll(20000, SelectMode.SelectRead)) { this.connector.ProcessAndDiscardBackendResponses(); } } } } catch (IOException ex) { this.connector._notificationException = ex; } } } public bool RequireReadyForQuery { get { return _requireReadyForQuery; } set { _requireReadyForQuery = value; } } public void AddParameterStatus(NpgsqlParameterStatus ps) { if (_serverParameters.ContainsKey(ps.Parameter)) { _serverParameters[ps.Parameter] = ps; } else { _serverParameters.Add(ps.Parameter, ps); } if (ps.Parameter == "standard_conforming_strings") { NativeToBackendTypeConverterOptions.UseConformantStrings = (ps.ParameterValue == "on"); } } public IDictionary ServerParameters { get { return new NpgsqlReadOnlyDictionary(_serverParameters); } } } }
X Tutup