From d672900def5afd45f978d757056ca23d3da2ec01 Mon Sep 17 00:00:00 2001
From: Daniel Lerch <36048059+daniel-lerch@users.noreply.github.com>
Date: Mon, 10 Jun 2024 22:42:11 +0200
Subject: [PATCH 1/2] Fix memory leak at outbox email queueing (#66)
---
.../EmailDelivery/EmailDeliveryJobController.cs | 6 ++----
server/Korga/EmailDelivery/EmailDeliveryService.cs | 11 +++++++++--
server/Korga/EmailRelay/EmailRelayJobController.cs | 14 +++++++++-----
3 files changed, 20 insertions(+), 11 deletions(-)
diff --git a/server/Korga/EmailDelivery/EmailDeliveryJobController.cs b/server/Korga/EmailDelivery/EmailDeliveryJobController.cs
index 1a2da18..e31fef5 100644
--- a/server/Korga/EmailDelivery/EmailDeliveryJobController.cs
+++ b/server/Korga/EmailDelivery/EmailDeliveryJobController.cs
@@ -38,10 +38,8 @@ protected override async ValueTask ExecuteJob(OutboxEmail outboxEmail, Cancellat
{
SmtpClient smtp = await GetConnection(cancellationToken);
- MimeMessage mimeMessage;
-
- using (MemoryStream memoryStream = new(outboxEmail.Content))
- mimeMessage = MimeMessage.Load(memoryStream, CancellationToken.None);
+ using MemoryStream memoryStream = new(outboxEmail.Content);
+ using MimeMessage mimeMessage = MimeMessage.Load(memoryStream, CancellationToken.None);
try
{
diff --git a/server/Korga/EmailDelivery/EmailDeliveryService.cs b/server/Korga/EmailDelivery/EmailDeliveryService.cs
index 8ce5e75..b3f725d 100644
--- a/server/Korga/EmailDelivery/EmailDeliveryService.cs
+++ b/server/Korga/EmailDelivery/EmailDeliveryService.cs
@@ -1,4 +1,5 @@
-using Korga.Utilities;
+using Korga.EmailDelivery.Entities;
+using Korga.Utilities;
using Microsoft.EntityFrameworkCore;
using MimeKit;
using System;
@@ -35,8 +36,14 @@ public async ValueTask Enqueue(string emailAddress, MimeMessage mimeMessag
content = memoryStream.ToArray();
}
- database.OutboxEmails.Add(new(emailAddress, content) { InboxEmailId = inboxEmailId });
+ OutboxEmail outboxEmail = new(emailAddress, content) { InboxEmailId = inboxEmailId };
+ database.OutboxEmails.Add(outboxEmail);
await database.SaveChangesAsync(cancellationToken);
+
+ // Explicitly free entity for garbage collection because our DbContext won't be disposed soon enough
+ // Without this line, Korga takes gigabytes of memory when sending large messages to many recipients
+ database.Entry(outboxEmail).State = EntityState.Detached;
+
jobQueue.EnsureRunning();
return true;
}
diff --git a/server/Korga/EmailRelay/EmailRelayJobController.cs b/server/Korga/EmailRelay/EmailRelayJobController.cs
index 4e06427..f68a01a 100644
--- a/server/Korga/EmailRelay/EmailRelayJobController.cs
+++ b/server/Korga/EmailRelay/EmailRelayJobController.cs
@@ -40,7 +40,8 @@ protected override async ValueTask ExecuteJob(InboxEmail email, CancellationToke
{
if (email.Receiver == null)
{
- await SendErrorMessage(email, emailRelay.InvalidServerConfiguration(email), cancellationToken);
+ using MimeMessage? errorMessage = emailRelay.InvalidServerConfiguration(email);
+ await SendErrorMessage(email, errorMessage, cancellationToken);
logger.LogWarning("Could not determine receiver for message #{Id} from {From} to {To}. This message will not be forwarded." +
"Please make sure your email provider specifies the receiver in the Received, Envelope-To, or X-Envelope-To header", email.Id, email.From, email.To);
@@ -54,7 +55,8 @@ protected override async ValueTask ExecuteJob(InboxEmail email, CancellationToke
if (distributionList == null)
{
- await SendErrorMessage(email, emailRelay.InvalidAlias(email), cancellationToken);
+ using MimeMessage? errorMessage = emailRelay.InvalidAlias(email);
+ await SendErrorMessage(email, errorMessage, cancellationToken);
logger.LogInformation("No group found with alias {Receiver} for email #{Id} from {From}", email.Receiver, email.Id, email.From);
return;
@@ -62,7 +64,8 @@ protected override async ValueTask ExecuteJob(InboxEmail email, CancellationToke
if (email.Header == null)
{
- await SendErrorMessage(email, emailRelay.TooManyHeaders(email), cancellationToken);
+ using MimeMessage? errorMessage = emailRelay.TooManyHeaders(email);
+ await SendErrorMessage(email, errorMessage, cancellationToken);
logger.LogInformation("Email #{Id} from {From} to {Receiver} exceeded the header size limit", email.Id, email.From, email.Receiver);
return;
@@ -70,7 +73,8 @@ protected override async ValueTask ExecuteJob(InboxEmail email, CancellationToke
if (email.Body == null)
{
- await SendErrorMessage(email, emailRelay.TooBigMessage(email), cancellationToken);
+ using MimeMessage? errorMessage = emailRelay.TooBigMessage(email);
+ await SendErrorMessage(email, errorMessage, cancellationToken);
logger.LogInformation("Email #{Id} from {From} to {Receiver} exceeded the body size limit", email.Id, email.From, email.Receiver);
return;
@@ -79,7 +83,7 @@ protected override async ValueTask ExecuteJob(InboxEmail email, CancellationToke
MailboxAddress[] recipients = await distributionListService.GetRecipients(distributionList, cancellationToken);
foreach (MailboxAddress address in recipients)
{
- MimeMessage preparedMessage = distributionList.Flags.HasFlag(DistributionListFlags.Newsletter)
+ using MimeMessage preparedMessage = distributionList.Flags.HasFlag(DistributionListFlags.Newsletter)
? await emailRelay.PrepareForForwardTo(email, address, cancellationToken)
: emailRelay.PrepareForResentTo(email, address);
await emailDelivery.Enqueue(address.Address, preparedMessage, email.Id, cancellationToken);
From 6c8e3187e59744c0e8d82f243cb548180366d69a Mon Sep 17 00:00:00 2001
From: Daniel Lerch <36048059+daniel-lerch@users.noreply.github.com>
Date: Mon, 1 Jul 2024 09:24:43 +0200
Subject: [PATCH 2/2] Migrate from webpack to Vite (#67)
* Migrate from webpack to Vite
* Fix SPA hosting unit tests
* Add icon and update README
* Move Prettier config into own file
This is required for npm run format to use the correct config like ESLint
---
README.md | 2 +-
server/Korga.Tests/Http/VueSpaTests.cs | 2 +-
server/Korga/Utilities/VueSpaFileProvider.cs | 16 +-
server/Korga/appsettings.Development.json | 2 +-
server/Korga/wwwroot/index.html | 2 +-
webapp/.env.development | 2 +-
webapp/{.eslintrc.js => .eslintrc.cjs} | 17 +-
webapp/.prettierrc.json | 8 +
webapp/env.d.ts | 1 +
webapp/{public => }/index.html | 9 +-
webapp/package-lock.json | 13889 +++--------------
webapp/package.json | 59 +-
webapp/public/brand.png | Bin 0 -> 2687 bytes
webapp/src/App.vue | 26 +-
webapp/src/components/LoadingSpinner.vue | 5 +-
webapp/src/components/ProfileNav.vue | 25 +-
webapp/src/custom_styles.scss | 10 +-
webapp/src/main.ts | 12 +-
webapp/src/router/index.ts | 19 +-
webapp/src/services/client.ts | 43 +-
webapp/src/services/distribution-list.ts | 26 +-
webapp/src/services/profile.ts | 26 +-
webapp/src/services/service.ts | 22 +-
webapp/src/shims-vue.d.ts | 5 -
webapp/src/views/DistributionLists.vue | 44 +-
webapp/src/views/ServiceList.vue | 26 +-
webapp/src/window.d.ts | 2 +-
webapp/tsconfig.app.json | 14 +
webapp/tsconfig.json | 43 +-
webapp/tsconfig.node.json | 19 +
webapp/vite.config.ts | 21 +
webapp/vue.config.js | 11 -
32 files changed, 2492 insertions(+), 11916 deletions(-)
rename webapp/{.eslintrc.js => .eslintrc.cjs} (69%)
create mode 100644 webapp/.prettierrc.json
create mode 100644 webapp/env.d.ts
rename webapp/{public => }/index.html (61%)
create mode 100644 webapp/public/brand.png
delete mode 100644 webapp/src/shims-vue.d.ts
create mode 100644 webapp/tsconfig.app.json
create mode 100644 webapp/tsconfig.node.json
create mode 100644 webapp/vite.config.ts
delete mode 100644 webapp/vue.config.js
diff --git a/README.md b/README.md
index d82a0f6..143e513 100644
--- a/README.md
+++ b/README.md
@@ -170,5 +170,5 @@ During development the frontend running on the Vue CLI development server will u
That means the backend can be running in Visual Studio with Debugger attached.
If you just want to work on the frontend you can also use a public test server by creating a file `webapp/.env.development.local`
-to override the defaults with `VUE_APP_API_URL=https://lerchen.net/korga`.
+to override the defaults with `VITE_API_URL=https://lerchen.net/korga`.
Then you don't have to setup a database server and the ASP.NET Core backend.
diff --git a/server/Korga.Tests/Http/VueSpaTests.cs b/server/Korga.Tests/Http/VueSpaTests.cs
index 4ec62bf..105429d 100644
--- a/server/Korga.Tests/Http/VueSpaTests.cs
+++ b/server/Korga.Tests/Http/VueSpaTests.cs
@@ -23,6 +23,6 @@ public async Task TestVueEntrypoint(string pathbase, string url)
Assert.Equal("text/html", response.Content.Headers.ContentType?.MediaType);
string body = await response.Content.ReadAsStringAsync();
Assert.True(body.StartsWith(""), "Body is missing DOCTYPE");
- Assert.True(body.Contains($"window.resourceBasePath = '{pathbase}/'"), "Body is missing pathbase");
+ Assert.True(body.Contains($"window.basePath = '{pathbase}/'"), "Body is missing pathbase");
}
}
diff --git a/server/Korga/Utilities/VueSpaFileProvider.cs b/server/Korga/Utilities/VueSpaFileProvider.cs
index 3592337..e01980b 100644
--- a/server/Korga/Utilities/VueSpaFileProvider.cs
+++ b/server/Korga/Utilities/VueSpaFileProvider.cs
@@ -66,20 +66,8 @@ public IFileInfo GetFileInfo(string subpath)
private MemoryFileInfo CreateCache(DateTime lastWrite, string? pathBase)
{
- byte[] buffer;
-
- if (string.IsNullOrEmpty(pathBase))
- {
- buffer = File.ReadAllBytes(path);
- }
- else
- {
- buffer = Encoding.UTF8.GetBytes(
- File.ReadAllText(path)
- .Replace("\"/", $"\"{pathBase}/")
- .Replace("'/'", $"'{pathBase}/'")
- );
- }
+ byte[] buffer = Encoding.UTF8.GetBytes(
+ File.ReadAllText(path).Replace("/__base_path__/", $"{pathBase}/"));
return new MemoryFileInfo("index.html", buffer, lastWrite, pathBase);
}
diff --git a/server/Korga/appsettings.Development.json b/server/Korga/appsettings.Development.json
index 99d607e..0e13a3b 100644
--- a/server/Korga/appsettings.Development.json
+++ b/server/Korga/appsettings.Development.json
@@ -4,7 +4,7 @@
},
"Hosting": {
"CorsOrigins": [
- "http://localhost:8080"
+ "http://localhost:5173"
]
},
"Logging": {
diff --git a/server/Korga/wwwroot/index.html b/server/Korga/wwwroot/index.html
index 7b9bdd4..a694434 100644
--- a/server/Korga/wwwroot/index.html
+++ b/server/Korga/wwwroot/index.html
@@ -48,7 +48,7 @@ Korga Backend