Skip to content

Commit f6c0c50

Browse files
committed
chore: concurrent logic and improved tests
1 parent d7b1790 commit f6c0c50

File tree

2 files changed

+30
-11
lines changed

2 files changed

+30
-11
lines changed

src/kernels/deepnote/deepnoteServerStarter.node.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -499,14 +499,16 @@ export class DeepnoteServerStarter implements IDeepnoteServerStarter, IExtension
499499
* @returns Object with jupyterPort and lspPort
500500
*/
501501
private async allocatePorts(key: string): Promise<{ jupyterPort: number; lspPort: number }> {
502-
// Wait for any ongoing port allocation to complete
503-
await this.portAllocationLock;
504-
505-
// Create new allocation promise and update the lock
502+
// Chain onto the existing lock promise to serialize allocations even when multiple calls start concurrently
503+
const previousLock = this.portAllocationLock;
506504
let releaseLock: () => void;
507-
this.portAllocationLock = new Promise((resolve) => {
505+
const currentLock = new Promise<void>((resolve) => {
508506
releaseLock = resolve;
509507
});
508+
this.portAllocationLock = previousLock.then(() => currentLock);
509+
510+
// Wait until all prior allocations have completed before proceeding
511+
await previousLock;
510512

511513
try {
512514
// Get all ports currently in use by our managed servers
@@ -547,7 +549,7 @@ export class DeepnoteServerStarter implements IDeepnoteServerStarter, IExtension
547549

548550
return { jupyterPort, lspPort };
549551
} finally {
550-
// Release the lock to allow next allocation
552+
// Release the lock to allow next allocation in the chain to proceed
551553
releaseLock!();
552554
}
553555
}

src/kernels/deepnote/deepnoteServerStarter.unit.test.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ suite('DeepnoteServerStarter - Port Allocation Integration Tests', () => {
5353
);
5454
});
5555

56+
teardown(async () => {
57+
// Dispose the serverStarter to clean up any allocated ports and state
58+
if (serverStarter) {
59+
await serverStarter.dispose();
60+
}
61+
});
62+
5663
suite('isPortAvailable', () => {
5764
let checkStub: sinon.SinonStub;
5865

@@ -344,7 +351,13 @@ suite('DeepnoteServerStarter - Port Allocation Integration Tests', () => {
344351
// if Jupyter port was available but LSP port (Jupyter+1) was not,
345352
// the system would allocate non-consecutive ports causing server hangs
346353

347-
const keys = ['regression-1', 'regression-2', 'regression-3'];
354+
// Use unique keys with timestamp to avoid conflicts with other tests
355+
const timestamp = Date.now();
356+
const keys = [
357+
`concurrent-test-${timestamp}-1`,
358+
`concurrent-test-${timestamp}-2`,
359+
`concurrent-test-${timestamp}-3`
360+
];
348361

349362
// eslint-disable-next-line @typescript-eslint/no-explicit-any
350363
const allocatePorts = getPrivateMethod(serverStarter as any, 'allocatePorts');
@@ -437,21 +450,25 @@ suite('DeepnoteServerStarter - Port Allocation Integration Tests', () => {
437450
test('should throw DeepnoteServerStartupError when max attempts exhausted', async () => {
438451
// This test covers the scenario where we cannot find consecutive ports
439452
// after maxAttempts (100 attempts). This should throw a DeepnoteServerStartupError.
453+
// Strategy: Block every other port so individual ports are available,
454+
// but no consecutive pairs exist (blocking the +1 port for each available port)
440455

441456
const net = require('net');
442457
const servers: any[] = [];
443458

444459
try {
445-
// Block a large range of ports to make it impossible to find consecutive pairs
460+
// Block every other port starting from 55001 (leaving 55000, 55002, 55004, etc. available)
461+
// This ensures findAvailablePort succeeds, but the consecutive port is always blocked
446462
const startPort = 55000;
447-
const portsToBlock = 210; // More than maxAttempts (100) * 2
463+
const portsToBlock = 200; // Block 200 odd-numbered ports
448464

449-
// Create servers blocking many ports
465+
// Create servers blocking every other port (the +1 ports)
450466
for (let i = 0; i < portsToBlock; i++) {
467+
const portToBlock = startPort + i * 2 + 1; // Block 55001, 55003, 55005, etc.
451468
const server = net.createServer();
452469
servers.push(server);
453470
await new Promise<void>((resolve) => {
454-
server.listen(startPort + i, 'localhost', () => resolve());
471+
server.listen(portToBlock, 'localhost', () => resolve());
455472
});
456473
}
457474

0 commit comments

Comments
 (0)