Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tag: release-2.2.6
Fetching contributors…

Cannot retrieve contributors at this time

600 lines (515 sloc) 20.667 kb
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cstring>
#include <cstdlib>
#include <cerrno>
#include <sys/types.h>
#include <signal.h>
#include <utime.h>
/**
* This file is used as a template to test the different ApplicationPool implementations.
* It is #included in StandardApplicationPoolTest.cpp and ApplicationServer_ApplicationPoolTest.cpp
*/
#ifdef USE_TEMPLATE
/** Create some stub request headers. */
static string createRequestHeaders(const char *uri = "/foo/new") {
string headers;
#define ADD_HEADER(name, value) \
headers.append(name); \
headers.append(1, '\0'); \
headers.append(value); \
headers.append(1, '\0')
ADD_HEADER("HTTP_HOST", "www.test.com");
ADD_HEADER("QUERY_STRING", "");
ADD_HEADER("REQUEST_URI", uri);
ADD_HEADER("REQUEST_METHOD", "GET");
ADD_HEADER("REMOTE_ADDR", "localhost");
ADD_HEADER("SCRIPT_NAME", "");
ADD_HEADER("PATH_INFO", uri);
return headers;
}
static Application::SessionPtr spawnRackApp(ApplicationPoolPtr pool, const char *appRoot) {
PoolOptions options;
options.appRoot = appRoot;
options.appType = "rack";
return pool->get(options);
}
static Application::SessionPtr spawnWsgiApp(ApplicationPoolPtr pool, const char *appRoot) {
PoolOptions options;
options.appRoot = appRoot;
options.appType = "wsgi";
return pool->get(options);
}
TEST_METHOD(1) {
// Calling ApplicationPool.get() once should return a valid Session.
Application::SessionPtr session(spawnRackApp(pool, "stub/rack"));
session->sendHeaders(createRequestHeaders());
session->shutdownWriter();
int reader = session->getStream();
string result(readAll(reader));
session->closeStream();
ensure(result.find("hello <b>world</b>") != string::npos);
}
TEST_METHOD(2) {
// Verify that the pool spawns a new app, and that
// after the session is closed, the app is kept around.
Application::SessionPtr session(spawnRackApp(pool, "stub/rack"));
ensure_equals("Before the session was closed, the app was busy", pool->getActive(), 1u);
ensure_equals("Before the session was closed, the app was in the pool", pool->getCount(), 1u);
session.reset();
ensure_equals("After the session is closed, the app is no longer busy", pool->getActive(), 0u);
ensure_equals("After the session is closed, the app is kept around", pool->getCount(), 1u);
}
TEST_METHOD(3) {
// If we call get() with an application root, then we close the session,
// and then we call get() again with the same application root,
// then the pool should not have spawned more than 1 app in total.
Application::SessionPtr session(spawnRackApp(pool, "stub/rack"));
session.reset();
session = spawnRackApp(pool, "stub/rack");
ensure_equals(pool->getCount(), 1u);
}
TEST_METHOD(4) {
// If we call get() with an application root, then we call get() again before closing
// the session, then the pool should have spawned 2 apps in total.
Application::SessionPtr session(spawnRackApp(pool, "stub/rack"));
Application::SessionPtr session2(spawnRackApp(pool2, "stub/rack"));
ensure_equals(pool->getCount(), 2u);
}
TEST_METHOD(5) {
// If we call get() twice with different application roots,
// then the pool should spawn two different apps.
TempDirCopy c1("stub/rack", "rackapp1.tmp");
TempDirCopy c2("stub/rack", "rackapp2.tmp");
replaceStringInFile("rackapp2.tmp/config.ru", "world", "world 2");
Application::SessionPtr session(spawnRackApp(pool, "rackapp1.tmp"));
Application::SessionPtr session2(spawnRackApp(pool2, "rackapp2.tmp"));
ensure_equals("Before the sessions were closed, both apps were busy", pool->getActive(), 2u);
ensure_equals("Before the sessions were closed, both apps were in the pool", pool->getCount(), 2u);
session->sendHeaders(createRequestHeaders());
string result(readAll(session->getStream()));
ensure("Session 1 belongs to the correct app", result.find("hello <b>world</b>") != string::npos);
session.reset();
session2->sendHeaders(createRequestHeaders());
result = readAll(session2->getStream());
ensure("Session 2 belongs to the correct app", result.find("hello <b>world 2</b>") != string::npos);
session2.reset();
}
TEST_METHOD(6) {
// If we call get() twice with different application roots,
// and we close both sessions, then both 2 apps should still
// be in the pool.
TempDirCopy c1("stub/rack", "rackapp1.tmp");
TempDirCopy c2("stub/rack", "rackapp2.tmp");
Application::SessionPtr session(spawnRackApp(pool, "rackapp1.tmp"));
Application::SessionPtr session2(spawnRackApp(pool, "rackapp2.tmp"));
session.reset();
session2.reset();
ensure_equals("There are 0 active apps", pool->getActive(), 0u);
ensure_equals("There are 2 apps in total", pool->getCount(), 2u);
}
TEST_METHOD(7) {
// If we call get() even though the pool is already full
// (active == max), and the application root is already
// in the pool, then the pool must wait until there's an
// inactive application.
pool->setMax(1);
// TODO: How do we test this?
}
TEST_METHOD(8) {
// If ApplicationPool spawns a new instance,
// and we kill it, then the next get() with the
// same application root should throw an exception.
// But the get() thereafter should not:
// ApplicationPool should have spawned a new instance
// after detecting that the original one died.
Application::SessionPtr session(spawnRackApp(pool, "stub/rack"));
kill(session->getPid(), SIGTERM);
session.reset();
try {
session = spawnRackApp(pool, "stub/rack");
fail("ApplicationPool::get() is supposed to "
"throw an exception because we killed "
"the app instance.");
} catch (const exception &e) {
session = spawnRackApp(pool, "stub/rack");
// Should not throw.
}
}
struct PoolWaitTestThread {
ApplicationPoolPtr pool;
Application::SessionPtr &m_session;
bool &m_done;
PoolWaitTestThread(const ApplicationPoolPtr &pool,
Application::SessionPtr &session,
bool &done)
: m_session(session), m_done(done) {
this->pool = pool;
done = false;
}
void operator()() {
m_session = spawnWsgiApp(pool, "stub/wsgi");
m_done = true;
}
};
TEST_METHOD(9) {
// If we call get() even though the pool is already full
// (active == max), and the application root is *not* already
// in the pool, then the pool will wait until enough sessions
// have been closed.
pool->setMax(2);
Application::SessionPtr session1(spawnRackApp(pool, "stub/rack"));
Application::SessionPtr session2(spawnRackApp(pool2, "stub/rack"));
Application::SessionPtr session3;
bool done;
shared_ptr<thread> thr = ptr(new thread(PoolWaitTestThread(pool2, session3, done)));
usleep(500000);
ensure("ApplicationPool is still waiting", !done);
ensure_equals(pool->getActive(), 2u);
ensure_equals(pool->getCount(), 2u);
// Now release one slot from the pool.
session1.reset();
// Wait at most 10 seconds.
time_t begin = time(NULL);
while (!done && time(NULL) - begin < 10) {
usleep(100000);
}
ensure("Session 3 is openend", done);
ensure_equals(pool->getActive(), 2u);
ensure_equals(pool->getCount(), 2u);
thr->join();
thr.reset();
}
TEST_METHOD(10) {
// If we call get(), and:
// * the pool is already full, but there are inactive apps
// (active < count && count == max)
// and
// * the application root for this get() is *not* already in the pool
// then the an inactive app should be killed in order to
// satisfy this get() command.
TempDirCopy c1("stub/rack", "rackapp1.tmp");
TempDirCopy c2("stub/rack", "rackapp2.tmp");
pool->setMax(2);
Application::SessionPtr session1(spawnRackApp(pool, "rackapp1.tmp"));
Application::SessionPtr session2(spawnRackApp(pool, "rackapp1.tmp"));
session1.reset();
session2.reset();
ensure_equals(pool->getActive(), 0u);
ensure_equals(pool->getCount(), 2u);
session1 = spawnRackApp(pool, "rackapp2.tmp");
ensure_equals(pool->getActive(), 1u);
ensure_equals(pool->getCount(), 2u);
}
TEST_METHOD(11) {
// A Session should still be usable after the pool has been destroyed.
Application::SessionPtr session(spawnRackApp(pool, "stub/rack"));
pool->clear();
pool.reset();
pool2.reset();
session->sendHeaders(createRequestHeaders());
session->shutdownWriter();
int reader = session->getStream();
string result(readAll(reader));
session->closeStream();
ensure(result.find("hello <b>world</b>") != string::npos);
}
TEST_METHOD(12) {
// If tmp/restart.txt didn't exist but has now been created,
// then the applications under app_root should be restarted.
struct stat buf;
TempDirCopy c("stub/rack", "rackapp.tmp");
Application::SessionPtr session1 = spawnRackApp(pool, "rackapp.tmp");
Application::SessionPtr session2 = spawnRackApp(pool, "rackapp.tmp");
session1.reset();
session2.reset();
touchFile("rackapp.tmp/tmp/restart.txt");
spawnRackApp(pool, "rackapp.tmp");
ensure_equals("No apps are active", pool->getActive(), 0u);
ensure_equals("Both apps are killed, and a new one was spawned",
pool->getCount(), 1u);
ensure("Restart file still exists",
stat("rackapp.tmp/tmp/restart.txt", &buf) == 0);
}
TEST_METHOD(13) {
// If tmp/restart.txt was present, and its timestamp changed
// since the last check, then the applications under app_root
// should still be restarted. However, a subsequent get()
// should not result in a restart.
pid_t old_pid;
TempDirCopy c("stub/rack", "rackapp.tmp");
TempDir d("rackapp.tmp/tmp/restart.txt");
Application::SessionPtr session = spawnRackApp(pool, "rackapp.tmp");
old_pid = session->getPid();
session.reset();
touchFile("rackapp.tmp/tmp/restart.txt", 10);
session = spawnRackApp(pool, "rackapp.tmp");
ensure("The app was restarted", session->getPid() != old_pid);
old_pid = session->getPid();
session.reset();
session = spawnRackApp(pool, "rackapp.tmp");
ensure_equals("The app was not restarted",
old_pid, session->getPid());
}
TEST_METHOD(15) {
// Test whether restarting with restart.txt really results in code reload.
TempDirCopy c("stub/rack", "rackapp.tmp");
Application::SessionPtr session = spawnRackApp(pool, "rackapp.tmp");
session->sendHeaders(createRequestHeaders());
string result = readAll(session->getStream());
ensure(result.find("hello <b>world</b>") != string::npos);
session.reset();
touchFile("rackapp.tmp/tmp/restart.txt");
replaceStringInFile("rackapp.tmp/config.ru", "world", "world 2");
session = spawnRackApp(pool, "rackapp.tmp");
session->sendHeaders(createRequestHeaders());
result = readAll(session->getStream());
ensure("App code has been reloaded", result.find("hello <b>world 2</b>") != string::npos);
}
TEST_METHOD(16) {
// If tmp/always_restart.txt is present and is a file,
// then the application under app_root should be always restarted.
struct stat buf;
pid_t old_pid;
TempDirCopy c("stub/rack", "rackapp.tmp");
Application::SessionPtr session1 = spawnRackApp(pool, "rackapp.tmp");
Application::SessionPtr session2 = spawnRackApp(pool2, "rackapp.tmp");
session1.reset();
session2.reset();
touchFile("rackapp.tmp/tmp/always_restart.txt");
// This get() results in a restart.
session1 = spawnRackApp(pool, "rackapp.tmp");
old_pid = session1->getPid();
session1.reset();
ensure_equals("First restart: no apps are active", pool->getActive(), 0u);
ensure_equals("First restart: the first 2 apps were killed, and a new one was spawned",
pool->getCount(), 1u);
ensure("always_restart file has not been deleted",
stat("rackapp.tmp/tmp/always_restart.txt", &buf) == 0);
// This get() results in a restart as well.
session1 = spawnRackApp(pool, "rackapp.tmp");
ensure(old_pid != session1->getPid());
session1.reset();
ensure_equals("Second restart: no apps are active", pool->getActive(), 0u);
ensure_equals("Second restart: the last app was killed, and a new one was spawned",
pool->getCount(), 1u);
ensure("always_restart file has not been deleted",
stat("rackapp.tmp/tmp/always_restart.txt", &buf) == 0);
}
TEST_METHOD(17) {
// If tmp/always_restart.txt is present and is a directory,
// then the application under app_root should be always restarted.
struct stat buf;
pid_t old_pid;
TempDirCopy c("stub/rack", "rackapp.tmp");
Application::SessionPtr session1 = spawnRackApp(pool, "rackapp.tmp");
Application::SessionPtr session2 = spawnRackApp(pool, "rackapp.tmp");
session1.reset();
session2.reset();
TempDir d("rackapp.tmp/tmp/always_restart.txt");
// This get() results in a restart.
session1 = spawnRackApp(pool, "rackapp.tmp");
old_pid = session1->getPid();
session1.reset();
ensure_equals("First restart: no apps are active", pool->getActive(), 0u);
ensure_equals("First restart: the first 2 apps were killed, and a new one was spawned",
pool->getCount(), 1u);
ensure("always_restart directory has not been deleted",
stat("rackapp.tmp/tmp/always_restart.txt", &buf) == 0);
// This get() results in a restart as well.
session1 = spawnRackApp(pool, "rackapp.tmp");
ensure(old_pid != session1->getPid());
session1.reset();
ensure_equals("Second restart: no apps are active", pool->getActive(), 0u);
ensure_equals("Second restart: the last app was killed, and a new one was spawned",
pool->getCount(), 1u);
ensure("always_restart directory has not been deleted",
stat("rackapp.tmp/tmp/always_restart.txt", &buf) == 0);
}
TEST_METHOD(18) {
// Test whether restarting with tmp/always_restart.txt really results in code reload.
TempDirCopy c("stub/rack", "rackapp.tmp");
Application::SessionPtr session = spawnRackApp(pool, "rackapp.tmp");
session->sendHeaders(createRequestHeaders());
string result = readAll(session->getStream());
ensure(result.find("hello <b>world</b>") != string::npos);
session.reset();
touchFile("rackapp.tmp/tmp/always_restart.txt");
replaceStringInFile("rackapp.tmp/config.ru", "world", "world 2");
session = spawnRackApp(pool, "rackapp.tmp");
session->sendHeaders(createRequestHeaders());
result = readAll(session->getStream());
ensure("App code has been reloaded (1)", result.find("hello <b>world 2</b>") != string::npos);
session.reset();
replaceStringInFile("rackapp.tmp/config.ru", "world 2", "world 3");
session = spawnRackApp(pool, "rackapp.tmp");
session->sendHeaders(createRequestHeaders());
result = readAll(session->getStream());
ensure("App code has been reloaded (2)", result.find("hello <b>world 3</b>") != string::npos);
session.reset();
}
TEST_METHOD(19) {
// If tmp/restart.txt and tmp/always_restart.txt are present,
// the application under app_root should still be restarted and
// both files must be kept.
pid_t old_pid, pid;
struct stat buf;
TempDirCopy c("stub/rack", "rackapp.tmp");
Application::SessionPtr session1 = spawnRackApp(pool, "rackapp.tmp");
Application::SessionPtr session2 = spawnRackApp(pool2, "rackapp.tmp");
session1.reset();
session2.reset();
touchFile("rackapp.tmp/tmp/restart.txt");
touchFile("rackapp.tmp/tmp/always_restart.txt");
old_pid = spawnRackApp(pool, "rackapp.tmp")->getPid();
ensure("always_restart.txt file has not been deleted",
stat("rackapp.tmp/tmp/always_restart.txt", &buf) == 0);
ensure("restart.txt file has not been deleted",
stat("rackapp.tmp/tmp/restart.txt", &buf) == 0);
pid = spawnRackApp(pool, "rackapp.tmp")->getPid();
ensure("The app was restarted", pid != old_pid);
}
TEST_METHOD(20) {
// It should look for restart.txt in the directory given by
// the restartDir option, if available.
struct stat buf;
char path[1024];
PoolOptions options("stub/rack");
options.appType = "rack";
options.restartDir = string(getcwd(path, sizeof(path))) + "/stub/rack";
Application::SessionPtr session1 = pool->get(options);
Application::SessionPtr session2 = pool2->get(options);
session1.reset();
session2.reset();
DeleteFileEventually f("stub/rack/restart.txt");
touchFile("stub/rack/restart.txt");
pool->get(options);
ensure_equals("No apps are active", pool->getActive(), 0u);
ensure_equals("Both apps are killed, and a new one was spawned",
pool->getCount(), 1u);
ensure("Restart file still exists",
stat("stub/rack/restart.txt", &buf) == 0);
}
TEST_METHOD(21) {
// restartDir may also be a directory relative to the
// application root.
struct stat buf;
PoolOptions options("stub/rack");
options.appType = "rack";
options.restartDir = "public";
Application::SessionPtr session1 = pool->get(options);
Application::SessionPtr session2 = pool2->get(options);
session1.reset();
session2.reset();
DeleteFileEventually f("stub/rack/public/restart.txt");
touchFile("stub/rack/public/restart.txt");
pool->get(options);
ensure_equals("No apps are active", pool->getActive(), 0u);
ensure_equals("Both apps are killed, and a new one was spawned",
pool->getCount(), 1u);
ensure("Restart file still exists",
stat("stub/rack/public/restart.txt", &buf) == 0);
}
TEST_METHOD(22) {
// The cleaner thread should clean idle applications without crashing.
pool->setMaxIdleTime(1);
spawnRackApp(pool, "stub/rack");
time_t begin = time(NULL);
while (pool->getCount() == 1u && time(NULL) - begin < 10) {
usleep(100000);
}
ensure_equals("App should have been cleaned up", pool->getCount(), 0u);
}
TEST_METHOD(23) {
// MaxPerApp is respected.
pool->setMax(3);
pool->setMaxPerApp(1);
// We connect to stub/rack while it already has an instance with
// 1 request in its queue. Assert that the pool doesn't spawn
// another instance.
Application::SessionPtr session1 = spawnRackApp(pool, "stub/rack");
Application::SessionPtr session2 = spawnRackApp(pool2, "stub/rack");
ensure_equals(pool->getCount(), 1u);
// We connect to stub/wsgi. Assert that the pool spawns a new
// instance for this app.
TempDirCopy c("stub/wsgi", "wsgiapp.tmp");
ApplicationPoolPtr pool3(newPoolConnection());
Application::SessionPtr session3 = spawnWsgiApp(pool3, "wsgiapp.tmp");
ensure_equals(pool->getCount(), 2u);
}
TEST_METHOD(24) {
// Application instance is shutdown after 'maxRequests' requests.
PoolOptions options("stub/rack");
int reader;
pid_t originalPid;
Application::SessionPtr session;
options.appType = "rack";
options.maxRequests = 4;
pool->setMax(1);
session = pool->get(options);
originalPid = session->getPid();
session.reset();
for (unsigned int i = 0; i < 4; i++) {
session = pool->get(options);
session->sendHeaders(createRequestHeaders());
session->shutdownWriter();
reader = session->getStream();
readAll(reader);
// Must explicitly call reset() here because we
// want to close the session right now.
session.reset();
// In case of ApplicationPoolServer, we sleep here
// for a little while to force a context switch to
// the server, so that the session close event may
// be processed.
usleep(100000);
}
session = pool->get(options);
ensure(session->getPid() != originalPid);
}
struct SpawnRackAppFunction {
ApplicationPoolPtr pool;
bool *done;
void operator()() {
PoolOptions options;
options.appRoot = "stub/rack";
options.appType = "rack";
options.useGlobalQueue = true;
pool->get(options);
*done = true;
}
};
TEST_METHOD(25) {
// If global queueing mode is enabled, then get() waits until
// there's at least one idle backend process for this application
// domain.
pool->setMax(1);
PoolOptions options;
options.appRoot = "stub/rack";
options.appType = "rack";
options.useGlobalQueue = true;
Application::SessionPtr session = pool->get(options);
bool done = false;
SpawnRackAppFunction func;
func.pool = pool2;
func.done = &done;
boost::thread thr(func);
usleep(100000);
// Previous session hasn't been closed yet, so pool should still
// be waiting.
ensure(!done);
// Close the previous session. The thread should now finish.
session.reset();
thr.join();
}
TEST_METHOD(26) {
// When a previous application domain spinned down, and we touched
// restart.txt and try to spin up a new process for this domain,
// then any ApplicationSpawner/FrameworkSpawner processes should be
// killed first.
// TODO: to test this we first need to be able to move
// ApplicationPoolServer in-process so that we can use mock objects
}
/*************************************/
#endif /* USE_TEMPLATE */
Jump to Line
Something went wrong with that request. Please try again.