/*
 *  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_PROCESS_H_
#define _PASSENGER_APPLICATION_POOL_PROCESS_H_

#include <string>
#include <vector>
#include <boost/intrusive_ptr.hpp>
#include <boost/move/core.hpp>
#include <boost/container/vector.hpp>
#include <oxt/system_calls.hpp>
#include <oxt/spin_lock.hpp>
#include <oxt/macros.hpp>
#include <sys/types.h>
#include <cstdio>
#include <climits>
#include <cassert>
#include <cstring>
#include <Constants.h>
#include <FileDescriptor.h>
#include <LoggingKit/LoggingKit.h>
#include <SystemTools/ProcessMetricsCollector.h>
#include <SystemTools/SystemTime.h>
#include <StrIntTools/StrIntUtils.h>
#include <Utils/Lock.h>
#include <Core/ApplicationPool/Common.h>
#include <Core/ApplicationPool/Socket.h>
#include <Core/ApplicationPool/Session.h>
#include <Core/SpawningKit/PipeWatcher.h>
#include <Core/SpawningKit/Result.h>
#include <Shared/ApplicationPoolApiKey.h>

namespace tut {
	template<class Data> class test_object;
}

namespace Passenger {
namespace ApplicationPool2 {

using namespace std;
using namespace boost;


typedef boost::container::vector<ProcessPtr> ProcessList;

/**
 * Represents an application process, as spawned by a SpawningKit::Spawner. Every
 * Process has a PID, a stdin pipe, an output pipe and a list of sockets on which
 * it listens for connections. A Process object is contained inside a Group.
 *
 * The stdin pipe is mapped to the process's STDIN and is used for garbage
 * collection: closing the STDIN part causes the process to gracefully terminate itself.
 *
 * The output pipe is mapped to the process' STDOUT and STDERR. All data coming
 * from those pipes will be printed.
 *
 * Except for the otherwise documented parts, this class is not thread-safe,
 * so only use within the Pool lock.
 *
 * ## Normal usage
 *
 *  1. Create a session with newSession().
 *  2. Initiate the session by calling initiate() on it.
 *  3. Perform I/O through session->fd().
 *  4. When done, close the session by calling close() on it.
 *  5. Call process.sessionClosed().
 *
 * ## Life time
 *
 * A Process object lives until the containing Group calls `detach(process)`,
 * which indicates that it wants this Process to shut down. The Process object
 * is stored in the `detachedProcesses` collection in the Group and is no longer
 * eligible for receiving requests. Once all requests on this Process have finished,
 * `triggerShutdown()` will be called, which will send a message to the
 * OS process telling it to shut down. Once the OS process is gone, `cleanup()` is
 * called, and the Process object is removed from the collection.
 *
 * This means that a Group outlives all its Processes, a Process outlives all
 * its Sessions, and a Process also outlives the OS process.
 */
class Process {
public:
	friend class Group;
	template<class Data> friend class tut::test_object;

	static const unsigned int MAX_SOCKETS_ACCEPTING_HTTP_REQUESTS = 3;

private:
	/*************************************************************
	 * Read-only fields, set once during initialization and never
	 * written to again. Reading is thread-safe.
	 *************************************************************/

	BasicProcessInfo info;
	DynamicBuffer stringBuffer;
	SocketList sockets;

	/**
	 * The maximum amount of concurrent sessions this process can handle.
	 * 0 means unlimited. Automatically inferred from the sockets.
	 */
	int concurrency;

	/**
	 * A subset of 'sockets': all sockets that accept HTTP requests
	 * from the Passenger Core controller.
	 */
	unsigned int socketsAcceptingHttpRequestsCount;
	Socket *socketsAcceptingHttpRequests[MAX_SOCKETS_ACCEPTING_HTTP_REQUESTS];

	/** Input pipe. See Process class description. */
	FileDescriptor inputPipe;

	/**
	 * Pipe on which this process outputs stdout and stderr data. Mapped to the
	 * process's STDOUT and STDERR.
	 */
	FileDescriptor outputPipe;

	/**
	 * The code revision of the application, inferred through various means.
	 * See Spawner::prepareSpawn() to learn how this is determined.
	 * May be an empty string if no code revision has been inferred.
	 */
	StaticString codeRevision;

	/**
	 * Time at which the Spawner that created this process was created.
	 * Microseconds resolution.
	 */
	unsigned long long spawnerCreationTime;

