Implemented statistics. Refactored storage abstractions. Added config options for docker and local storage. Added server service and server updating.
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user