mirror of
https://github.com/suitenumerique/docs
synced 2026-04-21 13:37:20 +00:00
🐛(y-provider) destroy Y.Doc instances after each convert request
The Yjs reader and writer in `convertHandler.ts` were creating `Y.Doc`instances on every request without calling `.destroy()`, causing a slow heap leak that could crash the server. Fixed by wrapping both sites in `try/finally` blocks that call `ydoc.destroy()`. Regression tests added to assert `destroy` is called the expected number of times per request path.
This commit is contained in:
parent
c886cbb41d
commit
525d8c8417
3 changed files with 31 additions and 7 deletions
|
|
@ -13,10 +13,13 @@ and this project adheres to
|
|||
### Changed
|
||||
|
||||
- 💄(frontend) improve comments highlights #1961
|
||||
- ♿️(frontend) improve BoxButton a11y and native button semantics
|
||||
#2103
|
||||
- ♿️(frontend) improve BoxButton a11y and native button semantics #2103
|
||||
- ♿️(frontend) improve language picker accessibility #2069
|
||||
|
||||
### Fixed
|
||||
|
||||
- 🐛(y-provider) destroy Y.Doc instances after each convert request #2129
|
||||
|
||||
## [v4.8.3] - 2026-03-23
|
||||
|
||||
### Changed
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ServerBlockNoteEditor } from '@blocknote/server-util';
|
||||
import request from 'supertest';
|
||||
import { describe, expect, test, vi } from 'vitest';
|
||||
import { afterEach, describe, expect, test, vi } from 'vitest';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
vi.mock('../src/env', async (importOriginal) => {
|
||||
|
|
@ -62,7 +62,11 @@ const expectedBlocks = [
|
|||
|
||||
console.error = vi.fn();
|
||||
|
||||
describe('Server Tests', () => {
|
||||
describe('Conversion Testing', () => {
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test('POST /api/convert with incorrect API key responds with 401', async () => {
|
||||
const app = initApp();
|
||||
|
||||
|
|
@ -170,6 +174,7 @@ describe('Server Tests', () => {
|
|||
});
|
||||
|
||||
test('POST /api/convert BlockNote to Yjs', async () => {
|
||||
const destroySpy = vi.spyOn(Y.Doc.prototype, 'destroy');
|
||||
const app = initApp();
|
||||
const editor = ServerBlockNoteEditor.create();
|
||||
const blocks = await editor.tryParseMarkdownToBlocks(expectedMarkdown);
|
||||
|
|
@ -192,6 +197,7 @@ describe('Server Tests', () => {
|
|||
const decodedBlocks = editor.yDocToBlocks(ydoc, 'document-store');
|
||||
|
||||
expect(decodedBlocks).toStrictEqual(expectedBlocks);
|
||||
expect(destroySpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('POST /api/convert BlockNote to HTML', async () => {
|
||||
|
|
@ -253,6 +259,7 @@ describe('Server Tests', () => {
|
|||
});
|
||||
|
||||
test('POST /api/convert Yjs to JSON', async () => {
|
||||
const destroySpy = vi.spyOn(Y.Doc.prototype, 'destroy');
|
||||
const app = initApp();
|
||||
const editor = ServerBlockNoteEditor.create();
|
||||
const blocks = await editor.tryParseMarkdownToBlocks(expectedMarkdown);
|
||||
|
|
@ -272,6 +279,7 @@ describe('Server Tests', () => {
|
|||
);
|
||||
expect(response.body).toBeInstanceOf(Array);
|
||||
expect(response.body).toStrictEqual(expectedBlocks);
|
||||
expect(destroySpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('POST /api/convert Markdown to JSON', async () => {
|
||||
|
|
@ -293,6 +301,7 @@ describe('Server Tests', () => {
|
|||
});
|
||||
|
||||
test('POST /api/convert with invalid Yjs content returns 400', async () => {
|
||||
const destroySpy = vi.spyOn(Y.Doc.prototype, 'destroy');
|
||||
const app = initApp();
|
||||
const response = await request(app)
|
||||
.post('/api/convert')
|
||||
|
|
@ -304,5 +313,6 @@ describe('Server Tests', () => {
|
|||
|
||||
expect(response.status).toBe(400);
|
||||
expect(response.body).toStrictEqual({ error: 'Invalid content' });
|
||||
expect(destroySpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -60,8 +60,12 @@ const readers: InputReader[] = [
|
|||
supportedContentTypes: [ContentTypes.YJS, ContentTypes.OctetStream],
|
||||
read: async (data) => {
|
||||
const ydoc = new Y.Doc();
|
||||
Y.applyUpdate(ydoc, data);
|
||||
return editor.yDocToBlocks(ydoc, 'document-store') as PartialBlock[];
|
||||
try {
|
||||
Y.applyUpdate(ydoc, data);
|
||||
return editor.yDocToBlocks(ydoc, 'document-store') as PartialBlock[];
|
||||
} finally {
|
||||
ydoc.destroy();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -77,7 +81,14 @@ const writers: OutputWriter[] = [
|
|||
},
|
||||
{
|
||||
supportedContentTypes: [ContentTypes.YJS, ContentTypes.OctetStream],
|
||||
write: async (blocks) => Y.encodeStateAsUpdate(createYDocument(blocks)),
|
||||
write: async (blocks) => {
|
||||
const ydoc = createYDocument(blocks);
|
||||
try {
|
||||
return Y.encodeStateAsUpdate(ydoc);
|
||||
} finally {
|
||||
ydoc.destroy();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
supportedContentTypes: [ContentTypes.Markdown, ContentTypes.XMarkdown],
|
||||
|
|
|
|||
Loading…
Reference in a new issue