	/** Time at which we started spawning this process. Microseconds resolution. */
	unsigned long long spawnStartTime;

	/**
	 * Time at which we finished spawning this process, i.e. when this
	 * process was finished initializing. Microseconds resolution.
	 */
	unsigned long long spawnEndTime;

	SpawningKit::Result::Type type;

	/**
	 * Whether it is required that triggerShutdown() and cleanup() must be called
	 * before destroying this Process. Normally true, except for dummy Process
	 * objects created by Pool::asyncGet() with options.noop == true, because those
	 * processes are never added to Group.enabledProcesses.
	 */
	bool requiresShutdown;


	/*************************************************************
	 * Read-write fields.
	 *************************************************************/

	mutable boost::atomic<int> refcount;

	/** A mutex to protect access to `lifeStatus`. */
	mutable oxt::spin_lock lifetimeSyncher;

	/** The index inside the associated Group's process list. */
	unsigned int index;


	/*************************************************************
	 * Methods
	 *************************************************************/

	/****** Initialization and destruction ******/

	struct InitializationLog {
		struct String {
			unsigned int offset;
			unsigned int size;
		};

		struct SocketStringOffsets {
			String address;
			String protocol;
			String description;
		};

		vector<SocketStringOffsets> socketStringOffsets;
		String codeRevision;
	};

	void appendJsonFieldToBuffer(std::string &buffer, const Json::Value &json,
		const char *key, InitializationLog::String &str, bool required = true) const
	{
		StaticString value;
		if (required) {
			value = getJsonStaticStringField(json, key);
		} else {
			value = getJsonStaticStringField(json, Json::StaticString(key),
				StaticString());
		}
		str.offset = buffer.size();
		str.size   = value.size();
		buffer.append(value.data(), value.size());
		buffer.append(1, '\0');
	}

	void initializeSocketsAndStringFields(const SpawningKit::Result &result) {
		Json::Value doc, sockets(Json::arrayValue);
		vector<SpawningKit::Result::Socket>::const_iterator it, end = result.sockets.end();

		for (it = result.sockets.begin(); it != end; it++) {
			sockets.append(it->inspectAsJson());
		}

		doc["sockets"] = sockets;
		initializeSocketsAndStringFields(doc);
	}

	void initializeSocketsAndStringFields(const Json::Value &json) {
		InitializationLog log;
		string buffer;


		// Step 1: append strings to temporary buffer and take note of their
		// offsets within the temporary buffer.

		Json::Value sockets = getJsonField(json, "sockets");
		// The const_cast here works around a jsoncpp bug.
		Json::Value::const_iterator it = const_cast<const Json::Value &>(sockets).begin();
		Json::Value::const_iterator end = const_cast<const Json::Value &>(sockets).end();
		buffer.reserve(1024);

		for (it = sockets.begin(); it != end; it++) {
			const Json::Value &socket = *it;
			InitializationLog::SocketStringOffsets offsets;

			appendJsonFieldToBuffer(buffer, socket, "address", offsets.address);
			appendJsonFieldToBuffer(buffer, socket, "protocol", offsets.protocol);
			appendJsonFieldToBuffer(buffer, socket, "description", offsets.description,
				false);

			log.socketStringOffsets.push_back(offsets);
		}

		if (json.isMember("code_revision")) {
			appendJsonFieldToBuffer(buffer, json, "code_revision", log.codeRevision);
		}


		// Step 2: allocate the real buffer.

		this->stringBuffer = DynamicBuffer(buffer.size());
		memcpy(this->stringBuffer.data, buffer.data(), buffer.size());


		// Step 3: initialize the string fields and point them to
		// addresses within the real buffer.

		unsigned int i;
		const char *base = this->stringBuffer.data;

		it = const_cast<const Json::Value &>(sockets).begin();
		for (i = 0; it != end; it++, i++) {
			const Json::Value &socket = *it;
			this->sockets.add(
				info.pid,
				StaticString(base + log.socketStringOffsets[i].address.offset,
					log.socketStringOffsets[i].address.size),
				StaticString(base + log.socketStringOffsets[i].protocol.offset,
					log.socketStringOffsets[i].protocol.size),
				StaticString(base + log.socketStringOffsets[i].description.offset,
					log.socketStringOffsets[i].description.size),
				getJsonIntField(socket, "concurrency"),
				getJsonBoolField(socket, "accept_http_requests")
			);
		}

		if (json.isMember("code_revision")) {
			codeRevision = StaticString(base + log.codeRevision.offset,
				log.codeRevision.size);
		}
	}

