Browse Source

fixed build and worked on memory leak

master
silberengel 9 months ago
parent
commit
27fdbde8df
  1. 9
      Dockerfile
  2. 10
      deno.json
  3. 19
      import_map.json
  4. 22
      src/lib/data_structures/websocket_pool.ts
  5. 35
      src/lib/ndk.ts
  6. 10
      src/routes/+layout.svelte

9
Dockerfile

@ -1,6 +1,11 @@ @@ -1,6 +1,11 @@
FROM denoland/deno:alpine AS build
WORKDIR /app/src
COPY . .
# Set memory limits for Deno to prevent memory leaks
ENV DENO_MEMORY_LIMIT=512MB
ENV DENO_GC_INTERVAL=1000
RUN deno install
RUN deno task build
@ -11,6 +16,10 @@ COPY --from=build /app/src/import_map.json . @@ -11,6 +16,10 @@ COPY --from=build /app/src/import_map.json .
ENV ORIGIN=http://localhost:3000
# Set memory limits for runtime to prevent memory leaks
ENV DENO_MEMORY_LIMIT=512MB
ENV DENO_GC_INTERVAL=1000
RUN deno cache --import-map=import_map.json ./build/index.js
EXPOSE 3000

10
deno.json

@ -2,5 +2,15 @@ @@ -2,5 +2,15 @@
"importMap": "./import_map.json",
"compilerOptions": {
"lib": ["dom", "dom.iterable", "dom.asynciterable", "deno.ns"]
},
"tasks": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write .",
"test": "vitest"
}
}

19
import_map.json

@ -2,18 +2,25 @@ @@ -2,18 +2,25 @@
"imports": {
"he": "npm:he@1.2.x",
"@nostr-dev-kit/ndk": "npm:@nostr-dev-kit/ndk@^2.14.32",
"@nostr-dev-kit/ndk-cache-dexie": "npm:@nostr-dev-kit/ndk-cache-dexie@^2.6.33",
"@nostr-dev-kit/ndk-cache-dexie": "npm:@nostr-dev-kit/ndk-cache-dexie@2.6.x",
"@popperjs/core": "npm:@popperjs/core@2.11.x",
"@tailwindcss/forms": "npm:@tailwindcss/forms@0.5.x",
"@tailwindcss/typography": "npm:@tailwindcss/typography@0.5.x",
"asciidoctor": "npm:asciidoctor@3.0.x",
"d3": "npm:d3@7.9.x",
"nostr-tools": "npm:nostr-tools@^2.15.1",
"d3": "npm:d3@^7.9.0",
"nostr-tools": "npm:nostr-tools@2.15.x",
"tailwind-merge": "npm:tailwind-merge@^3.3.1",
"svelte": "npm:svelte@^5.36.8",
"flowbite": "npm:flowbite@^3.1.2",
"flowbite-svelte": "npm:flowbite-svelte@^1.10.10",
"flowbite-svelte-icons": "npm:flowbite-svelte-icons@^2.2.1",
"flowbite": "npm:flowbite@2.x",
"flowbite-svelte": "npm:flowbite-svelte@0.48.x",
"flowbite-svelte-icons": "npm:flowbite-svelte-icons@2.1.x",
"@noble/curves": "npm:@noble/curves@^1.9.4",
"@noble/hashes": "npm:@noble/hashes@^1.8.0",
"bech32": "npm:bech32@^2.0.0",
"highlight.js": "npm:highlight.js@^11.11.1",
"node-emoji": "npm:node-emoji@^2.2.0",
"plantuml-encoder": "npm:plantuml-encoder@^1.4.0",
"qrcode": "npm:qrcode@^1.5.4",
"child_process": "node:child_process"
}
}

22
src/lib/data_structures/websocket_pool.ts

