| 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
 |