	void indexSocketsAcceptingHttpRequests() {
		SocketList::iterator it;

		concurrency = 0;
		memset(socketsAcceptingHttpRequests, 0, sizeof(socketsAcceptingHttpRequests));

		for (it = sockets.begin(); it != sockets.end(); it++) {
			Socket *socket = &(*it);
			if (!socket->acceptHttpRequests) {
				continue;
			}
			if (socketsAcceptingHttpRequestsCount == MAX_SOCKETS_ACCEPTING_HTTP_REQUESTS) {
				throw RuntimeException("The process has too many sockets that accept HTTP requests. "
					"A maximum of " + toString(MAX_SOCKETS_ACCEPTING_HTTP_REQUESTS) + " is allowed");
			}

			socketsAcceptingHttpRequests[socketsAcceptingHttpRequestsCount] = socket;
			socketsAcceptingHttpRequestsCount++;

			if (concurrency >= 0) {
				if (socket->concurrency < 0) {
					// If one of the sockets has a concurrency of
					// < 0 (unknown) then we mark this entire Process
					// as having a concurrency of -1 (unknown).
					concurrency = -1;
				} else if (socket->concurrency == 0) {
					// If one of the sockets has a concurrency of
					// 0 (unlimited) then we mark this entire Process
					// as having a concurrency of 0.
					concurrency = -999;
				} else {
					concurrency += socket->concurrency;
				}
			}
		}

		if (concurrency == -999) {
			concurrency = 0;
		}
	}

	void destroySelf() const {
		Context *context = getContext();
		Process *storagePointer = const_cast<Process *>(this);
		this->~Process();

		LockGuard l(context->memoryManagementSyncher);
		// Use `storagePointer` because using `this` after calling the destructor
		// is undefined behavior.
		context->processObjectPool.free(storagePointer);
	}


	/****** Miscellaneous ******/

	static bool isZombie(pid_t pid) {
		string filename = "/proc/" + toString(pid) + "/status";
		FILE *f = fopen(filename.c_str(), "r");
		if (f == NULL) {
			// Don't know.
			return false;
		}

		bool result = false;
		while (!feof(f)) {
			char buf[512];
			const char *line;

			line = fgets(buf, sizeof(buf), f);
			if (line == NULL) {
				break;
			}
			if (strcmp(line, "State:	Z (zombie)\n") == 0) {
				// Is a zombie.
				result = true;
				break;
			}
		}
		fclose(f);
		return result;
	}

	string getAppGroupName(const BasicGroupInfo *info) const;
	string getAppLogFile(const BasicGroupInfo *info) const;

public:
	/*************************************************************
	 * Information used by Pool. Do not write to these from
	 * outside the Pool. If you read these make sure the Pool
	 * isn't concurrently modifying.
	 *************************************************************/

	/** Last time when a session was opened for this Process. */
	unsigned long long lastUsed;
    /** Which generation of app processes this one belongs to,
        inherited from the app group, incremented when a restart
		is initiated*/
	const unsigned int generation;
	/** Number of sessions currently open.
	 * @invariant session >= 0
	 */
	int sessions;
	/** Number of sessions opened so far. */
	unsigned int processed;
	/** Do not access directly, always use `isAlive()`/`isDead()`/`getLifeStatus()` or
	 * through `lifetimeSyncher`. */
	enum LifeStatus {
		/** Up and operational. */
		ALIVE,
		/** This process has been detached, and the detached processes checker has
		 * verified that there are no active sessions left and has told the process
		 * to shut down. In this state we're supposed to wait until the process
		 * has actually shutdown, after which cleanup() must be called. */
		SHUTDOWN_TRIGGERED,
		/**
		 * The process has exited and cleanup() has been called. In this state,
		 * this object is no longer usable.
		 */
		DEAD
	} lifeStatus;
	enum EnabledStatus {
		/** Up and operational. */
		ENABLED,
		/** Process is being disabled. The containing Group is waiting for
		 * all sessions on this Process to finish. It may in some corner
		 * cases still be selected for processing requests.
		 */
		DISABLING,
		/** Process is fully disabled and should not be handling any
		 * requests. It *may* still handle some requests, e.g. by
		 * the Out-of-Band-Work trigger.
		 */
		DISABLED,
		/**
		 * Process has been detached. It will be removed from the Group
		 * as soon we have detected that the OS process has exited. Detached
		 * processes are allowed to finish their requests, but are not
		 * eligible for new requests.
		 */
		DETACHED
	} enabled;
	enum OobwStatus {
		/** Process is not using out-of-band work. */
		OOBW_NOT_ACTIVE,
		/** The process has requested out-of-band work. At some point, the code
		 * will see this and set the status to OOBW_IN_PROGRESS. */
		OOBW_REQUESTED,
		/** An out-of-band work is in progress. We need to wait until all
		 * sessions have ended and the process has been disabled before the
		 * out-of-band work can be performed. */
		OOBW_IN_PROGRESS,
	} oobwStatus;
	/** Caches whether or not the OS process still exists. */
	mutable bool m_osProcessExists: 1;
	bool longRunningConnectionsAborted: 1;
	/** Time at which shutdown began. */
	time_t shutdownStartTime;
	/** Collected by Pool::collectAnalytics(). */
	ProcessMetrics metrics;

