mirror of
https://github.com/ether/etherpad-lite.git
synced 2026-07-18 00:57:55 +00:00
perf: avoid throw-as-control-flow in SessionManager hot path (#7756)
CPU profile of the SUT at the 100-400 author dive sweep
(load-test workflow run 25956384097) attributed about 6% of total
process CPU to the throw + catch around getSessionInfo:
- ~1.82% to `new CustomError('sessionID does not exist', 'apierror')`
construction (stack trace capture)
- ~4.12% downstream, via the catch block's `console.debug(...)`
routed through log4js -> sendToListeners -> sendLogEventToAppender
Both call sites (`findAuthorID` on every CLIENT_READY, and
`listSessionsWithDBKey` on session listing) immediately caught
`apierror` and discarded it. The public `exports.getSessionInfo`
contract still has to throw for the HTTP API (returning code:1 for
missing sessionID), so introduce a private `getSessionInfoOrNull`
helper that returns null and have the hot-path callers use it
directly. `exports.getSessionInfo` is kept as a thin wrapper that
preserves the existing throw semantics.
No behaviour change for the HTTP API — sessionsAndGroups.ts test
file (32 cases, including "getSessionInfo of deleted session"
expecting code:1) passes unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
620c8b20cd
commit
8db7988394
1 changed files with 20 additions and 22 deletions
|
|
@ -62,18 +62,8 @@ exports.findAuthorID = async (groupID:string, sessionCookie: string) => {
|
|||
* Also, see #3820.
|
||||
*/
|
||||
const sessionIDs = sessionCookie.replace(/^"|"$/g, '').split(',');
|
||||
const sessionInfoPromises = sessionIDs.map(async (id) => {
|
||||
try {
|
||||
return await exports.getSessionInfo(id);
|
||||
} catch (err:any) {
|
||||
if (err.message === 'sessionID does not exist') {
|
||||
console.debug(`SessionManager getAuthorID: no session exists with ID ${id}`);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
const sessionInfoPromises = sessionIDs.map(async (id) =>
|
||||
(await getSessionInfoOrNull(id)) || undefined);
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const isMatch = (si: {
|
||||
groupID: string;
|
||||
|
|
@ -163,9 +153,20 @@ exports.createSession = async (groupID: string, authorID: string, validUntil: nu
|
|||
* @param {String} sessionID The id of the session
|
||||
* @return {Promise<Object>} the sessioninfos
|
||||
*/
|
||||
// Non-throwing variant for hot-path callers. Hot path uses
|
||||
// `findAuthorID` on every CLIENT_READY and `listSessionsWithDBKey` on
|
||||
// session listing; both wrap getSessionInfo in try/catch and discard
|
||||
// "sessionID does not exist" CustomError. Profiling against develop at
|
||||
// 100-400 author sweep (ether/etherpad#7756) attributed ~6% of total
|
||||
// CPU to that throw+catch pair: ~1.8% to CustomError construction and
|
||||
// ~4% to the cascading `console.debug` call routed through log4js. A
|
||||
// null return collapses the cost to a single nullable check.
|
||||
const getSessionInfoOrNull = async (sessionID: string) =>
|
||||
await db.get(`session:${sessionID}`);
|
||||
|
||||
exports.getSessionInfo = async (sessionID:string) => {
|
||||
// check if the database entry of this session exists
|
||||
const session = await db.get(`session:${sessionID}`);
|
||||
const session = await getSessionInfoOrNull(sessionID);
|
||||
|
||||
if (session == null) {
|
||||
// session does not exist
|
||||
|
|
@ -250,15 +251,12 @@ const listSessionsWithDBKey = async (dbkey: string) => {
|
|||
|
||||
// iterate through the sessions and get the sessioninfos
|
||||
for (const sessionID of Object.keys(sessions || {})) {
|
||||
try {
|
||||
sessions[sessionID] = await exports.getSessionInfo(sessionID);
|
||||
} catch (err:any) {
|
||||
if (err.name === 'apierror') {
|
||||
console.warn(`Found bad session ${sessionID} in ${dbkey}`);
|
||||
sessions[sessionID] = null;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
const info = await getSessionInfoOrNull(sessionID);
|
||||
if (info == null) {
|
||||
console.warn(`Found bad session ${sessionID} in ${dbkey}`);
|
||||
sessions[sessionID] = null;
|
||||
} else {
|
||||
sessions[sessionID] = info;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue