Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added mssql_python/msvcp140.dll
Binary file not shown.
236 changes: 135 additions & 101 deletions mssql_python/pybind/connection/connection.cpp

Large diffs are not rendered by default.

21 changes: 12 additions & 9 deletions mssql_python/pybind/connection/connection.h
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
// 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.
// 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 +52,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 +65,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 +81,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;
};
};
38 changes: 24 additions & 14 deletions mssql_python/pybind/connection/connection_pool.cpp
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
// 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.
// 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 +25,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 +35,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 +62,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 +83,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 +103,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 +114,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
47 changes: 29 additions & 18 deletions mssql_python/pybind/connection/connection_pool.h
Original file line number Diff line number Diff line change
@@ -1,59 +1,68 @@
// 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.
// 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 +72,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