Skip to content
Merged
235 changes: 133 additions & 102 deletions mssql_python/pybind/connection/connection.cpp

Large diffs are not rendered by default.

20 changes: 10 additions & 10 deletions mssql_python/pybind/connection/connection.h
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

// INFO|TODO - Note that is file is Windows specific right now. Making it arch agnostic will be
// taken up in future.

#pragma once
#include "ddbc_bindings.h"
#include <memory>
#include <string>
#include "../ddbc_bindings.h"

// Represents a single ODBC database connection.
// Manages connection handles.
// Note: This class does NOT implement pooling logic directly.

class Connection {
public:
public:
Connection(const std::wstring& connStr, bool fromPool);

~Connection();
Expand Down Expand Up @@ -50,7 +49,7 @@ class Connection {
// Add getter for DBC handle for error reporting
const SqlHandlePtr& getDbcHandle() const { return _dbcHandle; }

private:
private:
void allocateDbcHandle();
void checkError(SQLRETURN ret) const;
void applyAttrsBefore(const py::dict& attrs_before);
Expand All @@ -63,8 +62,9 @@ class Connection {
};

class ConnectionHandle {
public:
ConnectionHandle(const std::string& connStr, bool usePool, const py::dict& attrsBefore = py::dict());
public:
ConnectionHandle(const std::string& connStr, bool usePool,
const py::dict& attrsBefore = py::dict());
~ConnectionHandle();

void close();
Expand All @@ -78,8 +78,8 @@ class ConnectionHandle {
// Get information about the driver and data source
py::object getInfo(SQLUSMALLINT infoType) const;

private:
private:
std::shared_ptr<Connection> _conn;
bool _usePool;
std::wstring _connStr;
};
};
37 changes: 22 additions & 15 deletions mssql_python/pybind/connection/connection_pool.cpp
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

// INFO|TODO - Note that is file is Windows specific right now. Making it arch agnostic will be
// taken up in future.

#include "connection_pool.h"
#include "connection/connection_pool.h"
#include <exception>
#include <memory>
#include <vector>

ConnectionPool::ConnectionPool(size_t max_size, int idle_timeout_secs)
: _max_size(max_size), _idle_timeout_secs(idle_timeout_secs), _current_size(0) {}
: _max_size(max_size), _idle_timeout_secs(idle_timeout_secs),
_current_size(0) {}

std::shared_ptr<Connection> ConnectionPool::acquire(const std::wstring& connStr, const py::dict& attrs_before) {
std::shared_ptr<Connection> ConnectionPool::acquire(
const std::wstring& connStr, const py::dict& attrs_before) {
std::vector<std::shared_ptr<Connection>> to_disconnect;
std::shared_ptr<Connection> valid_conn = nullptr;
{
Expand All @@ -21,7 +22,8 @@ std::shared_ptr<Connection> ConnectionPool::acquire(const std::wstring& connStr,
// Phase 1: Remove stale connections, collect for later disconnect
_pool.erase(std::remove_if(_pool.begin(), _pool.end(),
[&](const std::shared_ptr<Connection>& conn) {
auto idle_time = std::chrono::duration_cast<std::chrono::seconds>(now - conn->lastUsed()).count();
auto idle_time = std::chrono::duration_cast<
std::chrono::seconds>(now - conn->lastUsed()).count();
if (idle_time > _idle_timeout_secs) {
to_disconnect.push_back(conn);
return true;
Expand All @@ -30,7 +32,8 @@ std::shared_ptr<Connection> ConnectionPool::acquire(const std::wstring& connStr,
}), _pool.end());

size_t pruned = before - _pool.size();
_current_size = (_current_size >= pruned) ? (_current_size - pruned) : 0;
_current_size = (_current_size >= pruned) ?
(_current_size - pruned) : 0;

// Phase 2: Attempt to reuse healthy connections
while (!_pool.empty()) {
Expand All @@ -56,7 +59,8 @@ std::shared_ptr<Connection> ConnectionPool::acquire(const std::wstring& connStr,
valid_conn->connect(attrs_before);
++_current_size;
} else if (!valid_conn) {
throw std::runtime_error("ConnectionPool::acquire: pool size limit reached");
throw std::runtime_error(
"ConnectionPool::acquire: pool size limit reached");
}
}

Expand All @@ -76,8 +80,7 @@ void ConnectionPool::release(std::shared_ptr<Connection> conn) {
if (_pool.size() < _max_size) {
conn->updateLastUsed();
_pool.push_back(conn);
}
else {
} else {
conn->disconnect();
if (_current_size > 0) --_current_size;
}
Expand All @@ -97,7 +100,8 @@ void ConnectionPool::close() {
try {
conn->disconnect();
} catch (const std::exception& ex) {
LOG("ConnectionPool::close: disconnect failed: {}", ex.what());
LOG("ConnectionPool::close: disconnect failed: {}",
ex.what());
}
}
}
Expand All @@ -107,18 +111,21 @@ ConnectionPoolManager& ConnectionPoolManager::getInstance() {
return manager;
}

std::shared_ptr<Connection> ConnectionPoolManager::acquireConnection(const std::wstring& connStr, const py::dict& attrs_before) {
std::shared_ptr<Connection> ConnectionPoolManager::acquireConnection(
const std::wstring& connStr, const py::dict& attrs_before) {
std::lock_guard<std::mutex> lock(_manager_mutex);

auto& pool = _pools[connStr];
if (!pool) {
LOG("Creating new connection pool");
pool = std::make_shared<ConnectionPool>(_default_max_size, _default_idle_secs);
pool = std::make_shared<ConnectionPool>(_default_max_size,
_default_idle_secs);
}
return pool->acquire(connStr, attrs_before);
}

void ConnectionPoolManager::returnConnection(const std::wstring& conn_str, const std::shared_ptr<Connection> conn) {
void ConnectionPoolManager::returnConnection(
const std::wstring& conn_str, const std::shared_ptr<Connection> conn) {
std::lock_guard<std::mutex> lock(_manager_mutex);
if (_pools.find(conn_str) != _pools.end()) {
_pools[conn_str]->release((conn));
Expand Down
44 changes: 26 additions & 18 deletions mssql_python/pybind/connection/connection_pool.h
Original file line number Diff line number Diff line change
@@ -1,59 +1,65 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

// INFO|TODO - Note that is file is Windows specific right now. Making it arch agnostic will be
// taken up in future.
#ifndef MSSQL_PYTHON_CONNECTION_POOL_H_
#define MSSQL_PYTHON_CONNECTION_POOL_H_

#pragma once
#include <chrono>
#include <deque>
#include <unordered_map>
#include <memory>
#include <mutex>
#include <string>
#include <chrono>
#include "connection.h"
#include <unordered_map>
#include "connection/connection.h"

// Manages a fixed-size pool of reusable database connections for a single connection string
// Manages a fixed-size pool of reusable database connections for a
// single connection string
class ConnectionPool {
public:
public:
ConnectionPool(size_t max_size, int idle_timeout_secs);

// Acquires a connection from the pool or creates a new one if under limit
std::shared_ptr<Connection> acquire(const std::wstring& connStr, const py::dict& attrs_before = py::dict());
std::shared_ptr<Connection> acquire(
const std::wstring& connStr,
const py::dict& attrs_before = py::dict());

// Returns a connection to the pool for reuse
void release(std::shared_ptr<Connection> conn);

// Closes all connections in the pool, releasing resources
void close();

private:
size_t _max_size; // Maximum number of connections allowed
int _idle_timeout_secs; // Idle time before connections are considered stale
private:
size_t _max_size; // Maximum number of connections allowed
int _idle_timeout_secs; // Idle time before connections are stale
size_t _current_size = 0;
std::deque<std::shared_ptr<Connection>> _pool; // Available connections
std::mutex _mutex; // Mutex for thread-safe access
std::mutex _mutex; // Mutex for thread-safe access
};

// Singleton manager that handles multiple pools keyed by connection string
class ConnectionPoolManager {
public:
public:
// Returns the singleton instance of the manager
static ConnectionPoolManager& getInstance();

void configure(int max_size, int idle_timeout);

// Gets a connection from the appropriate pool (creates one if none exists)
std::shared_ptr<Connection> acquireConnection(const std::wstring& conn_str, const py::dict& attrs_before = py::dict());
std::shared_ptr<Connection> acquireConnection(
const std::wstring& conn_str,
const py::dict& attrs_before = py::dict());

// Returns a connection to its original pool
void returnConnection(const std::wstring& conn_str, std::shared_ptr<Connection> conn);
void returnConnection(const std::wstring& conn_str,
std::shared_ptr<Connection> conn);

// Closes all pools and their connections
void closePools();

private:
ConnectionPoolManager() = default;
private:
ConnectionPoolManager() = default;
~ConnectionPoolManager() = default;

// Map from connection string to connection pool
Expand All @@ -63,8 +69,10 @@ class ConnectionPoolManager {
std::mutex _manager_mutex;
size_t _default_max_size = 10;
int _default_idle_secs = 300;

// Prevent copying
ConnectionPoolManager(const ConnectionPoolManager&) = delete;
ConnectionPoolManager& operator=(const ConnectionPoolManager&) = delete;
};

#endif // MSSQL_PYTHON_CONNECTION_POOL_H_
Loading