	Process(const BasicGroupInfo *groupInfo, const unsigned int gen, const Json::Value &args)
		: info(this, groupInfo, args),
		  socketsAcceptingHttpRequestsCount(0),
		  spawnerCreationTime(getJsonUint64Field(args, "spawner_creation_time")),
		  spawnStartTime(getJsonUint64Field(args, "spawn_start_time")),
		  spawnEndTime(SystemTime::getUsec()),
		  type(args["type"] == "dummy" ? SpawningKit::Result::DUMMY : SpawningKit::Result::UNKNOWN),
		  requiresShutdown(false),
		  refcount(1),
		  index(-1),
		  lastUsed(spawnEndTime),
		  generation(gen),
		  sessions(0),
		  processed(0),
		  lifeStatus(ALIVE),
		  enabled(ENABLED),
		  oobwStatus(OOBW_NOT_ACTIVE),
		  m_osProcessExists(true),
		  longRunningConnectionsAborted(false),
		  shutdownStartTime(0)
	{
		initializeSocketsAndStringFields(args);
		indexSocketsAcceptingHttpRequests();
	}

	Process(const BasicGroupInfo *groupInfo, const unsigned int gen, const SpawningKit::Result &skResult,
		const Json::Value &args)
		: info(this, groupInfo, skResult),
		  socketsAcceptingHttpRequestsCount(0),
		  spawnerCreationTime(getJsonUint64Field(args, "spawner_creation_time")),
		  spawnStartTime(skResult.spawnStartTime),
		  spawnEndTime(skResult.spawnEndTime),
		  type(skResult.type),
		  requiresShutdown(false),
		  refcount(1),
		  index(-1),
		  lastUsed(spawnEndTime),
		  generation(gen),
		  sessions(0),
		  processed(0),
		  lifeStatus(ALIVE),
		  enabled(ENABLED),
		  oobwStatus(OOBW_NOT_ACTIVE),
		  m_osProcessExists(true),
		  longRunningConnectionsAborted(false),
		  shutdownStartTime(0)
	{
		initializeSocketsAndStringFields(skResult);
		indexSocketsAcceptingHttpRequests();

		inputPipe = skResult.stdinFd;
		outputPipe = skResult.stdoutAndErrFd;

		if (outputPipe != -1) {
			SpawningKit::PipeWatcherPtr watcher = boost::make_shared<SpawningKit::PipeWatcher>(
				outputPipe, "output", getAppGroupName(groupInfo),
				getAppLogFile(groupInfo), skResult.pid);
			if (!args["log_file"].isNull()) {
				watcher->setLogFile(args["log_file"].asString());
			}
			watcher->initialize();
			watcher->start();
		}
	}

	~Process() {
		if (OXT_UNLIKELY(requiresShutdown && !isDead())) {
			P_BUG("You must call Process::triggerShutdown() and Process::cleanup() before actually "
				"destroying the Process object.");
		}
	}

	void initializeStickySessionId(unsigned int value) {
		info.stickySessionId = value;
	}

	void forceMaxConcurrency(int value) {
		assert(value >= 0);
		concurrency = value;
		for (unsigned i = 0; i < socketsAcceptingHttpRequestsCount; i++) {
			socketsAcceptingHttpRequests[i]->concurrency = concurrency;
		}
	}

	void shutdownNotRequired() {
		requiresShutdown = false;
	}


	/****** Memory and life time management ******/

