123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175 |
- /*
- * Copyright (c)2021 ZeroTier, Inc.
- *
- * Use of this software is governed by the Business Source License included
- * in the LICENSE.TXT file in the project's root directory.
- *
- * Change Date: 2026-01-01
- *
- * On the date above, in accordance with the Business Source License, use
- * of this software will be governed by version 2.0 of the Apache License.
- */
- /****/
- #ifndef ZT_CONNECTION_POOL_H_
- #define ZT_CONNECTION_POOL_H_
- #ifndef _DEBUG
- #define _DEBUG(x)
- #endif
- #include "../node/Metrics.hpp"
- #include <deque>
- #include <set>
- #include <memory>
- #include <mutex>
- #include <exception>
- #include <string>
- namespace ZeroTier {
- struct ConnectionUnavailable : std::exception {
- char const* what() const throw() {
- return "Unable to allocate connection";
- };
- };
- class Connection {
- public:
- virtual ~Connection() {};
- };
- class ConnectionFactory {
- public:
- virtual ~ConnectionFactory() {};
- virtual std::shared_ptr<Connection> create()=0;
- };
- struct ConnectionPoolStats {
- size_t pool_size;
- size_t borrowed_size;
- };
- template<class T>
- class ConnectionPool {
- public:
- ConnectionPool(size_t max_pool_size, size_t min_pool_size, std::shared_ptr<ConnectionFactory> factory)
- : m_maxPoolSize(max_pool_size)
- , m_minPoolSize(min_pool_size)
- , m_factory(factory)
- {
- Metrics::max_pool_size += max_pool_size;
- Metrics::min_pool_size += min_pool_size;
- while(m_pool.size() < m_minPoolSize){
- m_pool.push_back(m_factory->create());
- Metrics::pool_avail++;
- }
- };
- ConnectionPoolStats get_stats() {
- std::unique_lock<std::mutex> lock(m_poolMutex);
- ConnectionPoolStats stats;
- stats.pool_size = m_pool.size();
- stats.borrowed_size = m_borrowed.size();
- return stats;
- };
- ~ConnectionPool() {
- };
- /**
- * Borrow
- *
- * Borrow a connection for temporary use
- *
- * When done, either (a) call unborrow() to return it, or (b) (if it's bad) just let it go out of scope. This will cause it to automatically be replaced.
- * @retval a shared_ptr to the connection object
- */
- std::shared_ptr<T> borrow() {
- std::unique_lock<std::mutex> l(m_poolMutex);
-
- while((m_pool.size() + m_borrowed.size()) < m_minPoolSize) {
- std::shared_ptr<Connection> conn = m_factory->create();
- m_pool.push_back(conn);
- Metrics::pool_avail++;
- }
- if(m_pool.size()==0){
-
- if ((m_pool.size() + m_borrowed.size()) < m_maxPoolSize) {
- try {
- std::shared_ptr<Connection> conn = m_factory->create();
- m_borrowed.insert(conn);
- Metrics::pool_in_use++;
- return std::static_pointer_cast<T>(conn);
- } catch (std::exception &e) {
- Metrics::pool_errors++;
- throw ConnectionUnavailable();
- }
- } else {
- for(auto it = m_borrowed.begin(); it != m_borrowed.end(); ++it){
- if((*it).unique()) {
- // This connection has been abandoned! Destroy it and create a new connection
- try {
- // If we are able to create a new connection, return it
- _DEBUG("Creating new connection to replace discarded connection");
- std::shared_ptr<Connection> conn = m_factory->create();
- m_borrowed.erase(it);
- m_borrowed.insert(conn);
- return std::static_pointer_cast<T>(conn);
- } catch(std::exception& e) {
- // Error creating a replacement connection
- Metrics::pool_errors++;
- throw ConnectionUnavailable();
- }
- }
- }
- // Nothing available
- Metrics::pool_errors++;
- throw ConnectionUnavailable();
- }
- }
- // Take one off the front
- std::shared_ptr<Connection> conn = m_pool.front();
- m_pool.pop_front();
- Metrics::pool_avail--;
- // Add it to the borrowed list
- m_borrowed.insert(conn);
- Metrics::pool_in_use++;
- return std::static_pointer_cast<T>(conn);
- };
- /**
- * Unborrow a connection
- *
- * Only call this if you are returning a working connection. If the connection was bad, just let it go out of scope (so the connection manager can replace it).
- * @param the connection
- */
- void unborrow(std::shared_ptr<T> conn) {
- // Lock
- std::unique_lock<std::mutex> lock(m_poolMutex);
- m_borrowed.erase(conn);
- Metrics::pool_in_use--;
- if ((m_pool.size() + m_borrowed.size()) < m_maxPoolSize) {
- Metrics::pool_avail++;
- m_pool.push_back(conn);
- }
- };
- protected:
- size_t m_maxPoolSize;
- size_t m_minPoolSize;
- std::shared_ptr<ConnectionFactory> m_factory;
- std::deque<std::shared_ptr<Connection> > m_pool;
- std::set<std::shared_ptr<Connection> > m_borrowed;
- std::mutex m_poolMutex;
- };
- }
- #endif
|