// '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(); } } }
// '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(); } } }