Implemented statistics. Refactored storage abstractions. Added config options for docker and local storage. Added server service and server updating.

This commit is contained in:
2026-03-02 15:51:05 +00:00
parent 52dbd13fb5
commit 2d1b48b0d4
27 changed files with 493 additions and 147 deletions

View File

@@ -31,14 +31,15 @@ public class DockerConsole : IRuntimeConsole, IInstallConsole, IAsyncDisposable
Logger = logger;
}
public async Task AttachAsync()
{
// Fetch initial logs
Logger.LogTrace("Fetching pre-existing logs from container");
var logResponse = await DockerClient.Containers.GetContainerLogsAsync(
// Fetch initial container logs
Logger.LogTrace("Fetching initial container logs");
using var logStream = await DockerClient.Containers.GetContainerLogsAsync(
ContainerId,
new()
new ContainerLogsParameters()
{
Follow = false,
ShowStderr = true,
@@ -46,94 +47,50 @@ public class DockerConsole : IRuntimeConsole, IInstallConsole, IAsyncDisposable
}
);
// Append to cache
var logs = await logResponse.ReadOutputToEndAsync(Cts.Token);
// and process it
await ProcessStreamAsync(logStream, Cts.Token);
await CacheLock.WaitAsync(Cts.Token);
try
{
Cache.Add(logs.stdout);
Cache.Add(logs.stderr);
}
finally
{
CacheLock.Release();
}
// Stream new logs
Logger.LogTrace("Starting log streaming");
// After that we can actually start streaming the new logs
Logger.LogTrace("Attaching to container");
Stream = await DockerClient.Containers.AttachContainerAsync(
ContainerId,
new ContainerAttachParameters()
{
Stderr = true,
Stdin = true,
Stdout = true,
Stream = true
},
Cts.Token
);
Task.Run(async () =>
{
var capturedCt = Cts.Token;
Logger.LogTrace("Entered streaming loop");
Logger.LogTrace("Starting attach loop");
while (!capturedCt.IsCancellationRequested)
while (!Cts.IsCancellationRequested)
{
try
{
using var stream = await DockerClient.Containers.AttachContainerAsync(
ContainerId,
new ContainerAttachParameters()
{
Stderr = true,
Stdin = true,
Stdout = true,
Stream = true
},
capturedCt
);
// Make stream accessible from the outside
Stream = stream;
const int bufferSize = 1024;
var buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
while (!capturedCt.IsCancellationRequested)
if (Stream == null) // Triggers when e.g. a connection issue occurs cause the catch clause resets the stream
{
try
{
var readResult = await stream.ReadOutputAsync(buffer, 0, bufferSize, capturedCt);
Logger.LogTrace("Reattaching to container");
if (readResult.Count > 0)
Stream = await DockerClient.Containers.AttachContainerAsync(
ContainerId,
new ContainerAttachParameters()
{
var decodedBuffer = Encoding.UTF8.GetString(buffer, 0, readResult.Count);
await CacheLock.WaitAsync(capturedCt);
try
{
if (Cache.Count > 300)
Cache.RemoveRange(0, 50);
Cache.Add(decodedBuffer);
}
finally
{
CacheLock.Release();
}
if (OnOutput != null)
await OnOutput.Invoke(decodedBuffer);
}
if (readResult.EOF)
break;
}
catch (OperationCanceledException)
{
// Ignored
}
catch (Exception e)
{
Logger.LogError(e, "An unhandled error occured while processing container stream");
}
Stderr = true,
Stdin = true,
Stdout = true,
Stream = true
},
Cts.Token
);
}
ArrayPool<byte>.Shared.Return(buffer);
await ProcessStreamAsync(Stream, Cts.Token);
}
catch (OperationCanceledException)
{
@@ -141,14 +98,57 @@ public class DockerConsole : IRuntimeConsole, IInstallConsole, IAsyncDisposable
}
catch (Exception e)
{
Logger.LogError(e, "An unhandled error occured while handling container attaching");
Logger.LogError(e, "An unhandled error occured while processing container stream");
}
finally
{
Stream?.Dispose();
Stream = null;
}
}
Logger.LogTrace("Attach loop exited");
Logger.LogTrace("Exited streaming loop");
});
}
private async Task ProcessStreamAsync(MultiplexedStream stream, CancellationToken cancellationToken)
{
const int bufferSize = 1024;
var buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
while (!cancellationToken.IsCancellationRequested)
{
var readResult = await stream.ReadOutputAsync(buffer, 0, bufferSize, cancellationToken);
if (readResult.Count > 0)
{
var decodedBuffer = Encoding.UTF8.GetString(buffer, 0, readResult.Count);
await CacheLock.WaitAsync(cancellationToken);
try
{
if (Cache.Count > 300)
Cache.RemoveRange(0, 50);
Cache.Add(decodedBuffer);
}
finally
{
CacheLock.Release();
}
if (OnOutput != null)
await OnOutput.Invoke(decodedBuffer);
}
if (readResult.EOF)
break;
}
ArrayPool<byte>.Shared.Return(buffer);
}
public async Task WriteInputAsync(string value)
{
if (Stream == null)
@@ -188,6 +188,8 @@ public class DockerConsole : IRuntimeConsole, IInstallConsole, IAsyncDisposable
public async ValueTask DisposeAsync()
{
Logger.LogTrace("Disposing");
await Cts.CancelAsync();
Stream?.Dispose();
CacheLock.Dispose();