/*
 *  Phusion Passenger - https://www.phusionpassenger.com/
 *  Copyright (c) 2011-2025 Asynchronous B.V.
 *
 *  "Passenger", "Phusion Passenger" and "Union Station" are registered
 *  trademarks of Asynchronous B.V.
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *  THE SOFTWARE.
 */
#ifndef _PASSENGER_APPLICATION_POOL_SOCKET_H_
#define _PASSENGER_APPLICATION_POOL_SOCKET_H_

#include <vector>
#include <oxt/macros.hpp>
#include <boost/thread.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <boost/container/small_vector.hpp>
#include <climits>
#include <cassert>
#include <LoggingKit/LoggingKit.h>
#include <StaticString.h>
#include <MemoryKit/palloc.h>
#include <IOTools/IOUtils.h>
#include <Core/ApplicationPool/Common.h>

namespace Passenger {
namespace ApplicationPool2 {

using namespace std;
using namespace boost;


struct Connection {
	int fd;
	bool wantKeepAlive: 1;
	bool fail: 1;
	bool ready: 1;

	Connection()
		: fd(-1),
		  wantKeepAlive(false),
		  fail(false),
		  ready(false)
		{ }

	void close() {
		if (fd != -1) {
			int fd2 = fd;
			fd = -1;
			wantKeepAlive = false;
			safelyClose(fd2);
			P_LOG_FILE_DESCRIPTOR_CLOSE(fd2);
		}
	}
};

/**
 * Not thread-safe except for the connection pooling methods, so only use
 * within the ApplicationPool lock.
 */
class Socket {
private:
	boost::mutex connectionPoolLock;
	vector<Connection> idleConnections;

	OXT_FORCE_INLINE
	int connectionPoolLimit() const {
		return concurrency;
	}

	/** Initiates a non-blocking connect. */
	Connection connect() const {
		Connection connection;
		P_TRACE(3, "Connecting to " << address);
		auto result = createNonBlockingSocketConnection(address, __FILE__, __LINE__);
		connection.ready = result.second;
		connection.fail = true;
		connection.fd = result.first;
		connection.wantKeepAlive = false;
		P_LOG_FILE_DESCRIPTOR_PURPOSE(connection.fd, "App " << pid << " connection");
		return connection;
	}

public:
	// Socket properties. Read-only.
	StaticString address;
	StaticString protocol;
	StaticString description;
	pid_t pid;
	/**
	 * Special values:
	 * 0 = unlimited concurrency
	 * -1 = unknown
	 */
	int concurrency;
	bool acceptHttpRequests;

	// Private. In public section as alignment optimization.
	int totalConnections;
	int totalIdleConnections;

	/** Invariant: sessions >= 0 */
	int sessions;

	Socket()
		: pid(-1),
		  concurrency(-1),
		  acceptHttpRequests(0)
		{ }

	Socket(pid_t _pid, const StaticString &_address, const StaticString &_protocol,
		const StaticString &_description, int _concurrency, bool _acceptHttpRequests)
		: address(_address),
		  protocol(_protocol),
		  description(_description),
		  pid(_pid),
		  concurrency(_concurrency),
		  acceptHttpRequests(_acceptHttpRequests),
		  totalConnections(0),
		  totalIdleConnections(0),
		  sessions(0)
		{ }

	Socket(const Socket &other)
		: idleConnections(other.idleConnections),
		  address(other.address),
		  protocol(other.protocol),
		  description(other.description),
		  pid(other.pid),
		  concurrency(other.concurrency),
		  acceptHttpRequests(other.acceptHttpRequests),
		  totalConnections(other.totalConnections),
		  totalIdleConnections(other.totalIdleConnections),
		  sessions(other.sessions)
		{ }

	Socket &operator=(const Socket &other) {
		totalConnections = other.totalConnections;
		totalIdleConnections = other.totalIdleConnections;
		idleConnections = other.idleConnections;
		address = other.address;
		protocol = other.protocol;
		description = other.description;
		pid = other.pid;
		concurrency = other.concurrency;
		acceptHttpRequests = other.acceptHttpRequests;
		sessions = other.sessions;
		return *this;
	}

