| 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 <exception>#include <memory>#include <mutex>#include <set>#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;};}	// namespace ZeroTier#endif
 |