private async Task <MySqlSession> CreateSessionAsync(IOBehavior ioBehavior, CancellationToken cancellationToken) { var connectTimeout = m_connectionSettings.ConnectionTimeout == 0 ? Timeout.InfiniteTimeSpan : TimeSpan.FromSeconds(checked ((int)m_connectionSettings.ConnectionTimeout)); using (var timeoutSource = new CancellationTokenSource(connectTimeout)) using (var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutSource.Token)) { try { // get existing session from the pool if possible if (m_connectionSettings.Pooling) { var pool = ConnectionPool.GetPool(m_connectionSettings); // this returns an open session return(await pool.GetSessionAsync(ioBehavior, linkedSource.Token).ConfigureAwait(false)); } else { var session = new MySqlSession(); await session.ConnectAsync(m_connectionSettings, ioBehavior, linkedSource.Token).ConfigureAwait(false); return(session); } } catch (OperationCanceledException ex) when(timeoutSource.IsCancellationRequested) { throw new MySqlException("Connect Timeout expired.", ex); } } }
internal static DbTable <T> MockTempTable <T>(this MySqlSession mySqlSession, IList <MySqlCommand> commands, T fromModel = null, Action <T> initializer = null) where T : Model, new() { var result = mySqlSession.MockTempTable <T>(fromModel, initializer); commands.Add(mySqlSession.GetCreateTableCommand(result.Model, true)); return(result); }
public bool Return(MySqlSession session) { lock (m_lock) { // TODO: Dispose oldest connections in the pool first? if (m_sessions.Count >= m_maximumSize) return false; m_sessions.Enqueue(session); return true; } }
public bool Return(MySqlSession session) { lock (m_lock) { // TODO: Dispose oldest connections in the pool first? if (m_sessions.Count >= m_maximumSize) { return(false); } m_sessions.Enqueue(session); return(true); } }
public async Task <MySqlSession> GetSessionAsync(IOBehavior ioBehavior, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); // wait for an open slot if (ioBehavior == IOBehavior.Asynchronous) { await m_session_semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); } else { m_session_semaphore.Wait(cancellationToken); } try { MySqlSession session; // check for a pooled session if (m_sessions.TryDequeue(out session)) { if (session.PoolGeneration != m_generation || !await session.TryPingAsync(ioBehavior, cancellationToken).ConfigureAwait(false)) { // session is either old or cannot communicate with the server await session.DisposeAsync(ioBehavior, cancellationToken).ConfigureAwait(false); } else { // session is valid, reset if supported if (m_resetConnections) { await session.ResetConnectionAsync(m_userId, m_password, m_database, ioBehavior, cancellationToken).ConfigureAwait(false); } // pooled session is ready to be used; return it return(session); } } session = new MySqlSession(this, m_generation); await session.ConnectAsync(m_servers, m_port, m_userId, m_password, m_database, ioBehavior, cancellationToken).ConfigureAwait(false); return(session); } catch { m_session_semaphore.Release(); throw; } }
public void Return(MySqlSession session) { try { if (session.PoolGeneration == m_generation) { m_sessions.Enqueue(session); } else { session.DisposeAsync(IOBehavior.Synchronous, CancellationToken.None).ConfigureAwait(false); } } finally { m_session_semaphore.Release(); } }
public bool Return(MySqlSession session) { try { m_semaphore.Wait(); // TODO: Dispose oldest connections in the pool first? if (m_sessions.Count >= m_maximumSize) { return(false); } m_sessions.Enqueue(session); return(true); } finally { m_semaphore.Release(); } }
private static async Task <bool> TryPingAsync(MySqlSession session, CancellationToken cancellationToken) { await session.SendAsync(PingPayload.Create(), cancellationToken).ConfigureAwait(false); try { var payload = await session.ReceiveReplyAsync(cancellationToken).ConfigureAwait(false); OkPayload.Create(payload); return(true); } catch (EndOfStreamException) { } catch (SocketException) { } return(false); }
public void Return(MySqlSession session) { try { lock (m_leasedSessions) m_leasedSessions.Remove(session.Id); if (SessionIsHealthy(session)) { lock (m_sessions) m_sessions.AddFirst(session); } else { session.DisposeAsync(IOBehavior.Synchronous, CancellationToken.None).ConfigureAwait(false); } } finally { m_sessionSemaphore.Release(); } }
private bool SessionIsHealthy(MySqlSession session) { if (!session.IsConnected) { return(false); } if (session.PoolGeneration != m_generation) { return(false); } if (session.DatabaseOverride != null) { return(false); } if (m_connectionSettings.ConnectionLifeTime > 0 && (DateTime.UtcNow - session.CreatedUtc).TotalSeconds >= m_connectionSettings.ConnectionLifeTime) { return(false); } return(true); }
private async Task CleanPoolAsync(IOBehavior ioBehavior, Func <MySqlSession, bool> shouldCleanFn, bool respectMinPoolSize, CancellationToken cancellationToken) { // synchronize access to this method as only one clean routine should be run at a time if (ioBehavior == IOBehavior.Asynchronous) { await m_cleanSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); } else { m_cleanSemaphore.Wait(cancellationToken); } try { var waitTimeout = TimeSpan.FromMilliseconds(10); while (true) { // if respectMinPoolSize is true, return if (leased sessions + waiting sessions <= minPoolSize) if (respectMinPoolSize) { lock (m_sessions) if (m_connectionSettings.MaximumPoolSize - m_sessionSemaphore.CurrentCount + m_sessions.Count <= m_connectionSettings.MinimumPoolSize) { return; } } // try to get an open slot; if this fails, connection pool is full and sessions will be disposed when returned to pool if (ioBehavior == IOBehavior.Asynchronous) { if (!await m_sessionSemaphore.WaitAsync(waitTimeout, cancellationToken).ConfigureAwait(false)) { return; } } else { if (!m_sessionSemaphore.Wait(waitTimeout, cancellationToken)) { return; } } try { // check for a waiting session MySqlSession session = null; lock (m_sessions) { if (m_sessions.Count > 0) { session = m_sessions.Last.Value; m_sessions.RemoveLast(); } } if (session == null) { return; } if (shouldCleanFn(session)) { // session should be cleaned; dispose it and keep iterating await session.DisposeAsync(ioBehavior, cancellationToken).ConfigureAwait(false); } else { // session should not be cleaned; put it back in the queue and stop iterating lock (m_sessions) m_sessions.AddLast(session); return; } } finally { m_sessionSemaphore.Release(); } } } finally { m_cleanSemaphore.Release(); } }
public async Task <MySqlSession> GetSessionAsync(MySqlConnection connection, IOBehavior ioBehavior, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); // if all sessions are used, see if any have been leaked and can be recovered // check at most once per second (although this isn't enforced via a mutex so multiple threads might block // on the lock in RecoverLeakedSessions in high-concurrency situations if (m_sessionSemaphore.CurrentCount == 0 && unchecked (((uint)Environment.TickCount) - m_lastRecoveryTime) >= 1000u) { RecoverLeakedSessions(); } // wait for an open slot (until the cancellationToken is cancelled, which is typically due to timeout) if (ioBehavior == IOBehavior.Asynchronous) { await m_sessionSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); } else { m_sessionSemaphore.Wait(cancellationToken); } try { // check for a waiting session MySqlSession session = null; lock (m_sessions) { if (m_sessions.Count > 0) { session = m_sessions.First.Value; m_sessions.RemoveFirst(); } } if (session != null) { bool reuseSession; if (session.PoolGeneration != m_generation) { reuseSession = false; } else { if (m_connectionSettings.ConnectionReset) { reuseSession = await session.TryResetConnectionAsync(m_connectionSettings, ioBehavior, cancellationToken).ConfigureAwait(false); } else { reuseSession = await session.TryPingAsync(ioBehavior, cancellationToken).ConfigureAwait(false); } } if (!reuseSession) { // session is either old or cannot communicate with the server await session.DisposeAsync(ioBehavior, cancellationToken).ConfigureAwait(false); } else { // pooled session is ready to be used; return it session.OwningConnection = new WeakReference <MySqlConnection>(connection); lock (m_leasedSessions) m_leasedSessions.Add(session.Id, session); return(session); } } // create a new session session = new MySqlSession(this, m_generation, Interlocked.Increment(ref m_lastId)); await session.ConnectAsync(m_connectionSettings, ioBehavior, cancellationToken).ConfigureAwait(false); session.OwningConnection = new WeakReference <MySqlConnection>(connection); lock (m_leasedSessions) m_leasedSessions.Add(session.Id, session); return(session); } catch { m_sessionSemaphore.Release(); throw; } }
internal static DbTable <T> MockTempTable <T>(this MySqlSession mySqlSession, T fromModel = null, Action <T> initializer = null) where T : Model, new() { return(mySqlSession.CreateTempTableInstance(fromModel, initializer)); }
public override async Task OpenAsync(CancellationToken cancellationToken) { VerifyNotDisposed(); if (State != ConnectionState.Closed) { throw new InvalidOperationException("Cannot Open when State is {0}.".FormatInvariant(State)); } #if !NETSTANDARD1_3 if (System.Transactions.Transaction.Current != null) { throw new NotSupportedException("Ambient transactions are not supported. Use BeginTransaction instead."); } #endif SetState(ConnectionState.Connecting); bool success = false; try { // get existing session from the pool if possible var pool = ConnectionPool.GetPool(m_connectionStringBuilder); m_session = pool == null ? null : await pool.TryGetSessionAsync(cancellationToken).ConfigureAwait(false); if (m_session != null) { // test that session is still valid and (optionally) reset it if (!await TryPingAsync(m_session, cancellationToken).ConfigureAwait(false)) { Utility.Dispose(ref m_session); } else if (m_connectionStringBuilder.ConnectionReset) { await ResetConnectionAsync(cancellationToken).ConfigureAwait(false); } } if (m_session == null) { m_session = new MySqlSession(pool); var connected = await m_session.ConnectAsync(m_connectionStringBuilder.Server.Split(','), (int)m_connectionStringBuilder.Port).ConfigureAwait(false); if (!connected) { SetState(ConnectionState.Closed); throw new MySqlException("Unable to connect to any of the specified MySQL hosts."); } var payload = await m_session.ReceiveAsync(cancellationToken).ConfigureAwait(false); var reader = new ByteArrayReader(payload.ArraySegment.Array, payload.ArraySegment.Offset, payload.ArraySegment.Count); var initialHandshake = new InitialHandshakePacket(reader); if (initialHandshake.AuthPluginName != "mysql_native_password") { throw new NotSupportedException("Only 'mysql_native_password' authentication method is supported."); } m_session.ServerVersion = new ServerVersion(Encoding.ASCII.GetString(initialHandshake.ServerVersion)); m_session.AuthPluginData = initialHandshake.AuthPluginData; var response = HandshakeResponse41Packet.Create(initialHandshake, m_connectionStringBuilder.UserID, m_connectionStringBuilder.Password, m_database); payload = new PayloadData(new ArraySegment <byte>(response)); await m_session.SendReplyAsync(payload, cancellationToken).ConfigureAwait(false); await m_session.ReceiveReplyAsync(cancellationToken).ConfigureAwait(false); // TODO: Check success } m_hasBeenOpened = true; SetState(ConnectionState.Open); success = true; } catch (MySqlException) { SetState(ConnectionState.Closed); throw; } catch (SocketException ex) { SetState(ConnectionState.Closed); throw new MySqlException("Unable to connect to any of the specified MySQL hosts.", ex); } finally { if (!success) { Utility.Dispose(ref m_session); } } }
public override async Task OpenAsync(CancellationToken cancellationToken) { VerifyNotDisposed(); if (State != ConnectionState.Closed) throw new InvalidOperationException("Cannot Open when State is {0}.".FormatInvariant(State)); #if !NETSTANDARD1_3 if (System.Transactions.Transaction.Current != null) throw new NotSupportedException("Ambient transactions are not supported. Use BeginTransaction instead."); #endif if (m_connectionStringBuilder.UseCompression) throw new NotSupportedException("Compression not supported."); SetState(ConnectionState.Connecting); bool success = false; try { var pool = ConnectionPool.GetPool(m_connectionStringBuilder); m_session = pool?.TryGetSession(); if (m_session == null) { m_session = new MySqlSession(pool); var connected = await m_session.ConnectAsync(m_connectionStringBuilder.Server.Split(','), (int) m_connectionStringBuilder.Port).ConfigureAwait(false); if (!connected) { SetState(ConnectionState.Closed); throw new MySqlException("Unable to connect to any of the specified MySQL hosts."); } var payload = await m_session.ReceiveAsync(cancellationToken).ConfigureAwait(false); var reader = new ByteArrayReader(payload.ArraySegment.Array, payload.ArraySegment.Offset, payload.ArraySegment.Count); var initialHandshake = new InitialHandshakePacket(reader); if (initialHandshake.AuthPluginName != "mysql_native_password") throw new NotSupportedException("Only 'mysql_native_password' authentication method is supported."); m_session.ServerVersion = new ServerVersion(Encoding.ASCII.GetString(initialHandshake.ServerVersion)); m_session.AuthPluginData = initialHandshake.AuthPluginData; var response = HandshakeResponse41Packet.Create(initialHandshake, m_connectionStringBuilder.UserID, m_connectionStringBuilder.Password, m_database); payload = new PayloadData(new ArraySegment<byte>(response)); await m_session.SendReplyAsync(payload, cancellationToken).ConfigureAwait(false); await m_session.ReceiveReplyAsync(cancellationToken).ConfigureAwait(false); // TODO: Check success } else if (m_connectionStringBuilder.ConnectionReset) { if (m_session.ServerVersion.Version.CompareTo(ServerVersions.SupportsResetConnection) >= 0) { await m_session.SendAsync(ResetConnectionPayload.Create(), cancellationToken).ConfigureAwait(false); var payload = await m_session.ReceiveReplyAsync(cancellationToken).ConfigureAwait(false); OkPayload.Create(payload); } else { // optimistically hash the password with the challenge from the initial handshake (supported by MariaDB; doesn't appear to be supported by MySQL) var hashedPassword = AuthenticationUtility.HashPassword(m_session.AuthPluginData, 0, m_connectionStringBuilder.Password); var payload = ChangeUserPayload.Create(m_connectionStringBuilder.UserID, hashedPassword, m_database); await m_session.SendAsync(payload, cancellationToken).ConfigureAwait(false); payload = await m_session.ReceiveReplyAsync(cancellationToken).ConfigureAwait(false); if (payload.HeaderByte == AuthenticationMethodSwitchRequestPayload.Signature) { // if the server didn't support the hashed password; rehash with the new challenge var switchRequest = AuthenticationMethodSwitchRequestPayload.Create(payload); if (switchRequest.Name != "mysql_native_password") throw new NotSupportedException("Only 'mysql_native_password' authentication method is supported."); hashedPassword = AuthenticationUtility.HashPassword(switchRequest.Data, 0, m_connectionStringBuilder.Password); payload = new PayloadData(new ArraySegment<byte>(hashedPassword)); await m_session.SendReplyAsync(payload, cancellationToken).ConfigureAwait(false); payload = await m_session.ReceiveReplyAsync(cancellationToken).ConfigureAwait(false); } OkPayload.Create(payload); } } m_hasBeenOpened = true; SetState(ConnectionState.Open); success = true; } catch (MySqlException) { SetState(ConnectionState.Closed); throw; } catch (SocketException ex) { SetState(ConnectionState.Closed); throw new MySqlException("Unable to connect to any of the specified MySQL hosts.", ex); } finally { if (!success) Utility.Dispose(ref m_session); } }
public override async Task OpenAsync(CancellationToken cancellationToken) { VerifyNotDisposed(); if (State != ConnectionState.Closed) { throw new InvalidOperationException("Cannot Open when State is {0}.".FormatInvariant(State)); } #if !NETSTANDARD1_3 if (System.Transactions.Transaction.Current != null) { throw new NotSupportedException("Ambient transactions are not supported. Use BeginTransaction instead."); } #endif if (m_connectionStringBuilder.UseCompression) { throw new NotSupportedException("Compression not supported."); } SetState(ConnectionState.Connecting); bool success = false; try { var pool = ConnectionPool.GetPool(m_connectionStringBuilder); m_session = pool?.TryGetSession(); if (m_session == null) { m_session = new MySqlSession(pool); var connected = await m_session.ConnectAsync(m_connectionStringBuilder.Server.Split(','), (int)m_connectionStringBuilder.Port).ConfigureAwait(false); if (!connected) { SetState(ConnectionState.Closed); throw new MySqlException("Unable to connect to any of the specified MySQL hosts."); } var payload = await m_session.ReceiveAsync(cancellationToken).ConfigureAwait(false); var reader = new ByteArrayReader(payload.ArraySegment.Array, payload.ArraySegment.Offset, payload.ArraySegment.Count); var initialHandshake = new InitialHandshakePacket(reader); if (initialHandshake.AuthPluginName != "mysql_native_password") { throw new NotSupportedException("Only 'mysql_native_password' authentication method is supported."); } m_session.ServerVersion = new ServerVersion(Encoding.ASCII.GetString(initialHandshake.ServerVersion)); var response = HandshakeResponse41Packet.Create(initialHandshake, m_connectionStringBuilder.UserID, m_connectionStringBuilder.Password, m_database); payload = new PayloadData(new ArraySegment <byte>(response)); await m_session.SendReplyAsync(payload, cancellationToken).ConfigureAwait(false); await m_session.ReceiveReplyAsync(cancellationToken).ConfigureAwait(false); // TODO: Check success } else if (m_connectionStringBuilder.ConnectionReset) { if (m_session.ServerVersion.Version.CompareTo(ServerVersions.SupportsResetConnection) >= 0) { await m_session.SendAsync(ResetConnectionPayload.Create(), cancellationToken).ConfigureAwait(false); var payload = await m_session.ReceiveReplyAsync(cancellationToken); OkPayload.Create(payload); } else { // MySQL doesn't appear to accept a replayed hashed password (using the challenge from the initial handshake), so just send zeroes // and expect to get a new challenge var payload = ChangeUserPayload.Create(m_connectionStringBuilder.UserID, new byte[20], m_database); await m_session.SendAsync(payload, cancellationToken).ConfigureAwait(false); payload = await m_session.ReceiveReplyAsync(cancellationToken).ConfigureAwait(false); var switchRequest = AuthenticationMethodSwitchRequestPayload.Create(payload); if (switchRequest.Name != "mysql_native_password") { throw new NotSupportedException("Only 'mysql_native_password' authentication method is supported."); } var hashedPassword = AuthenticationUtility.HashPassword(switchRequest.Data, 0, m_connectionStringBuilder.Password); payload = new PayloadData(new ArraySegment <byte>(hashedPassword)); await m_session.SendReplyAsync(payload, cancellationToken).ConfigureAwait(false); payload = await m_session.ReceiveReplyAsync(cancellationToken).ConfigureAwait(false); OkPayload.Create(payload); } } m_hasBeenOpened = true; SetState(ConnectionState.Open); success = true; } catch (MySqlException) { SetState(ConnectionState.Closed); throw; } catch (SocketException ex) { SetState(ConnectionState.Closed); throw new MySqlException("Unable to connect to any of the specified MySQL hosts.", ex); } finally { if (!success) { Utility.Dispose(ref m_session); } } }