Skip to content

Commit

Permalink
Simplify implementation of the public web host
Browse files Browse the repository at this point in the history
Remove all dependencies on the persistent process from the public app implementation. It looks like the public web interface will move into a volatile host mid-term anyway. With that constellation, it is also easier to see why the functionality for persistence does not belong in there.
  • Loading branch information
Viir committed May 3, 2020
1 parent 620f80e commit dad600a
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 216 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace Kalmit.PersistentProcess.Test
public class TestWebHost
{
[TestMethod]
public void Web_host_stores_reduction_every_hour()
public void Web_host_stores_process_history_reduction_every_hour_by_default()
{
var persistentProcessHostDateTime = new DateTimeOffset(2018, 11, 4, 8, 17, 13, TimeSpan.Zero);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
using Kalmit.PersistentProcess.WebHost;
using Microsoft.AspNetCore.Hosting;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ namespace Kalmit.PersistentProcess.WebHost
{
public class Program
{
static public string AppVersionId => "2020-05-02";
static public string AppVersionId => "2020-05-03";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@ public class StartupAdminInterface

static public string PathApiGetDeployedAppConfig => "/api/get-deployed-app-config";

public StartupAdminInterface()
private readonly ILogger<StartupAdminInterface> logger;

public StartupAdminInterface(ILogger<StartupAdminInterface> logger)
{
this.logger = logger;
}

public void ConfigureServices(IServiceCollection services)
Expand All @@ -45,7 +48,7 @@ public void ConfigureServices(IServiceCollection services)

class PublicHostConfiguration
{
public SyncPersistentProcess syncPersistentProcess;
public PersistentProcess.PersistentProcessVolatileRepresentation processVolatileRepresentation;

public IWebHost webHost;
}
Expand Down Expand Up @@ -80,6 +83,7 @@ void stopPublicApp()
{
publicAppHost?.webHost?.StopAsync(TimeSpan.FromSeconds(10)).Wait();
publicAppHost?.webHost?.Dispose();
publicAppHost?.processVolatileRepresentation?.Dispose();
publicAppHost = null;
}
}
Expand Down Expand Up @@ -108,21 +112,73 @@ void startPublicApp()

var newPublicAppConfig = new PublicHostConfiguration { };

var processStoreReader =
new ProcessStoreSupportingMigrations.ProcessStoreReaderInFileStore(processStoreFileStore);
logger.LogInformation("Begin to build the process volatile representation.");

var processVolatileRepresentation =
PersistentProcess.PersistentProcessVolatileRepresentation.Restore(
new ProcessStoreSupportingMigrations.ProcessStoreReaderInFileStore(processStoreFileStore),
logger: logEntry => logger.LogInformation(logEntry));

logger.LogInformation("Completed building the process volatile representation.");

var cyclicReductionStoreLock = new object();
DateTimeOffset? cyclicReductionStoreLastTime = null;
var cyclicReductionStoreDistanceSeconds = (int)TimeSpan.FromHours(1).TotalSeconds;

using (var restoredProcess = PersistentProcess.PersistentProcessVolatileRepresentation.Restore(
processStoreReader,
_ => { }))
void maintainStoreReductions()
{
var appConfigComponent =
restoredProcess?.lastAppConfig?.appConfigComponent;
var currentDateTime = getDateTimeOffset();

var webHost =
appConfigComponent == null
?
null
:
System.Threading.Thread.MemoryBarrier();
var cyclicReductionStoreLastAge = currentDateTime - cyclicReductionStoreLastTime;

if (!(cyclicReductionStoreLastAge?.TotalSeconds < cyclicReductionStoreDistanceSeconds))
{
if (System.Threading.Monitor.TryEnter(cyclicReductionStoreLock))
{
try
{
var afterLockCyclicReductionStoreLastAge = currentDateTime - cyclicReductionStoreLastTime;

if (afterLockCyclicReductionStoreLastAge?.TotalSeconds < cyclicReductionStoreDistanceSeconds)
return;

lock (processStoreWriter)
{
var reductionRecord = processVolatileRepresentation.StoreReductionRecordForCurrentState(processStoreWriter);
}

cyclicReductionStoreLastTime = currentDateTime;
System.Threading.Thread.MemoryBarrier();
}
finally
{
System.Threading.Monitor.Exit(cyclicReductionStoreLock);
}
}
}
}

var webHost =
processVolatileRepresentation?.lastAppConfig?.appConfigComponent == null
?
null
:
buildWebHost();

IWebHost buildWebHost()
{
var appConfigTree = Composition.ParseAsTree(
processVolatileRepresentation.lastAppConfig.Value.appConfigComponent).Ok;

var appConfigFilesNamesAndContents =
appConfigTree.EnumerateBlobsTransitive()
.Select(blobPathAndContent => (
fileName: (IImmutableList<string>)blobPathAndContent.path.Select(name => System.Text.Encoding.UTF8.GetString(name.ToArray())).ToImmutableList(),
fileContent: blobPathAndContent.blobContent))
.ToImmutableList();

return
Microsoft.AspNetCore.WebHost.CreateDefaultBuilder()
.ConfigureLogging((hostingContext, logging) =>
{
Expand All @@ -142,38 +198,41 @@ void startPublicApp()
.WithSettingDateTimeOffsetDelegate(getDateTimeOffset)
.ConfigureServices(services =>
{
services.AddSingleton<ProcessStoreSupportingMigrations.IProcessStoreReader>(processStoreReader);
services.AddSingleton<ProcessStoreSupportingMigrations.IProcessStoreWriter>(processStoreWriter);
services.AddSingleton<WebAppAndElmAppConfig>(
new WebAppAndElmAppConfig
{
WebAppConfiguration = WebAppConfiguration.FromFiles(appConfigFilesNamesAndContents),
ProcessEventInElmApp = serializedEvent =>
{
lock (processStoreWriter)
{
lock (publicAppLock)
{
var elmEventResponse =
processVolatileRepresentation.ProcessElmAppEvents(
processStoreWriter, new[] { serializedEvent }).Single();

maintainStoreReductions();

return elmEventResponse;
}
}
}
});
})
.ConfigureServices(services => services.AddSingleton(new PersistentProcessMap
{
mapPersistentProcess = originalPersistentProcess =>
{
return newPublicAppConfig.syncPersistentProcess = new SyncPersistentProcess(originalPersistentProcess);
}
}))
.Build();
}

newPublicAppConfig.webHost = webHost;
newPublicAppConfig.processVolatileRepresentation = processVolatileRepresentation;
newPublicAppConfig.webHost = webHost;

webHost?.StartAsync(appLifetime.ApplicationStopping).Wait();
publicAppHost = newPublicAppConfig;
}
webHost?.StartAsync(appLifetime.ApplicationStopping).Wait();
publicAppHost = newPublicAppConfig;
}
}

startPublicApp();

Composition.Component getPublicAppConfigFromStore()
{
using (var restoredProcess = PersistentProcess.PersistentProcessVolatileRepresentation.Restore(
new ProcessStoreSupportingMigrations.ProcessStoreReaderInFileStore(processStoreFileStore),
_ => { }))
{
return restoredProcess?.lastAppConfig?.appConfigComponent;
}
}

app.Run(async (context) =>
{
var syncIOFeature = context.Features.Get<Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature>();
Expand Down Expand Up @@ -227,7 +286,7 @@ Composition.Component getPublicAppConfigFromStore()
return;
}

var appConfig = getPublicAppConfigFromStore();
var appConfig = publicAppHost?.processVolatileRepresentation?.lastAppConfig?.appConfigComponent;

if (appConfig == null)
{
Expand Down Expand Up @@ -333,9 +392,9 @@ Composition.Component getPublicAppConfigFromStore()

if (string.Equals(context.Request.Method, "get", StringComparison.InvariantCultureIgnoreCase))
{
var syncPersistentProcess = publicAppHost?.syncPersistentProcess;
var processVolatileRepresentation = publicAppHost?.processVolatileRepresentation;

if (syncPersistentProcess == null)
if (processVolatileRepresentation == null)
{
context.Response.StatusCode = 500;
await context.Response.WriteAsync("Not possible because there is no Elm app deployed at the moment.");
Expand All @@ -352,7 +411,7 @@ Composition.Component getPublicAppConfigFromStore()
};

var reductionRecord =
syncPersistentProcess.StoreReductionRecordForCurrentState(storeWriter);
processVolatileRepresentation.StoreReductionRecordForCurrentState(storeWriter);

var elmAppStateReductionHashBase16 = reductionRecord.elmAppState?.HashBase16;

Expand Down Expand Up @@ -448,42 +507,4 @@ await context.Response.WriteAsync(
});
}
}

public class SyncPersistentProcess : PersistentProcess.IPersistentProcess
{
readonly object @lock = new object();

readonly PersistentProcess.IPersistentProcess persistentProcess;

public SyncPersistentProcess(PersistentProcess.IPersistentProcess persistentProcess)
{
this.persistentProcess = persistentProcess;
}

public void RunInLock(Action<PersistentProcess.IPersistentProcess> action)
{
lock (@lock)
{
action(persistentProcess);
}
}

public IImmutableList<string> ProcessElmAppEvents(
ProcessStoreSupportingMigrations.IProcessStoreWriter storeWriter, IReadOnlyList<string> serializedEvents)
{
lock (@lock)
{
return persistentProcess.ProcessElmAppEvents(storeWriter, serializedEvents);
}
}

public ProcessStoreSupportingMigrations.ProvisionalReductionRecordInFile StoreReductionRecordForCurrentState(
ProcessStoreSupportingMigrations.IProcessStoreWriter storeWriter)
{
lock (@lock)
{
return persistentProcess.StoreReductionRecordForCurrentState(storeWriter);
}
}
}
}
Loading

0 comments on commit dad600a

Please sign in to comment.