	void ref() const {
		refcount.fetch_add(1, boost::memory_order_relaxed);
	}

	void unref() const {
		if (refcount.fetch_sub(1, boost::memory_order_release) == 1) {
			boost::atomic_thread_fence(boost::memory_order_acquire);
			destroySelf();
		}
	}

	ProcessPtr shared_from_this() {
		return ProcessPtr(this);
	}

	static void forceTriggerShutdownAndCleanup(ProcessPtr process) {
		if (process != NULL) {
			process->triggerShutdown();
			// Pretend like the OS process has exited so
			// that the canCleanup() precondition is true.
			process->m_osProcessExists = false;
			process->cleanup();
		}
	}

	// Thread-safe.
	bool isAlive() const {
		oxt::spin_lock::scoped_lock lock(lifetimeSyncher);
		return lifeStatus == ALIVE;
	}

	// Thread-safe.
	bool hasTriggeredShutdown() const {
		oxt::spin_lock::scoped_lock lock(lifetimeSyncher);
		return lifeStatus == SHUTDOWN_TRIGGERED;
	}

	// Thread-safe.
	bool isDead() const {
		oxt::spin_lock::scoped_lock lock(lifetimeSyncher);
		return lifeStatus == DEAD;
	}

	// Thread-safe.
	LifeStatus getLifeStatus() const {
		oxt::spin_lock::scoped_lock lock(lifetimeSyncher);
		return lifeStatus;
	}

	bool canTriggerShutdown() const {
		return getLifeStatus() == ALIVE && sessions == 0;
	}

	void triggerShutdown() {
		assert(canTriggerShutdown());
		{
			time_t now = SystemTime::get();
			oxt::spin_lock::scoped_lock lock(lifetimeSyncher);
			assert(lifeStatus == ALIVE);
			lifeStatus = SHUTDOWN_TRIGGERED;
			shutdownStartTime = now;
		}
		if (inputPipe != -1) {
			inputPipe.close();
		}
		if (type == SpawningKit::Result::GENERIC) {
			syscalls::kill(getPid(), SIGTERM);
		}
	}

	bool shutdownTimeoutExpired() const {
		return SystemTime::get() >= shutdownStartTime + PROCESS_SHUTDOWN_TIMEOUT;
	}

	bool canCleanup() const {
		return getLifeStatus() == SHUTDOWN_TRIGGERED && !osProcessExists();
	}

	void cleanup() {
		assert(canCleanup());

		P_TRACE(2, "Cleaning up process " << inspect());
		if (type != SpawningKit::Result::DUMMY) {
			SocketList::iterator it, end = sockets.end();
			for (it = sockets.begin(); it != end; it++) {
				if (getSocketAddressType(it->address) == SAT_UNIX) {
					string filename = parseUnixSocketAddress(it->address);
					syscalls::unlink(filename.c_str());
				}
				it->closeAllConnections();
			}
		}

		oxt::spin_lock::scoped_lock lock(lifetimeSyncher);
		lifeStatus = DEAD;
	}


	/****** Basic information queries ******/

	OXT_FORCE_INLINE
	Context *getContext() const {
		return info.groupInfo->context;
	}

	Group *getGroup() const {
		return info.groupInfo->group;
	}

	StaticString getGroupName() const {
		return info.groupInfo->name;
	}

	const ApiKey &getApiKey() const {
		return info.groupInfo->apiKey;
	}

	const BasicProcessInfo &getInfo() const {
		return info;
	}

	pid_t getPid() const {
		return info.pid;
	}

	StaticString getGupid() const {
		return StaticString(info.gupid, info.gupidSize);
	}

	unsigned int getStickySessionId() const {
		return info.stickySessionId;
	}

	unsigned long long getSpawnerCreationTime() const {
		return spawnerCreationTime;
	}

	bool isDummy() const {
		return type == SpawningKit::Result::DUMMY;
	}


	/****** Miscellaneous ******/

	unsigned int getIndex() const {
		return index;
	}

	void setIndex(unsigned int i) {
		index = i;
	}

	const SocketList &getSockets() const {
		return sockets;
	}

