Skip to content

Commit 2a6c035

Browse files
authored
Track and prioritize routing according to application restart generations (#2564)
1 parent 181f0cf commit 2a6c035

File tree

10 files changed

+472
-190
lines changed

10 files changed

+472
-190
lines changed

CHANGELOG

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
Release 6.0.24 (Not yet released)
22
-------------
3+
* [Enterprise] Smarter rolling restarts for better performance and reliability. We changed the way we route requests. Instead of picking the least-busy process, we now first prioritize new processes first. During a rolling restart, this new behavior leads to more efficient utilization of application caches, faster validation of new rollouts, and faster recovery from problematic deployments. Closes GH-2551.
34
* Fix a regression from 6.0.10 where running `passenger-config system-properties` would throw an error. Closes GH-2565.
45
* [Enterprise] Fix a memory corruption-related crash that could occur during rolling restarting.
56
* [Ubuntu] Add packages for Ubuntu 24.10 "oracular".

src/agent/Core/ApplicationPool/Group.h

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,18 @@ class Group: public boost::enable_shared_from_this<Group> {
9292
};
9393

9494
struct RouteResult {
95-
Process *process;
96-
bool finished;
95+
/** The Process to route the request to, or nullptr if no process can be routed to. */
96+
Process * const process;
97+
/**
98+
* If `process` is nullptr, then `finished` indicates whether another `Group::route()`
99+
* call on a different request *could* succeed, meaning that the caller should continue
100+
* calling `Group::route()` if there are more queued requests that need to be processed.
101+
*
102+
* Usually `finished` is false because all processes are totally busy. But in some cases,
103+
* for example when using sticky sessions, it could be true because other requests can
104+
* potentially be routed to other processes.
105+
*/
106+
const bool finished;
97107

98108
RouteResult(Process *p, bool _finished = false)
99109
: process(p),
@@ -223,9 +233,9 @@ class Group: public boost::enable_shared_from_this<Group> {
223233
/****** Process list management ******/
224234

225235
Process *findProcessWithStickySessionId(unsigned int id) const;
226-
Process *findProcessWithStickySessionIdOrLowestBusyness(unsigned int id) const;
227-
Process *findProcessWithLowestBusyness(const ProcessList &processes) const;
228-
Process *findEnabledProcessWithLowestBusyness() const;
236+
Process *findBestProcessPreferringStickySessionId(unsigned int id) const;
237+
Process *findBestProcess(const ProcessList &processes) const;
238+
Process *findBestEnabledProcess() const;
229239

230240
void addProcessToList(const ProcessPtr &process, ProcessList &destination);
231241
void removeProcessFromList(const ProcessPtr &process, ProcessList &source);

src/agent/Core/ApplicationPool/Group/InternalUtils.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ Group::createNullProcessObject() {
184184
LockGuard l(context->memoryManagementSyncher);
185185
Process *process = context->processObjectPool.malloc();
186186
Guard guard(context, process);
187-
process = new (process) Process(&info, args);
187+
process = new (process) Process(&info, info.group->restartsInitiated, args);
188188
process->shutdownNotRequired();
189189
guard.clear();
190190
return ProcessPtr(process, false);
@@ -221,7 +221,7 @@ Group::createProcessObject(const SpawningKit::Spawner &spawner,
221221
LockGuard l(context->memoryManagementSyncher);
222222
Process *process = context->processObjectPool.malloc();
223223
Guard guard(context, process);
224-
process = new (process) Process(&info, spawnResult, args);
224+
process = new (process) Process(&info, info.group->restartsInitiated, spawnResult, args);
225225
guard.clear();
226226
return ProcessPtr(process, false);
227227
}

src/agent/Core/ApplicationPool/Group/ProcessListManagement.cpp

Lines changed: 51 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -63,72 +63,75 @@ Group::findProcessWithStickySessionId(unsigned int id) const {
6363
return NULL;
6464
}
6565

66+
/**
67+
* Return the process with the given sticky session ID if it exists.
68+
* If not, then find the "best" enabled process to route a request to,
69+
* according to the same criteria documented for findBestProcess().
70+
*
71+
* - If the process with the given sticky session ID exists, then always
72+
* returns that process. Meaning that this process could be `!canBeRoutedTo()`.
73+
* - If there is no process that can be routed to, then returns nullptr.
74+
*/
6675
Process *
67-
Group::findProcessWithStickySessionIdOrLowestBusyness(unsigned int id) const {
68-
int leastBusyProcessIndex = -1;
69-
int lowestBusyness = 0;
70-
unsigned int i, size = enabledProcessBusynessLevels.size();
71-
const int *enabledProcessBusynessLevels = &this->enabledProcessBusynessLevels[0];
72-
73-
for (i = 0; i < size; i++) {
74-
Process *process = enabledProcesses[i].get();
76+
Group::findBestProcessPreferringStickySessionId(unsigned int id) const {
77+
Process *bestProcess = nullptr;
78+
ProcessList::const_iterator it;
79+
ProcessList::const_iterator end = enabledProcesses.end();
80+
81+
for (it = enabledProcesses.begin(); it != end; it++) {
82+
Process *process = it->get();
7583
if (process->getStickySessionId() == id) {
7684
return process;
77-
} else if (leastBusyProcessIndex == -1 || enabledProcessBusynessLevels[i] < lowestBusyness) {
78-
leastBusyProcessIndex = i;
79-
lowestBusyness = enabledProcessBusynessLevels[i];
85+
} else if (!process->isTotallyBusy()
86+
&& (
87+
bestProcess == nullptr
88+
|| process->generation > bestProcess->generation
89+
|| (process->generation == bestProcess->generation && process->spawnStartTime < bestProcess->spawnStartTime)
90+
|| (process->generation == bestProcess->generation && process->spawnStartTime == bestProcess->spawnStartTime && process->busyness() < bestProcess->busyness())
91+
)
92+
) {
93+
bestProcess = process;
8094
}
8195
}
8296

83-
if (leastBusyProcessIndex == -1) {
84-
return NULL;
85-
} else {
86-
return enabledProcesses[leastBusyProcessIndex].get();
87-
}
97+
return bestProcess;
8898
}
8999

100+
/**
101+
* Given a ProcessList, find the "best" process to route a request to.
102+
* At the moment, "best" is defined as the process with the highest generation,
103+
* lowest start time, and lowest busyness, in that order of priority.
104+
*
105+
* If there is no process that can be routed to, then returns nullptr.
106+
*
107+
* @post result != nullptr || result.canBeRoutedTo()
108+
*/
90109
Process *
91-
Group::findProcessWithLowestBusyness(const ProcessList &processes) const {
110+
Group::findBestProcess(const ProcessList &processes) const {
92111
if (processes.empty()) {
93-
return NULL;
112+
return nullptr;
94113
}
95114

96-
int lowestBusyness = -1;
97-
Process *leastBusyProcess = NULL;
115+
Process *bestProcess = nullptr;
98116
ProcessList::const_iterator it;
99117
ProcessList::const_iterator end = processes.end();
100-
for (it = processes.begin(); it != end; it++) {
101-
Process *process = (*it).get();
102-
int busyness = process->busyness();
103-
if (lowestBusyness == -1 || lowestBusyness > busyness) {
104-
lowestBusyness = busyness;
105-
leastBusyProcess = process;
106-
}
107-
}
108-
return leastBusyProcess;
109-
}
110-
111-
/**
112-
* Cache-optimized version of findProcessWithLowestBusyness() for the common case.
113-
*/
114-
Process *
115-
Group::findEnabledProcessWithLowestBusyness() const {
116-
if (enabledProcesses.empty()) {
117-
return NULL;
118-
}
119118

120-
int leastBusyProcessIndex = -1;
121-
int lowestBusyness = 0;
122-
unsigned int i, size = enabledProcessBusynessLevels.size();
123-
const int *enabledProcessBusynessLevels = &this->enabledProcessBusynessLevels[0];
119+
for (it = processes.begin(); it != end; it++) {
120+
Process *process = it->get();
124121

125-
for (i = 0; i < size; i++) {
126-
if (leastBusyProcessIndex == -1 || enabledProcessBusynessLevels[i] < lowestBusyness) {
127-
leastBusyProcessIndex = i;
128-
lowestBusyness = enabledProcessBusynessLevels[i];
122+
if (!process->isTotallyBusy()
123+
&& (
124+
bestProcess == nullptr
125+
|| process->generation > bestProcess->generation
126+
|| (process->generation == bestProcess->generation && process->spawnStartTime < bestProcess->spawnStartTime)
127+
|| (process->generation == bestProcess->generation && process->spawnStartTime == bestProcess->spawnStartTime && process->busyness() < bestProcess->busyness())
128+
)
129+
) {
130+
bestProcess = process;
129131
}
130132
}
131-
return enabledProcesses[leastBusyProcessIndex].get();
133+
134+
return bestProcess;
132135
}
133136

134137
/**

src/agent/Core/ApplicationPool/Group/SessionManagement.cpp

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <Core/ApplicationPool/Pool.h>
3131
#endif
3232
#include <Core/ApplicationPool/Group.h>
33+
#include <cassert>
3334

3435
/*************************************************************************
3536
*
@@ -64,16 +65,17 @@ Group::RouteResult
6465
Group::route(const Options &options) const {
6566
if (OXT_LIKELY(enabledCount > 0)) {
6667
if (options.stickySessionId == 0) {
67-
Process *process = findEnabledProcessWithLowestBusyness();
68-
if (process->canBeRoutedTo()) {
68+
Process *process = findBestProcess(enabledProcesses);
69+
if (process != nullptr) {
70+
assert(process->canBeRoutedTo());
6971
return RouteResult(process);
7072
} else {
7173
return RouteResult(NULL, true);
7274
}
7375
} else {
74-
Process *process = findProcessWithStickySessionIdOrLowestBusyness(
76+
Process *process = findBestProcessPreferringStickySessionId(
7577
options.stickySessionId);
76-
if (process != NULL) {
78+
if (process != nullptr) {
7779
if (process->canBeRoutedTo()) {
7880
return RouteResult(process);
7981
} else {
@@ -84,8 +86,9 @@ Group::route(const Options &options) const {
8486
}
8587
}
8688
} else {
87-
Process *process = findProcessWithLowestBusyness(disablingProcesses);
88-
if (process->canBeRoutedTo()) {
89+
Process *process = findBestProcess(disablingProcesses);
90+
if (process != nullptr) {
91+
assert(process->canBeRoutedTo());
8992
return RouteResult(process);
9093
} else {
9194
return RouteResult(NULL, true);
@@ -310,9 +313,8 @@ Group::get(const Options &newOptions, const GetCallback &callback,
310313
assert(m_spawning || restarting() || poolAtFullCapacity());
311314

312315
if (disablingCount > 0 && !restarting()) {
313-
Process *process = findProcessWithLowestBusyness(disablingProcesses);
314-
assert(process != NULL);
315-
if (!process->isTotallyBusy()) {
316+
Process *process = findBestProcess(disablingProcesses);
317+
if (process != nullptr && !process->isTotallyBusy()) {
316318
return newSession(process, newOptions.currentTime);
317319
}
318320
}

src/agent/Core/ApplicationPool/Pool.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,8 @@
2828

2929
#include <string>
3030
#include <vector>
31-
#include <algorithm>
3231
#include <utility>
3332
#include <sstream>
34-
#include <iomanip>
3533
#include <boost/thread.hpp>
3634
#include <boost/bind/bind.hpp>
3735
#include <boost/shared_ptr.hpp>

src/agent/Core/ApplicationPool/Process.h

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@
5353
#include <Core/SpawningKit/Result.h>
5454
#include <Shared/ApplicationPoolApiKey.h>
5555

56+
namespace tut {
57+
template<class Data> class test_object;
58+
}
59+
5660
namespace Passenger {
5761
namespace ApplicationPool2 {
5862

@@ -99,6 +103,9 @@ typedef boost::container::vector<ProcessPtr> ProcessList;
99103
*/
100104
class Process {
101105
public:
106+
friend class Group;
107+
template<class Data> friend class tut::test_object;
108+
102109
static const unsigned int MAX_SOCKETS_ACCEPTING_HTTP_REQUESTS = 3;
103110

104111
private:
@@ -388,6 +395,10 @@ class Process {
388395

389396
/** Last time when a session was opened for this Process. */
390397
unsigned long long lastUsed;
398+
/** Which generation of app processes this one belongs to,
399+
inherited from the app group, incremented when a restart
400+
is initiated*/
401+
const unsigned int generation;
391402
/** Number of sessions currently open.
392403
* @invariant session >= 0
393404
*/
@@ -450,8 +461,7 @@ class Process {
450461
/** Collected by Pool::collectAnalytics(). */
451462
ProcessMetrics metrics;
452463

453-
454-
Process(const BasicGroupInfo *groupInfo, const Json::Value &args)
464+
Process(const BasicGroupInfo *groupInfo, const unsigned int gen, const Json::Value &args)
455465
: info(this, groupInfo, args),
456466
socketsAcceptingHttpRequestsCount(0),
457467
spawnerCreationTime(getJsonUint64Field(args, "spawner_creation_time")),
@@ -462,6 +472,7 @@ class Process {
462472
refcount(1),
463473
index(-1),
464474
lastUsed(spawnEndTime),
475+
generation(gen),
465476
sessions(0),
466477
processed(0),
467478
lifeStatus(ALIVE),
@@ -475,7 +486,7 @@ class Process {
475486
indexSocketsAcceptingHttpRequests();
476487
}
477488

478-
Process(const BasicGroupInfo *groupInfo, const SpawningKit::Result &skResult,
489+
Process(const BasicGroupInfo *groupInfo, const unsigned int gen, const SpawningKit::Result &skResult,
479490
const Json::Value &args)
480491
: info(this, groupInfo, skResult),
481492
socketsAcceptingHttpRequestsCount(0),
@@ -487,6 +498,7 @@ class Process {
487498
refcount(1),
488499
index(-1),
489500
lastUsed(spawnEndTime),
501+
generation(gen),
490502
sessions(0),
491503
processed(0),
492504
lifeStatus(ALIVE),

0 commit comments

Comments
 (0)