Permalink
Browse files

Only tell processes to shut down after all sessions have closed.

  • Loading branch information...
1 parent caf5698 commit 2f6be3fdd1eb6aec27f6cf49d7fd30897553878f @FooBarWidget FooBarWidget committed Apr 24, 2013
View
68 ext/common/ApplicationPool2/Group.h
@@ -251,7 +251,7 @@ class Group: public enable_shared_from_this<Group> {
}
foreach (const ProcessPtr &process, detachedProcesses) {
- assert(process->getLifeStatus() == Process::SHUTTING_DOWN);
+ assert(process->enabled == Process::DETACHED);
assert(process->pqHandle == NULL);
}
#endif
@@ -323,24 +323,26 @@ class Group: public enable_shared_from_this<Group> {
void removeProcessFromList(const ProcessPtr &process, ProcessList &source) {
ProcessPtr p = process; // Keep an extra reference count just in case.
source.erase(process->it);
- if (process->isAlive()) {
- switch (process->enabled) {
- case Process::ENABLED:
- enabledCount--;
- pqueue.erase(process->pqHandle);
- process->pqHandle = NULL;
- break;
- case Process::DISABLING:
- disablingCount--;
- break;
- case Process::DISABLED:
- disabledCount--;
- break;
- default:
- P_BUG("Unknown 'enabled' state " << (int) process->enabled);
- }
- } else {
+ switch (process->enabled) {
+ case Process::ENABLED:
+ assert(&source == &enabledProcesses);
+ enabledCount--;
+ pqueue.erase(process->pqHandle);
+ process->pqHandle = NULL;
+ break;
+ case Process::DISABLING:
+ assert(&source == &disablingProcesses);
+ disablingCount--;
+ break;
+ case Process::DISABLED:
+ assert(&source == &disabledProcesses);
+ disabledCount--;
+ break;
+ case Process::DETACHED:
assert(&source == &detachedProcesses);
+ break;
+ default:
+ P_BUG("Unknown 'enabled' state " << (int) process->enabled);
}
}
@@ -366,6 +368,7 @@ class Group: public enable_shared_from_this<Group> {
disabledCount++;
} else if (&destination == &detachedProcesses) {
assert(process->isAlive());
+ process->enabled = Process::DETACHED;
} else {
P_BUG("Unknown destination list");
}
@@ -485,14 +488,6 @@ class Group: public enable_shared_from_this<Group> {
}
}
- void shutdownAndRemoveProcess(const ProcessPtr &process) {
- TRACE_POINT();
- const ProcessPtr p = process;
- assert(process->canBeShutDown());
- removeProcessFromList(process, detachedProcesses);
- process->shutdown();
- }
-
bool shutdownCanFinish() const {
return getLifeStatus() == SHUTTING_DOWN
&& enabledCount == 0
@@ -600,10 +595,10 @@ class Group: public enable_shared_from_this<Group> {
/**
* When a process is detached, it is stored here until we've confirmed
- * that it can be shut down.
+ * that the OS process has exited.
*
* for all process in detachedProcesses:
- * process.getLifeStatus() == Process::SHUTTING_DOWN
+ * process.enabled == Process::DETACHED
* process.pqHandle == NULL
*/
ProcessList detachedProcesses;
@@ -663,7 +658,7 @@ class Group: public enable_shared_from_this<Group> {
void shutdown(const Callback &callback, vector<Callback> &postLockActions) {
assert(isAlive());
- P_DEBUG("Shutting down group " << name);
+ P_DEBUG("Begin shutting down group " << name);
shutdownCallback = callback;
detachAll(postLockActions);
startCheckingDetachedProcesses(true);
@@ -676,11 +671,6 @@ class Group: public enable_shared_from_this<Group> {
lock_guard<boost::mutex> l(lifetimeSyncher);
lifeStatus = SHUTTING_DOWN;
}
- if (shutdownCanFinish()) {
- finishShutdown(postLockActions);
- } else {
- P_DEBUG("Shutdown finalization of group " << name << " has been deferred");
- }
}
SessionPtr get(const Options &newOptions, const GetCallback &callback) {
@@ -863,12 +853,7 @@ class Group: public enable_shared_from_this<Group> {
}
addProcessToList(process, detachedProcesses);
- process->setShuttingDown();
- if (process->canBeShutDown()) {
- shutdownAndRemoveProcess(process);
- } else {
- startCheckingDetachedProcesses(false);
- }
+ startCheckingDetachedProcesses(false);
}
/**
@@ -882,15 +867,12 @@ class Group: public enable_shared_from_this<Group> {
foreach (ProcessPtr process, enabledProcesses) {
addProcessToList(process, detachedProcesses);
process->pqHandle = NULL;
- process->setShuttingDown();
}
foreach (ProcessPtr process, disablingProcesses) {
addProcessToList(process, detachedProcesses);
- process->setShuttingDown();
}
foreach (ProcessPtr process, disabledProcesses) {
addProcessToList(process, detachedProcesses);
- process->setShuttingDown();
}
enabledProcesses.clear();
View
83 ext/common/ApplicationPool2/Implementation.cpp
@@ -370,7 +370,7 @@ Group::onSessionInitiateFailure(const ProcessPtr &process, Session *session) {
// Standard resource management boilerplate stuff...
PoolPtr pool = getPool();
unique_lock<boost::mutex> lock(pool->syncher);
- assert(!process->isShutDown());
+ assert(process->isAlive());
assert(isAlive() || getLifeStatus() == SHUTTING_DOWN);
UPDATE_TRACE_POINT();
@@ -390,7 +390,7 @@ Group::onSessionClose(const ProcessPtr &process, Session *session) {
// Standard resource management boilerplate stuff...
PoolPtr pool = getPool();
unique_lock<boost::mutex> lock(pool->syncher);
- assert(!process->isShutDown());
+ assert(process->isAlive());
assert(isAlive() || getLifeStatus() == SHUTTING_DOWN);
P_TRACE(2, "Session closed for process " << process->inspect());
@@ -399,9 +399,11 @@ Group::onSessionClose(const ProcessPtr &process, Session *session) {
/* Update statistics. */
process->sessionClosed(session);
- Process::LifeStatus lifeStatus = process->getLifeStatus();
- assert(process->enabled == Process::ENABLED || process->enabled == Process::DISABLING);
- if (process->enabled == Process::ENABLED && lifeStatus == Process::ALIVE) {
+ assert(process->getLifeStatus() == Process::ALIVE);
+ assert(process->enabled == Process::ENABLED
+ || process->enabled == Process::DISABLING
+ || process->enabled == Process::DETACHED);
+ if (process->enabled == Process::ENABLED) {
pqueue.decrease(process->pqHandle, process->utilization());
}
@@ -410,16 +412,6 @@ Group::onSessionClose(const ProcessPtr &process, Session *session) {
*/
assert(!process->atFullUtilization());
- if (lifeStatus == Process::SHUTTING_DOWN) {
- UPDATE_TRACE_POINT();
- if (process->canBeShutDown()) {
- shutdownAndRemoveProcess(process);
- }
- verifyInvariants();
- verifyExpensiveInvariants();
- return;
- }
-
bool detachingBecauseOfMaxRequests = false;
bool detachingBecauseCapacityNeeded = false;
bool shouldDetach =
@@ -521,7 +513,10 @@ Group::lockAndAsyncOOBWRequestIfNeeded(const ProcessPtr &process, DisableResult
void
Group::asyncOOBWRequestIfNeeded(const ProcessPtr &process) {
- if (process->oobwStatus != Process::OOBW_REQUESTED || !process->isAlive()) {
+ if (process->oobwStatus != Process::OOBW_REQUESTED
+ || process->enabled == Process::DETACHED
+ || !process->isAlive())
+ {
return;
}
if (process->enabled == Process::ENABLED) {
@@ -715,7 +710,7 @@ Group::spawnThreadRealMain(const SpawnerPtr &spawner, const Options &options, un
}
UPDATE_TRACE_POINT();
- ScopeGuard guard(boost::bind(Process::maybeShutdown, process));
+ ScopeGuard guard(boost::bind(Process::forceTriggerShutdownAndCleanup, process));
unique_lock<boost::mutex> lock(pool->syncher);
if (!isAlive()) {
@@ -897,9 +892,14 @@ Group::finalizeRestart(GroupPtr self, Options options, SpawnerFactoryPtr spawner
}
}
+/**
+ * The `immediately` parameter only has effect if the detached processes checker
+ * thread is active. It means that, if the thread is currently sleeping, it should
+ * wake up immediately and perform work.
+ */
void
Group::startCheckingDetachedProcesses(bool immediately) {
- if (!detachedProcessesCheckerActive && !detachedProcesses.empty()) {
+ if (!detachedProcessesCheckerActive) {
P_DEBUG("Starting detached processes checker");
getPool()->nonInterruptableThreads.create_thread(
boost::bind(&Group::detachedProcessesCheckerMain, this, shared_from_this()),
@@ -916,32 +916,49 @@ void
Group::detachedProcessesCheckerMain(GroupPtr self) {
TRACE_POINT();
PoolPtr pool = getPool();
+ unique_lock<boost::mutex> lock(pool->syncher);
- while (!this_thread::interruption_requested()) {
- unique_lock<boost::mutex> lock(pool->syncher);
+ while (true) {
assert(detachedProcessesCheckerActive);
- if (getLifeStatus() == SHUT_DOWN) {
+ if (getLifeStatus() == SHUT_DOWN || this_thread::interruption_requested()) {
+ UPDATE_TRACE_POINT();
P_DEBUG("Stopping detached processes checker");
detachedProcessesCheckerActive = false;
break;
}
UPDATE_TRACE_POINT();
if (!detachedProcesses.empty()) {
- P_TRACE(2, "Checking whether any detached processes have exited...");
+ P_TRACE(2, "Checking whether any of the " << detachedProcesses.size() <<
+ " detached processes have exited...");
ProcessList::iterator it = detachedProcesses.begin();
ProcessList::iterator end = detachedProcesses.end();
while (it != end) {
const ProcessPtr process = *it;
- if (process->canBeShutDown()) {
- it++;
- P_DEBUG("Detached process " << process->inspect() << " has exited.");
- shutdownAndRemoveProcess(process);
- } else {
- P_DEBUG("Detached process " << process->inspect() << " not yet exited. "
- "sessions = " << process->sessions);
+ switch (process->getLifeStatus()) {
+ case Process::ALIVE:
+ if (process->canTriggerShutdown()) {
+ P_DEBUG("Detached process " << process->inspect() <<
+ " has 0 active sessions now. Triggering shutdown.");
+ process->triggerShutdown();
+ assert(process->getLifeStatus() == Process::SHUTDOWN_TRIGGERED);
+ }
it++;
+ break;
+ case Process::SHUTDOWN_TRIGGERED:
+ if (process->canCleanup()) {
+ P_DEBUG("Detached process " << process->inspect() << " has shut down. Cleaning up associated resources.");
+ process->cleanup();
+ assert(process->getLifeStatus() == Process::DEAD);
+ it++;
+ removeProcessFromList(process, detachedProcesses);
+ } else {
+ it++;
+ }
+ break;
+ default:
+ P_BUG("Unknown 'lifeStatus' state " << (int) process->getLifeStatus());
}
}
}
@@ -970,6 +987,8 @@ Group::detachedProcessesCheckerMain(GroupPtr self) {
verifyExpensiveInvariants();
}
+ // Not all processes can be shut down yet. Sleep for a while unless
+ // someone wakes us up.
UPDATE_TRACE_POINT();
detachedProcessesCheckerCond.timed_wait(lock,
posix_time::milliseconds(10));
@@ -1013,13 +1032,13 @@ Group::generateSecret(const SuperGroupPtr &superGroup) {
SuperGroupPtr
Process::getSuperGroup() const {
- assert(getLifeStatus() != SHUT_DOWN);
+ assert(getLifeStatus() != DEAD);
return getGroup()->getSuperGroup();
}
string
Process::inspect() const {
- assert(getLifeStatus() != SHUT_DOWN);
+ assert(getLifeStatus() != DEAD);
stringstream result;
result << "(pid=" << pid;
GroupPtr group = getGroup();
@@ -1055,7 +1074,7 @@ Session::getGroup() const {
void
Session::requestOOBW() {
ProcessPtr process = getProcess();
- assert(!process->isShutDown());
+ assert(process->isAlive());
process->getGroup()->requestOOBW(process);
}
View
13 ext/common/ApplicationPool2/Pool.h
@@ -428,7 +428,7 @@ class Pool: public enable_shared_from_this<Pool> {
}
void inspectProcessList(const InspectOptions &options, stringstream &result,
- const ProcessList &processes) const
+ const Group *group, const ProcessList &processes) const
{
ProcessList::const_iterator p_it;
for (p_it = processes.begin(); p_it != processes.end(); p_it++) {
@@ -453,8 +453,7 @@ class Pool: public enable_shared_from_this<Pool> {
result << " Disabling..." << endl;
} else if (process->enabled == Process::DISABLED) {
result << " DISABLED" << endl;
- }
- if (process->getLifeStatus() == Process::SHUTTING_DOWN) {
+ } else if (process->enabled == Process::DETACHED) {
result << " Shutting down...";
}
@@ -1388,10 +1387,10 @@ class Pool: public enable_shared_from_this<Pool> {
result << " (spawning new process...)" << endl;
}
result << " Requests in queue: " << group->getWaitlist.size() << endl;
- inspectProcessList(options, result, group->enabledProcesses);
- inspectProcessList(options, result, group->disablingProcesses);
- inspectProcessList(options, result, group->disabledProcesses);
- inspectProcessList(options, result, group->detachedProcesses);
+ inspectProcessList(options, result, group, group->enabledProcesses);
+ inspectProcessList(options, result, group, group->disablingProcesses);
+ inspectProcessList(options, result, group, group->disabledProcesses);
+ inspectProcessList(options, result, group, group->detachedProcesses);
result << endl;
}
}
View
105 ext/common/ApplicationPool2/Process.h
@@ -128,7 +128,7 @@ class Process: public enable_shared_from_this<Process> {
private:
friend class Group;
- /** A mutex to protect access to `m_shutDown`. */
+ /** A mutex to protect access to `lifeStatus`. */
mutable boost::mutex lifetimeSyncher;
/** Group inside the Pool that this Process belongs to.
@@ -200,9 +200,10 @@ class Process: public enable_shared_from_this<Process> {
* the admin socket need not be closed, etc.
*/
bool dummy;
- /** Whether it is required that shutdown() must be called before destroying
- * this Process. Normally true, except for dummy Process objects created by
- * Pool::asyncGet() with options.noop == true.
+ /** 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;
@@ -224,19 +225,21 @@ class Process: public enable_shared_from_this<Process> {
int sessions;
/** Number of sessions opened so far. */
unsigned int processed;
- /** Do not access directly, always use `isAlive()`/`isShutDown()`/`getLifeStatus()` or
+ /** Do not access directly, always use `isAlive()`/`isDead()`/`getLifeStatus()` or
* through `lifetimeSyncher`. */
enum LifeStatus {
/** Up and operational. */
ALIVE,
- /** Being shut down. The containing Group has just detached this
- * Process and is now waiting for it to be shutdownable.
- */
- SHUTTING_DOWN,
+ /** 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 clean() must be called. */
+ SHUTDOWN_TRIGGERED,
/**
- * Shut down. Object no longer usable. No more sessions are active.
+ * The process has exited and clean() has been called. In this state,
+ * this object is no longer usable.
*/
- SHUT_DOWN
+ DEAD
} lifeStatus;
enum EnabledStatus {
/** Up and operational. */
@@ -250,7 +253,14 @@ class Process: public enable_shared_from_this<Process> {
* requests. It *may* still handle some requests, e.g. by
* the Out-of-Band-Work trigger.
*/
- DISABLED
+ 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. */
@@ -330,15 +340,19 @@ class Process: public enable_shared_from_this<Process> {
}
~Process() {
- if (OXT_UNLIKELY(!isShutDown() && requiresShutdown)) {
- P_BUG("You must call Process::shutdown() before actually "
+ if (OXT_UNLIKELY(!isDead() && requiresShutdown)) {
+ P_BUG("You must call Process::triggerShutdown() and Process::cleanup() before actually "
"destroying the Process object.");
}
}
- static void maybeShutdown(ProcessPtr process) {
+ static void forceTriggerShutdownAndCleanup(ProcessPtr process) {
if (process != NULL) {
- process->shutdown();
+ process->triggerShutdown();
+ // Pretend like the OS process has exited so
+ // that the canCleanup() precondition is true.
+ process->m_osProcessExists = false;
+ process->cleanup();
}
}
@@ -348,7 +362,7 @@ class Process: public enable_shared_from_this<Process> {
* @post result != NULL
*/
const GroupPtr getGroup() const {
- assert(!isShutDown());
+ assert(!isDead());
return group.lock();
}
@@ -359,7 +373,7 @@ class Process: public enable_shared_from_this<Process> {
/**
* Thread-safe.
- * @pre getLifeState() != SHUT_DOWN
+ * @pre getLifeState() != DEAD
* @post result != NULL
*/
SuperGroupPtr getSuperGroup() const;
@@ -371,9 +385,15 @@ class Process: public enable_shared_from_this<Process> {
}
// Thread-safe.
- bool isShutDown() const {
+ bool hasTriggeredShutdown() const {
lock_guard<boost::mutex> lock(lifetimeSyncher);
- return lifeStatus == SHUT_DOWN;
+ return lifeStatus == SHUTDOWN_TRIGGERED;
+ }
+
+ // Thread-safe.
+ bool isDead() const {
+ lock_guard<boost::mutex> lock(lifetimeSyncher);
+ return lifeStatus == DEAD;
}
// Thread-safe.
@@ -382,32 +402,30 @@ class Process: public enable_shared_from_this<Process> {
return lifeStatus;
}
- void setShuttingDown() {
+ bool canTriggerShutdown() const {
+ return getLifeStatus() == ALIVE && sessions == 0;
+ }
+
+ void triggerShutdown() {
+ assert(canTriggerShutdown());
{
lock_guard<boost::mutex> lock(lifetimeSyncher);
assert(lifeStatus == ALIVE);
- lifeStatus = SHUTTING_DOWN;
+ lifeStatus = SHUTDOWN_TRIGGERED;
}
if (!dummy) {
syscalls::shutdown(adminSocket, SHUT_WR);
}
}
- void shutdown() {
- LifeStatus ls = getLifeStatus();
- if (ls == SHUT_DOWN || !requiresShutdown) {
- // Some code have guards that call process->shutdown().
- // Returning instead of enforcing !isShutdown() makes things easier.
- return;
- }
-
- assert(sessions == 0);
+ bool canCleanup() const {
+ return getLifeStatus() == SHUTDOWN_TRIGGERED && !osProcessExists();
+ }
- if (ls == ALIVE) {
- setShuttingDown();
- }
+ void cleanup() {
+ assert(canCleanup());
- P_TRACE(2, "Shutting down Process object " << inspect());
+ P_TRACE(2, "Cleaning up process " << inspect());
if (!dummy) {
if (OXT_LIKELY(sockets != NULL)) {
SocketList::const_iterator it, end = sockets->end();
@@ -421,11 +439,7 @@ class Process: public enable_shared_from_this<Process> {
}
lock_guard<boost::mutex> lock(lifetimeSyncher);
- lifeStatus = SHUT_DOWN;
- }
-
- bool canBeShutDown() const {
- return sessions == 0 && !osProcessExists();
+ lifeStatus = DEAD;
}
/** Checks whether the OS process exists.
@@ -535,11 +549,11 @@ class Process: public enable_shared_from_this<Process> {
case ALIVE:
stream << "<life_status>ALIVE</life_status>";
break;
- case SHUTTING_DOWN:
- stream << "<life_status>SHUTTING_DOWN</life_status>";
+ case SHUTDOWN_TRIGGERED:
+ stream << "<life_status>SHUTDOWN_TRIGGERED</life_status>";
break;
- case SHUT_DOWN:
- stream << "<life_status>SHUT_DOWN</life_status>";
+ case DEAD:
+ stream << "<life_status>DEAD</life_status>";
break;
default:
P_BUG("Unknown 'lifeStatus' state " << (int) lifeStatus);
@@ -554,6 +568,9 @@ class Process: public enable_shared_from_this<Process> {
case DISABLED:
stream << "<enabled>DISABLED</enabled>";
break;
+ case DETACHED:
+ stream << "<enabled>DETACHED</enabled>";
+ break;
default:
P_BUG("Unknown 'enabled' state " << (int) enabled);
}
View
8 test/cxx/ApplicationPool2/DirectSpawnerTest.cpp
@@ -27,7 +27,6 @@ namespace tut {
~ApplicationPool2_DirectSpawnerTest() {
setLogLevel(DEFAULT_LOG_LEVEL);
unlink("stub/wsgi/passenger_wsgi.pyc");
- Process::maybeShutdown(process);
PipeWatcher::onData = PipeWatcher::DataCallback();
}
@@ -67,7 +66,8 @@ namespace tut {
spawner.getConfig()->forwardStderr = false;
try {
- spawner.spawn(options);
+ process = spawner.spawn(options);
+ process->requiresShutdown = false;
fail("Timeout expected");
} catch (const SpawnException &e) {
ensure_equals(e.getErrorKind(),
@@ -89,7 +89,8 @@ namespace tut {
spawner.getConfig()->forwardStderr = false;
try {
- spawner.spawn(options);
+ process = spawner.spawn(options);
+ process->requiresShutdown = false;
fail("SpawnException expected");
} catch (const SpawnException &e) {
ensure_equals(e.getErrorKind(),
@@ -108,6 +109,7 @@ namespace tut {
options.startupFile = "start.rb";
SpawnerPtr spawner = createSpawner(options);
process = spawner->spawn(options);
+ process->requiresShutdown = false;
ensure_equals(process->sockets->size(), 1u);
Connection conn = process->sockets->front().checkoutConnection();
View
77 test/cxx/ApplicationPool2/PoolTest.cpp
@@ -732,7 +732,7 @@ namespace tut {
SessionPtr session3 = pool->get(options, &ticket);
{
LockGuard l(pool->syncher);
- ensure("(4)", !session1->getProcess()->isAlive());
+ ensure("(4)", session1->getProcess()->enabled == Process::DETACHED);
ensure_equals("(6)", fooGroup->getWaitlist.size(), 1u);
ensure_equals("(7)", pool->getWaitlist.size(), 0u);
}
@@ -763,10 +763,17 @@ namespace tut {
ProcessPtr process = currentSession->getProcess();
pool->detachProcess(currentSession->getProcess());
- ensure(!process->isAlive());
+ {
+ LockGuard l(pool->syncher);
+ ensure(process->enabled == Process::DETACHED);
+ }
EVENTUALLY(5,
result = pool->getProcessCount() == 2;
);
+ currentSession.reset();
+ EVENTUALLY(5,
+ result = process->isDead();
+ );
}
TEST_METHOD(31) {
@@ -866,6 +873,72 @@ namespace tut {
ensure(!superGroup->garbageCollectable());
}
+ TEST_METHOD(34) {
+ // When detaching a process, it waits until all sessions have
+ // finished before telling the process to shut down.
+ Options options = createOptions();
+ options.spawnMethod = "direct";
+ options.minProcesses = 0;
+ SessionPtr session = pool->get(options, &ticket);
+ ProcessPtr process = session->getProcess();
+
+ ensure(pool->detachProcess(process));
+ {
+ LockGuard l(pool->syncher);
+ ensure_equals(process->enabled, Process::DETACHED);
+ }
+ SHOULD_NEVER_HAPPEN(100,
+ LockGuard l(pool->syncher);
+ result = !process->isAlive()
+ || !process->osProcessExists();
+ );
+
+ session.reset();
+ EVENTUALLY(1,
+ LockGuard l(pool->syncher);
+ result = process->enabled == Process::DETACHED
+ && !process->osProcessExists()
+ && process->isDead();
+ );
+ }
+
+ TEST_METHOD(35) {
+ // When detaching a process, it waits until the OS processes
+ // have exited before cleaning up the in-memory data structures.
+ Options options = createOptions();
+ options.spawnMethod = "direct";
+ options.minProcesses = 0;
+ ProcessPtr process = pool->get(options, &ticket)->getProcess();
+
+ ScopeGuard g(boost::bind(::kill, process->pid, SIGCONT));
+ kill(process->pid, SIGSTOP);
+
+ ensure(pool->detachProcess(process));
+ {
+ LockGuard l(pool->syncher);
+ ensure_equals(process->enabled, Process::DETACHED);
+ }
+ EVENTUALLY(1,
+ result = process->getLifeStatus() == Process::SHUTDOWN_TRIGGERED;
+ );
+
+ SHOULD_NEVER_HAPPEN(100,
+ LockGuard l(pool->syncher);
+ result = process->isDead()
+ || !process->osProcessExists();
+ );
+
+ kill(process->pid, SIGCONT);
+ g.clear();
+
+ EVENTUALLY(1,
+ LockGuard l(pool->syncher);
+ result = process->enabled == Process::DETACHED
+ && !process->osProcessExists()
+ && process->isDead();
+ );
+ }
+
/*********** Test disabling and enabling processes ***********/
View
19 test/cxx/ApplicationPool2/SmartSpawnerTest.cpp
@@ -32,7 +32,6 @@ namespace tut {
~ApplicationPool2_SmartSpawnerTest() {
setLogLevel(DEFAULT_LOG_LEVEL);
unlink("stub/wsgi/passenger_wsgi.pyc");
- Process::maybeShutdown(process);
PipeWatcher::onData = PipeWatcher::DataCallback();
}
@@ -79,15 +78,17 @@ namespace tut {
options.startCommand = "ruby\1" "start.rb";
options.startupFile = "start.rb";
shared_ptr<SmartSpawner> spawner = createSpawner(options);
- spawner->spawn(options)->shutdown();
+ process = spawner->spawn(options);
+ process->requiresShutdown = false;
kill(spawner->getPreloaderPid(), SIGTERM);
// Give it some time to exit.
usleep(300000);
// No exception at next spawn.
setLogLevel(-1);
- spawner->spawn(options)->shutdown();
+ process = spawner->spawn(options);
+ process->requiresShutdown = false;
}
TEST_METHOD(81) {
@@ -100,7 +101,8 @@ namespace tut {
setLogLevel(-1);
shared_ptr<SmartSpawner> spawner = createSpawner(options, true);
try {
- spawner->spawn(options)->shutdown();
+ process = spawner->spawn(options);
+ process->requiresShutdown = false;
fail("SpawnException expected");
} catch (const SpawnException &) {
// Pass.
@@ -130,7 +132,8 @@ namespace tut {
spawner.getConfig()->forwardStderr = false;
try {
- spawner.spawn(options)->shutdown();
+ process = spawner.spawn(options);
+ process->requiresShutdown = false;
fail("SpawnException expected");
} catch (const SpawnException &e) {
ensure_equals(e.getErrorKind(),
@@ -161,7 +164,8 @@ namespace tut {
spawner.getConfig()->forwardStderr = false;
try {
- spawner.spawn(options)->shutdown();
+ process = spawner.spawn(options);
+ process->requiresShutdown = false;
fail("SpawnException expected");
} catch (const SpawnException &e) {
ensure_equals(e.getErrorKind(),
@@ -192,7 +196,8 @@ namespace tut {
spawner.getConfig()->forwardStderr = false;
try {
- spawner.spawn(options)->shutdown();
+ process = spawner.spawn(options);
+ process->requiresShutdown = false;
fail("SpawnException expected");
} catch (const SpawnException &e) {
ensure(containsSubstring(e["envvars"], "PASSENGER_FOO=foo\n"));
View
23 test/cxx/ApplicationPool2/SpawnerTestCases.cpp
@@ -22,6 +22,7 @@
#define RUN_USER_SWITCHING_TEST() \
process = spawner->spawn(options); \
+ process->requiresShutdown = false; \
BufferedIO io(FileDescriptor(open("/tmp/info.txt", O_RDONLY))); \
uid_t uid = (uid_t) atol(io.readLine().c_str()); \
gid_t gid = (gid_t) atol(io.readLine().c_str()); \
@@ -81,6 +82,7 @@
options.startupFile = "start.rb";
SpawnerPtr spawner = createSpawner(options);
process = spawner->spawn(options);
+ process->requiresShutdown = false;
ensure_equals(process->sockets->size(), 1u);
Connection conn = process->sockets->front().checkoutConnection();
@@ -98,7 +100,8 @@
options.startTimeout = 300;
SpawnerPtr spawner = createSpawner(options);
try {
- spawner->spawn(options);
+ process = spawner->spawn(options);
+ process->requiresShutdown = false;
fail("Timeout expected");
} catch (const SpawnException &e) {
ensure_equals(e.getErrorKind(),
@@ -115,7 +118,8 @@
options.startupFile = ".";
SpawnerPtr spawner = createSpawner(options);
try {
- spawner->spawn(options);
+ process = spawner->spawn(options);
+ process->requiresShutdown = false;
fail("Exception expected");
} catch (const SpawnException &e) {
ensure_equals(e.getErrorKind(),
@@ -132,7 +136,8 @@
options.startupFile = "start_error.pl";
SpawnerPtr spawner = createSpawner(options);
try {
- spawner->spawn(options);
+ process = spawner->spawn(options);
+ process->requiresShutdown = false;
fail("SpawnException expected");
} catch (const SpawnException &e) {
ensure_equals(e.getErrorKind(),
@@ -153,7 +158,8 @@
options.startTimeout = 300;
SpawnerPtr spawner = createSpawner(options);
try {
- spawner->spawn(options);
+ process = spawner->spawn(options);
+ process->requiresShutdown = false;
fail("Timeout expected");
} catch (const SpawnException &e) {
ensure_equals(e.getErrorKind(),
@@ -169,6 +175,7 @@
options.startupFile = "start.rb";
SpawnerPtr spawner = createSpawner(options);
process = spawner->spawn(options);
+ process->requiresShutdown = false;
ensure_equals(process->sockets->size(), 1u);
Connection conn = process->sockets->front().checkoutConnection();
@@ -187,6 +194,7 @@
options.environmentVariables.push_back(make_pair("PASSENGER_BAR", "bar"));
SpawnerPtr spawner = createSpawner(options);
process = spawner->spawn(options);
+ process->requiresShutdown = false;
ensure_equals(process->sockets->size(), 1u);
Connection conn = process->sockets->front().checkoutConnection();
@@ -206,7 +214,8 @@
options.environmentVariables.push_back(make_pair("PASSENGER_FOO", "foo"));
SpawnerPtr spawner = createSpawner(options);
try {
- spawner->spawn(options);
+ process = spawner->spawn(options);
+ process->requiresShutdown = false;
fail("Exception expected");
} catch (const SpawnException &e) {
ensure(containsSubstring(e["envvars"], "PASSENGER_FOO=foo\n"));
@@ -236,6 +245,7 @@
try {
process = spawner->spawn(options);
+ process->requiresShutdown = false;
fail("SpawnException expected");
} catch (const SpawnException &e) {
ensure("(1)", containsSubstring(e.getErrorPage(),
@@ -245,6 +255,7 @@
runShellCommand("chmod 700 tmp.check/a");
try {
process = spawner->spawn(options);
+ process->requiresShutdown = false;
fail("SpawnException expected");
} catch (const SpawnException &e) {
ensure("(2)", containsSubstring(e.getErrorPage(),
@@ -254,6 +265,7 @@
runShellCommand("chmod 700 tmp.check/a/b/c");
try {
process = spawner->spawn(options);
+ process->requiresShutdown = false;
fail("SpawnException expected");
} catch (const SpawnException &e) {
ensure("(3)", containsSubstring(e.getErrorPage(),
@@ -262,6 +274,7 @@
runShellCommand("chmod 700 tmp.check/a/b/c/d");
process = spawner->spawn(options); // Should not throw.
+ process->requiresShutdown = false;
}
}

0 comments on commit 2f6be3f

Please sign in to comment.