	Socket *findSocketsAcceptingHttpRequestsAndWithLowestBusyness() const {
		if (OXT_UNLIKELY(socketsAcceptingHttpRequestsCount == 0)) {
			return NULL;
		} else if (socketsAcceptingHttpRequestsCount == 1) {
			return socketsAcceptingHttpRequests[0];
		} else {
			int leastBusySocketIndex = 0;
			int lowestBusyness = socketsAcceptingHttpRequests[0]->busyness();

			for (unsigned i = 1; i < socketsAcceptingHttpRequestsCount; i++) {
				if (socketsAcceptingHttpRequests[i]->busyness() < lowestBusyness) {
					leastBusySocketIndex = i;
					lowestBusyness = socketsAcceptingHttpRequests[i]->busyness();
				}
			}

			return socketsAcceptingHttpRequests[leastBusySocketIndex];
		}
	}

	/** Checks whether the OS process exists.
	 * Once it has been detected that it doesn't, that event is remembered
	 * so that we don't accidentally ping any new processes that have the
	 * same PID.
	 */
	bool osProcessExists() const {
		if (type != SpawningKit::Result::DUMMY && m_osProcessExists) {
			if (syscalls::kill(getPid(), 0) == 0) {
				/* On some environments, e.g. Heroku, the init process does
				 * not properly reap adopted zombie processes, which can interfere
				 * with our process existance check. To work around this, we
				 * explicitly check whether or not the process has become a zombie.
				 */
				m_osProcessExists = !isZombie(getPid());
			} else {
				m_osProcessExists = errno != ESRCH;
			}
			return m_osProcessExists;
		} else {
			return false;
		}
	}

	/** Kill the OS process with the given signal. */
	int kill(int signo) {
		if (osProcessExists()) {
			return syscalls::kill(getPid(), signo);
		} else {
			return 0;
		}
	}

	int busyness() const {
		/* Different processes within a Group may have different
		 * 'concurrency' values. We want:
		 * - the process with the smallest busyness to be be picked for routing.
		 * - to give processes with concurrency == 0 or -1 more priority (in general)
		 *   over processes with concurrency > 0.
		 * Therefore, in case of processes 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 processes with concurrency > 0 is usually higher than that of processes
		 * with concurrency == 0 or -1.
		 */
		if (concurrency <= 0) {
			return sessions;
		} else {
			return (int) (((long long) sessions * INT_MAX) / (double) concurrency);
		}
	}

	/**
	 * Whether we've reached the maximum number of concurrent sessions for this
	 * process.
	 */
	bool isTotallyBusy() const {
		return concurrency > 0 && sessions >= concurrency;
	}

	/**
	 * Whether a get() request can be routed to this process, assuming that
	 * the sticky session ID (if any) matches. This is only not the case
	 * if this process is totally busy.
	 */
	bool canBeRoutedTo() const {
		return !isTotallyBusy();
	}

	/**
	 * Create a new communication session with this process. This will connect to one
	 * of the session sockets or reuse an existing connection. See Session for
	 * more information about sessions.
	 *
	 * If you know the current time (in microseconds), pass it to `now`, which
	 * prevents this function from having to query the time.
	 *
	 * You SHOULD call sessionClosed() when one's done with the session.
	 * Failure to do so will mess up internal statistics but will otherwise
	 * not result in any harmful behavior.
	 */
	SessionPtr newSession(unsigned long long now = 0) {
		Socket *socket = findSocketsAcceptingHttpRequestsAndWithLowestBusyness();
		if (socket->isTotallyBusy()) {
			return SessionPtr();
		} else {
			socket->sessions++;
			this->sessions++;
			if (now != 0) {
				lastUsed = now;
			} else {
				lastUsed = SystemTime::getUsec();
			}
			return createSessionObject(socket);
		}
	}

	SessionPtr createSessionObject(Socket *socket) {
		struct Guard {
			Context *context;
			Session *session;

			Guard(Context *c, Session *s)
				: context(c),
				  session(s)
				{ }

			~Guard() {
				if (session != NULL) {
					context->sessionObjectPool.free(session);
				}
			}

			void clear() {
				session = NULL;
			}
		};

		Context *context = getContext();
		LockGuard l(context->memoryManagementSyncher);
		Session *session = context->sessionObjectPool.malloc();
		Guard guard(context, session);
		session = new (session) Session(context, &info, socket);
		guard.clear();
		return SessionPtr(session, false);
	}

	void sessionClosed(Session *session) {
		Socket *socket = session->getSocket();

		assert(socket->sessions > 0);
		assert(sessions > 0);

		socket->sessions--;
		this->sessions--;
		processed++;
		assert(!isTotallyBusy());
	}

