Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding close method to AuditLogger to free up resources. #1292

Merged
merged 10 commits into from
Nov 8, 2024

Conversation

berhalak
Copy link
Contributor

@berhalak berhalak commented Nov 5, 2024

Context

During DocApi2 server tests we noticed that the mocha process hangs on some resources.

Proposed solution

AuditLogger class had two issues which are now fixed:

  • It was creating instances of MapWithTTL class without closing them
  • Not all promises (created by Promise.all) where awaited

Has this been tested?

  • 👍 existing tests

@@ -172,15 +207,27 @@ export class AuditLogger implements IAuditLogger {

private async _getOrSetStreamingDestinations(event: AuditEvent) {
const orgId = event.context.site?.id;
const destinations = await Promise.all([
Copy link
Contributor Author

@berhalak berhalak Nov 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To recreate similar issue just promises just put those lines here and run the DocApi2 test.

      new Promise<AuditLogStreamingDestination[] | null>((resolve) => setTimeout(resolve, 10 * 1000, null)),
      new Promise<AuditLogStreamingDestination[] | null>((res, reject) => setTimeout(reject, 1, null)),

Copy link
Collaborator

@fflorent fflorent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

I just have a suggestion, otherwise looks good to me (but I don't know much this part of the code, so I can't put a seal of quality on that part :)).

// All log calls should be awaited already and errors should be logged.
await logCall.catch(() => {});
}
if (this._logsInProgress.length > 0) {
Copy link
Collaborator

@fflorent fflorent Nov 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I understand why you do that: if the logEventOrThrow method is called while lines 119 to 122 are executed, this should log an error (just like the message suggests). It surprised me a bit (I took some time to understand that despite setting _logsInProgress to [], it may still be populated in other places)

But the tests might hang if such a case occur, don't they?

I have an alternative in mind, would it make sense?

  • we introduce a boolean property (_isClosed for example) which would indicate if the close method has been called;
  • in logEventOrThrow(), if the AuditLogger has been closed, we don't even call the internal method _logEventOrThrow and rather log the message below. This way, nothing is done and we don't have to worry about what happens in this case anyway.

@@ -12,9 +12,6 @@ describe('ColumnOps.ntest', function() {
await gu.supportOldTimeyTestCode();
await gu.useFixtureDoc(cleanup, "World.grist", true);
await gu.toggleSidePanel('left', 'close');
// The banner appearing mid-test occasionally interferes with the rest of
// the tests (for some unknown reason), so wait for it to appear first.
await $('.test-banner-element').wait();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This banner was only shown on internal CLI (probably), we will sort it differently.

@berhalak berhalak requested a review from fflorent November 5, 2024 22:56
Copy link
Collaborator

@fflorent fflorent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, with a suggestion! (for what it's worth)

app/server/lib/AuditLogger.ts Outdated Show resolved Hide resolved
/**
* Returns a promise that resolves in the given number of milliseconds.
* (A replica of bluebird.delay using native promises.)
*/
export function delay(msec: number): Promise<void> {
return new Promise<void>((resolve) => setTimeout(resolve, msec));
}

export async function waitToPass(fn: () => MaybePromise<any>, maxWaitMs: number = 2000) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. A bit unusual to put a test-only method in app? But given that there's nowhere very obvious in tests to put a common library, I guess that's ok. Can you add some documentation about what the method is for (I know there's already undocumented copies of the function lying around, but we should have a higher standard for app/common).

Copy link
Contributor Author

@berhalak berhalak Nov 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. Turned out we already have it, so I refactored the old one a bit.

await delay(10);
}
}
await fn();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a little note that this is to throw a meaningful error if the fn isn't passing, rather than just for example saying the timeout failed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored it to make it more obvious what is going on - and it doesn't need a comment now.

app/server/lib/AuditLogger.ts Outdated Show resolved Hide resolved
@@ -960,6 +960,7 @@ export class FlexServer implements GristServer {
// Do this after _shutdown, since DocWorkerMap is used during shutdown.
if (this._docWorkerMap) { await this._docWorkerMap.close(); }
if (this._sessionStore) { await this._sessionStore.close(); }
if (this._auditLogger) { await this._auditLogger.close(); }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about what happens if there are slow audit sinks and this close takes time. Not much we can do. We could have started the close earlier, in parallel with other actions - but perhaps we want to audit those :).

Let's leave it so.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will go away with jobs. Then we would just abort it, and resume later.

@berhalak berhalak requested a review from paulfitz November 7, 2024 13:57
@berhalak
Copy link
Contributor Author

berhalak commented Nov 7, 2024

Sorry for the additional code, there were two additional problems:

  • Existing: redis client wasn't disconnected, which also was causing problems (but not always)
  • The close method implementation in AuditLogger was wrong. It turned out that promises create other promises that are not awaited, so now the code tries to drain it.

app/server/lib/shutdown.js Outdated Show resolved Hide resolved
@berhalak
Copy link
Contributor Author

berhalak commented Nov 8, 2024

Last commit was just a rebase.

@berhalak berhalak merged commit 5b35a86 into main Nov 8, 2024
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants