// 'lockNow == false' may only be used during unit tests. Normally we // should never call the callback while holding the lock. void Pool::asyncGet(const Options &options, const GetCallback &callback, bool lockNow) { DynamicScopedLock lock(syncher, lockNow); assert(lifeStatus == ALIVE || lifeStatus == PREPARED_FOR_SHUTDOWN); verifyInvariants(); P_TRACE(2, "asyncGet(appGroupName=" << options.getAppGroupName() << ")"); boost::container::vector<Callback> actions; Group *existingGroup = findMatchingGroup(options); if (OXT_LIKELY(existingGroup != NULL)) { /* Best case: the app group is already in the pool. Let's use it. */ P_TRACE(2, "Found existing Group"); existingGroup->verifyInvariants(); SessionPtr session = existingGroup->get(options, callback, actions); existingGroup->verifyInvariants(); verifyInvariants(); P_TRACE(2, "asyncGet() finished"); if (lockNow) { lock.unlock(); } if (session != NULL) { callback(session, ExceptionPtr()); } } else if (!atFullCapacityUnlocked()) { /* The app super group isn't in the pool and we have enough free * resources to make a new one. */ P_DEBUG("Spawning new Group"); GroupPtr group = createGroupAndAsyncGetFromIt(options, callback, actions); group->verifyInvariants(); verifyInvariants(); P_DEBUG("asyncGet() finished"); } else { /* Uh oh, the app super group isn't in the pool but we don't * have the resources to make a new one. The sysadmin should * configure the system to let something like this happen * as least as possible, but let's try to handle it as well * as we can. */ ProcessPtr freedProcess = forceFreeCapacity(NULL, actions); if (freedProcess == NULL) { /* No process is eligible for killing. This could happen if, for example, * all (super)groups are currently initializing/restarting/spawning/etc. * We have no choice but to satisfy this get() action later when resources * become available. */ P_DEBUG("Could not free a process; putting request to top-level getWaitlist"); getWaitlist.push_back(GetWaiter( options.copyAndPersist(), callback)); } else { /* Now that a process has been trashed we can create * the missing Group. */ P_DEBUG("Creating new Group"); GroupPtr group = createGroup(options); SessionPtr session = group->get(options, callback, actions); /* The Group is now spawning a process so the callback * should now have been put on the wait list, * unless something has changed and we forgot to update * some code here or if options.noop... */ if (session != NULL) { assert(options.noop); actions.push_back(boost::bind(GetCallback::call, callback, session, ExceptionPtr())); } freedProcess->getGroup()->verifyInvariants(); group->verifyInvariants(); } assert(atFullCapacityUnlocked()); verifyInvariants(); verifyExpensiveInvariants(); P_TRACE(2, "asyncGet() finished"); } if (!actions.empty()) { if (lockNow) { if (lock.owns_lock()) { lock.unlock(); } runAllActions(actions); } else { // This state is not allowed. If we reach // here then it probably indicates a bug in // the test suite. abort(); } } }
void SuperGroup::realDoInitialize(const Options &options, unsigned int generation) { vector<ComponentInfo> componentInfos; vector<ComponentInfo>::const_iterator it; ExceptionPtr exception; P_TRACE(2, "Initializing SuperGroup " << inspect() << " in the background..."); try { componentInfos = loadComponentInfos(options); } catch (const tracable_exception &e) { exception = copyException(e); } if (componentInfos.empty() && exception == NULL) { string message = "The directory " + options.appRoot + " does not seem to contain a web application."; exception = boost::make_shared<SpawnException>( message, message, false); } PoolPtr pool = getPool(); Pool::DebugSupportPtr debug = pool->debugSupport; vector<Callback> actions; { if (debug != NULL && debug->superGroup) { debug->debugger->send("About to finish SuperGroup initialization"); debug->messages->recv("Proceed with initializing SuperGroup"); } boost::unique_lock<boost::mutex> lock(getPoolSyncher(pool)); this_thread::disable_interruption di; this_thread::disable_syscall_interruption dsi; NOT_EXPECTING_EXCEPTIONS(); if (OXT_UNLIKELY(getPool() == NULL || generation != this->generation)) { return; } P_TRACE(2, "Initialization of SuperGroup " << inspect() << " almost done; grabbed lock"); assert(state == INITIALIZING); verifyInvariants(); if (componentInfos.empty()) { /* Somehow initialization failed. Maybe something has deleted * the supergroup files while we're working. */ assert(exception != NULL); setState(DESTROYED); actions.reserve(getWaitlist.size()); while (!getWaitlist.empty()) { const GetWaiter &waiter = getWaitlist.front(); actions.push_back(boost::bind(waiter.callback, SessionPtr(), exception)); getWaitlist.pop_front(); } } else { for (it = componentInfos.begin(); it != componentInfos.end(); it++) { const ComponentInfo &info = *it; GroupPtr group = boost::make_shared<Group>(shared_from_this(), options, info); groups.push_back(group); if (info.isDefault) { defaultGroup = group.get(); } } setState(READY); assignGetWaitlistToGroups(actions); } verifyInvariants(); P_TRACE(2, "Done initializing SuperGroup " << inspect()); } this_thread::disable_interruption di; this_thread::disable_syscall_interruption dsi; runAllActions(actions); runInitializationHooks(); }
// 'lockNow == false' may only be used during unit tests. Normally we // should never call the callback while holding the lock. void Pool::asyncGet(const Options &options, const GetCallback &callback, bool lockNow, UnionStation::StopwatchLog **stopwatchLog) { DynamicScopedLock lock(syncher, lockNow); assert(lifeStatus == ALIVE || lifeStatus == PREPARED_FOR_SHUTDOWN); verifyInvariants(); P_TRACE(2, "asyncGet(appGroupName=" << options.getAppGroupName() << ")"); boost::container::vector<Callback> actions; Group *existingGroup = findMatchingGroup(options); if (stopwatchLog != NULL) { // Log some essentials stats about what this request is facing in its upcoming journey through the queue: // 1) position in the queue upon entry, and 2) whether spawning activity is occurring (which takes cycles // but also indicates the server has headroom to handle the load). Json::Value data; if (!existingGroup) { data["message"] = "spawning.."; // the first of this group, so keep it simple (also: we don't know maxQ yet) } else { char queueMaxStr[10]; int queueMax = existingGroup->options.maxRequestQueueSize; if (queueMax > 0) { snprintf(queueMaxStr, 10, "%d", queueMax); } char message[50]; snprintf(message, 100, "queue: %zu / %s, spawning: %s", existingGroup->getWaitlist.size(), (queueMax == 0 ? "inf" : queueMaxStr), (existingGroup->processesBeingSpawned == 0 ? "no" : "yes")); data["message"] = message; } Json::Value json; json["data"] = data; json["data_type"] = "generic"; json["name"] = "Await available process"; *stopwatchLog = new UnionStation::StopwatchLog(options.transaction, "Pool::asyncGet", stringifyJson(json).c_str()); } if (OXT_LIKELY(existingGroup != NULL)) { /* Best case: the app group is already in the pool. Let's use it. */ P_TRACE(2, "Found existing Group"); existingGroup->verifyInvariants(); SessionPtr session = existingGroup->get(options, callback, actions); existingGroup->verifyInvariants(); verifyInvariants(); P_TRACE(2, "asyncGet() finished"); if (lockNow) { lock.unlock(); } if (session != NULL) { callback(session, ExceptionPtr()); } } else if (!atFullCapacityUnlocked()) { /* The app super group isn't in the pool and we have enough free * resources to make a new one. */ P_DEBUG("Spawning new Group"); GroupPtr group = createGroupAndAsyncGetFromIt(options, callback, actions); group->verifyInvariants(); verifyInvariants(); P_DEBUG("asyncGet() finished"); } else { /* Uh oh, the app super group isn't in the pool but we don't * have the resources to make a new one. The sysadmin should * configure the system to let something like this happen * as least as possible, but let's try to handle it as well * as we can. */ ProcessPtr freedProcess = forceFreeCapacity(NULL, actions); if (freedProcess == NULL) { /* No process is eligible for killing. This could happen if, for example, * all (super)groups are currently initializing/restarting/spawning/etc. * We have no choice but to satisfy this get() action later when resources * become available. */ P_DEBUG("Could not free a process; putting request to top-level getWaitlist"); getWaitlist.push_back(GetWaiter( options.copyAndPersist().detachFromUnionStationTransaction(), callback)); } else { /* Now that a process has been trashed we can create * the missing Group. */ P_DEBUG("Creating new Group"); GroupPtr group = createGroup(options); SessionPtr session = group->get(options, callback, actions); /* The Group is now spawning a process so the callback * should now have been put on the wait list, * unless something has changed and we forgot to update * some code here or if options.noop... */ if (session != NULL) { assert(options.noop); actions.push_back(boost::bind(GetCallback::call, callback, session, ExceptionPtr())); } freedProcess->getGroup()->verifyInvariants(); group->verifyInvariants(); } assert(atFullCapacityUnlocked()); verifyInvariants(); verifyExpensiveInvariants(); P_TRACE(2, "asyncGet() finished"); } if (!actions.empty()) { if (lockNow) { if (lock.owns_lock()) { lock.unlock(); } runAllActions(actions); } else { // This state is not allowed. If we reach // here then it probably indicates a bug in // the test suite. abort(); } } }