	/**
	 * Returns the uptime of this process so far, as a string.
	 */
	string uptime() const {
		return distanceOfTimeInWords(spawnEndTime / 1000000);
	}

	string inspect() const {
		assert(getLifeStatus() != DEAD);
		stringstream result;
		result << "(pid=" << getPid() << ", group=" << getGroupName() << ")";
		return result.str();
	}

	template<typename Stream>
	void inspectXml(Stream &stream, bool includeSockets = true) const {
		stream << "<pid>" << getPid() << "</pid>";
		stream << "<sticky_session_id>" << getStickySessionId() << "</sticky_session_id>";
		stream << "<gupid>" << getGupid() << "</gupid>";
		stream << "<concurrency>" << concurrency << "</concurrency>";
		stream << "<sessions>" << sessions << "</sessions>";
		stream << "<busyness>" << busyness() << "</busyness>";
		stream << "<processed>" << processed << "</processed>";
		stream << "<spawner_creation_time>" << spawnerCreationTime << "</spawner_creation_time>";
		stream << "<spawn_start_time>" << spawnStartTime << "</spawn_start_time>";
		stream << "<spawn_end_time>" << spawnEndTime << "</spawn_end_time>";
		stream << "<last_used>" << lastUsed << "</last_used>";
		stream << "<last_used_desc>" << distanceOfTimeInWords(lastUsed / 1000000).c_str() << " ago</last_used_desc>";
		stream << "<uptime>" << uptime() << "</uptime>";
		if (!codeRevision.empty()) {
			stream << "<code_revision>" << escapeForXml(codeRevision) << "</code_revision>";
		}
		switch (lifeStatus) {
		case ALIVE:
			stream << "<life_status>ALIVE</life_status>";
			break;
		case SHUTDOWN_TRIGGERED:
			stream << "<life_status>SHUTDOWN_TRIGGERED</life_status>";
			break;
		case DEAD:
			stream << "<life_status>DEAD</life_status>";
			break;
		default:
			P_BUG("Unknown 'lifeStatus' state " << (int) lifeStatus);
		}
		switch (enabled) {
		case ENABLED:
			stream << "<enabled>ENABLED</enabled>";
			break;
		case DISABLING:
			stream << "<enabled>DISABLING</enabled>";
			break;
		case DISABLED:
			stream << "<enabled>DISABLED</enabled>";
			break;
		case DETACHED:
			stream << "<enabled>DETACHED</enabled>";
			break;
		default:
			P_BUG("Unknown 'enabled' state " << (int) enabled);
		}
		if (metrics.isValid()) {
			stream << "<has_metrics>true</has_metrics>";
			stream << "<cpu>" << (int) metrics.cpu << "</cpu>";
			stream << "<rss>" << metrics.rss << "</rss>";
			stream << "<pss>" << metrics.pss << "</pss>";
			stream << "<private_dirty>" << metrics.privateDirty << "</private_dirty>";
			stream << "<swap>" << metrics.swap << "</swap>";
			stream << "<real_memory>" << metrics.realMemory() << "</real_memory>";
			stream << "<vmsize>" << metrics.vmsize << "</vmsize>";
			stream << "<process_group_id>" << metrics.processGroupId << "</process_group_id>";
			stream << "<command>" << escapeForXml(metrics.command) << "</command>";
		}
		if (includeSockets) {
			SocketList::const_iterator it;

			stream << "<sockets>";
			for (it = sockets.begin(); it != sockets.end(); it++) {
				const Socket &socket = *it;
				stream << "<socket>";
				stream << "<address>" << escapeForXml(socket.address) << "</address>";
				stream << "<protocol>" << escapeForXml(socket.protocol) << "</protocol>";
				if (!socket.description.empty()) {
					stream << "<description>" << escapeForXml(socket.description) << "</description>";
				}
				stream << "<concurrency>" << socket.concurrency << "</concurrency>";
				stream << "<accept_http_requests>" << socket.acceptHttpRequests << "</accept_http_requests>";
				stream << "<sessions>" << socket.sessions << "</sessions>";
				stream << "</socket>";
			}
			stream << "</sockets>";
		}
	}
};


inline void
intrusive_ptr_add_ref(const Process *process) {
	process->ref();
}

inline void
intrusive_ptr_release(const Process *process) {
	process->unref();
}


} // namespace ApplicationPool2
} // namespace Passenger

#endif /* _PASSENGER_APPLICATION_POOL2_PROCESS_H_ */