	/**
	 * Initiates a non-blocking connection to this socket or reuse an existing connection.
	 * Use `result.ready` to check whether the connect is finished or whether you need
	 * to wait for it to finish.
	 *
	 * One MUST call checkinConnection() when one's done using the Connection.
	 * Failure to do so will result in a resource leak.
	 *
	 * @throws SystemException Something went wrong.
	 * @throws RuntimeException Something went wrong.
	 * @throws boost::thread_interrupted A system call has been interrupted.
	 */
	Connection checkoutConnection() {
		boost::unique_lock<boost::mutex> l(connectionPoolLock);

		if (!idleConnections.empty()) {
			TRACE_POINT();
			P_TRACE(3, "Socket " << address << ": checking out connection from connection pool (" <<
				idleConnections.size() << " -> " << (idleConnections.size() - 1) <<
				" items). Current total number of connections: " << totalConnections);
			Connection connection = idleConnections.back();
			idleConnections.pop_back();
			totalIdleConnections--;
			return connection;
		} else {
			TRACE_POINT();
			Connection connection = connect();
			totalConnections++;
			P_TRACE(3, "Socket " << address << ": there are now " <<
				totalConnections << " total connections");
			l.unlock();
			return connection;
		}
	}

	void checkinConnection(Connection &connection) {
		boost::unique_lock<boost::mutex> l(connectionPoolLock);

		if (connection.fail || !connection.wantKeepAlive || totalIdleConnections >= connectionPoolLimit()) {
			totalConnections--;
			assert(totalConnections >= 0);
			P_TRACE(3, "Socket " << address << ": connection not checked back into "
				"connection pool. There are now " << totalConnections <<
				" connections in total");
			l.unlock();
			connection.close();
		} else {
			P_TRACE(3, "Socket " << address << ": checking in connection into connection pool (" <<
				totalIdleConnections << " -> " << (totalIdleConnections + 1) <<
				" items). Current total number of connections: " << totalConnections);
			totalIdleConnections++;
			idleConnections.push_back(connection);
		}
	}

	void closeAllConnections() {
		boost::unique_lock<boost::mutex> l(connectionPoolLock);
		assert(sessions == 0);
		assert(totalConnections == totalIdleConnections);
		vector<Connection>::iterator it, end = idleConnections.end();

		for (it = idleConnections.begin(); it != end; it++) {
			try {
				it->close();
			} catch (const SystemException &e) {
				P_ERROR("Cannot close a connection with socket " << address << ": " << e.what());
			}
		}
		idleConnections.clear();
		totalConnections = 0;
		totalIdleConnections = 0;
	}


	bool isIdle() const {
		return sessions == 0;
	}

	int busyness() const {
		/* Different sockets within a Process may have different
		 * 'concurrency' values. We want:
		 * - the socket with the smallest busyness to be be picked for routing.
		 * - to give sockets with concurrency == 0 or -1 more priority (in general)
		 *   over sockets with concurrency > 0.
		 * Therefore, in case of sockets with concurrency > 0, we describe our
		 * busyness as a percentage of 'concurrency', with the percentage value
		 * in [0..INT_MAX] instead of [0..1]. That way, the busyness value
		 * of sockets with concurrency > 0 is usually higher than that of sockets
		 * with concurrency == 0 or -1.
		 */
		if (concurrency <= 0) {
			return sessions;
		} else {
			return (int) (((long long) sessions * INT_MAX) / (double) concurrency);
		}
	}

	bool isTotallyBusy() const {
		return concurrency > 0 && sessions >= concurrency;
	}

	void recreateStrings(psg_pool_t *newPool) {
		recreateString(newPool, address);
		recreateString(newPool, protocol);
		recreateString(newPool, description);
	}
};

class SocketList: public boost::container::small_vector<Socket, 1> {
public:
	void add(pid_t pid, const StaticString &address, const StaticString &protocol,
		const StaticString &description, int concurrency, bool acceptHttpRequests)
	{
		push_back(Socket(pid, address, protocol, description, concurrency,
			acceptHttpRequests));
	}

	const Socket *findFirstSocketWithProtocol(const StaticString &protocol) const {
		const_iterator it, end = this->end();
		for (it = begin(); it != end; it++) {
			if (it->protocol == protocol) {
				return &(*it);
			}
		}
		return NULL;
	}
};

typedef boost::shared_ptr<SocketList> SocketListPtr;


} // namespace ApplicationPool2
} // namespace Passenger

#endif /* _PASSENGER_APPLICATION_POOL2_SOCKET_H_ */