@ -191,20 +191,27 @@ export class WebSocketPool { @@ -191,20 +191,27 @@ export class WebSocketPool {
* Closes all WebSocket connections and "drains" the pool.
*/
public drain(): void {
console.debug(`[WebSocketPool] Draining pool with ${this.#pool.size} connections and ${this.#waitingQueue.length} waiting requests`);
// Clear all idle timers first
for (const handle of this.#pool.values()) {
this.#clearIdleTimer(handle);
}
// Reject all waiting requests
for (const { reject } of this.#waitingQueue) {
reject(new Error('[WebSocketPool] Draining pool.'));
}
this.#waitingQueue = [];
// Close all connections and clean up
for (const handle of this.#pool.values()) {
if (handle.ws && handle.ws.readyState === WebSocket.OPEN) {
handle.ws.close();
}
}
this.#pool.clear();
console.debug('[WebSocketPool] Pool drained successfully');
}
// #endregion
@ -243,8 +250,18 @@ export class WebSocketPool { @@ -243,8 +250,18 @@ export class WebSocketPool {
#removeSocket(handle: WebSocketHandle): void {
this.#clearIdleTimer(handle);
handle.ws.onopen = handle.ws.onerror = handle.ws.onclose = null;
this.#pool.delete(this.#normalizeUrl(handle.ws.url));
// Clean up event listeners to prevent memory leaks
if (handle.ws) {
handle.ws.onopen = null;
handle.ws.onerror = null;
handle.ws.onclose = null;
handle.ws.onmessage = null;
}
const url = this.#normalizeUrl(handle.ws.url);
this.#pool.delete(url);
console.debug(`[WebSocketPool] Removed socket for ${url}, pool size: ${this.#pool.size}`);
this.#processWaitingQueue();
}
@ -261,6 +278,7 @@ export class WebSocketPool { @@ -261,6 +278,7 @@ export class WebSocketPool {
handle.idleTimer = setTimeout(() => {
const refCount = handle.refCount;
if (refCount === 0 && handle.ws.readyState === WebSocket.OPEN) {
console.debug(`[WebSocketPool] Closing idle connection to ${handle.ws.url}`);
handle.ws.close();
this.#removeSocket(handle);
}

35
src/lib/ndk.ts

@ -654,7 +654,10 @@ export function initNdk(): NDK { @@ -654,7 +654,10 @@ export function initNdk(): NDK {
if (retryCount < maxRetries) {
retryCount++;
console.debug(`[NDK.ts] Attempting to reconnect (${retryCount}/${maxRetries})...`);
setTimeout(attemptConnection, 2000); // Reduce timeout to 2 seconds
// Use a more reasonable retry delay and prevent memory leaks
setTimeout(() => {
attemptConnection();
}, 2000 * retryCount); // Exponential backoff
} else {
console.warn("[NDK.ts] Max retries reached, continuing with limited functionality");
// Still try to update relay stores even if connection failed
@ -691,6 +694,36 @@ export function initNdk(): NDK { @@ -691,6 +694,36 @@ export function initNdk(): NDK {
return ndk;
}
/**
* Cleans up NDK resources to prevent memory leaks
* Should be called when the application is shutting down or when NDK needs to be reset
*/
export function cleanupNdk(): void {
console.debug("[NDK.ts] Cleaning up NDK resources");
const ndk = get(ndkInstance);
if (ndk) {
try {
// Disconnect from all relays
if (ndk.pool) {
for (const relay of ndk.pool.relays.values()) {
relay.disconnect();
}
}
// Drain the WebSocket pool
WebSocketPool.instance.drain();
// Stop network monitoring
stopNetworkStatusMonitoring();
console.debug("[NDK.ts] NDK cleanup completed");
} catch (error) {
console.warn("[NDK.ts] Error during NDK cleanup:", error);
}
}
}
/**
* Signs in with a NIP-07 browser extension using the new relay management system
* @returns The user's profile, if it is available

10
src/routes/+layout.svelte

@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
import { goto } from "$app/navigation";
import { Alert } from "flowbite-svelte";
import { HammerSolid } from "flowbite-svelte-icons";
import { logCurrentRelayConfiguration, activeInboxRelays, activeOutboxRelays } from "$lib/ndk";
import { logCurrentRelayConfiguration, activeInboxRelays, activeOutboxRelays, cleanupNdk } from "$lib/ndk";
// Define children prop for Svelte 5
let { children } = $props();
@ -83,7 +83,13 @@ @@ -83,7 +83,13 @@
}
document.addEventListener("click", handleInternalLinkClick);
return () => document.removeEventListener("click", handleInternalLinkClick);
// Cleanup function to prevent memory leaks
return () => {
document.removeEventListener("click", handleInternalLinkClick);
// Clean up NDK resources when component unmounts
cleanupNdk();
};
});
</script>

Loading…
Cancel
Save