Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions src/CoreTests/setting_solo_mode_in_test_support.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System.Threading.Tasks;
using JasperFx.Events.Daemon;
using Marten;
using Marten.Events.Daemon.Coordination;
using Marten.Testing.Harness;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Shouldly;
using Xunit;

namespace CoreTests;

public class setting_solo_mode_in_test_support
{
[Fact]
public async Task override_every_store_to_use_a_solo_async_daemon()
{
using var host = await Host.CreateDefaultBuilder()
.ConfigureServices(services =>
{
// Mostly just to prove we can mix and match
services.AddMarten(ConnectionSource.ConnectionString).AddAsyncDaemon(DaemonMode.HotCold);

services.AddMartenStore<IFirstStore>(opts =>
{
opts.Connection(ConnectionSource.ConnectionString);
opts.DatabaseSchemaName = "first_store";
}).AddAsyncDaemon(DaemonMode.HotCold);

services.AddMartenStore<ISecondStore>(services =>
{
var opts = new StoreOptions();
opts.Connection(ConnectionSource.ConnectionString);
opts.DatabaseSchemaName = "second_store";

return opts;
}).AddAsyncDaemon(DaemonMode.HotCold);

// Forget what the application says, let's make all the daemons run in solo mode!
services.MartenDaemonModeIsSolo();
}).StartAsync();

host.Services.GetRequiredService<IProjectionCoordinator>().Mode.ShouldBe(DaemonMode.Solo);
host.Services.GetRequiredService<IProjectionCoordinator<IFirstStore>>().Mode.ShouldBe(DaemonMode.Solo);
host.Services.GetRequiredService<IProjectionCoordinator<ISecondStore>>().Mode.ShouldBe(DaemonMode.Solo);
}
}
8 changes: 8 additions & 0 deletions src/Marten.AspNetCore.Testing/AppFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ public async Task InitializeAsync()
{
b.ConfigureServices((context, services) =>
{
// Important! You can make your test harness work a little faster (important on its own)
// and probably be more reliable by overriding your Marten configuration to run all
// async daemons in "Solo" mode so they spin up faster and there's no issues from
// PostgreSQL having trouble with advisory locks when projections are rapidly started and stopped

// This was added in V8.8
services.MartenDaemonModeIsSolo();

services.Configure<MartenSettings>(s =>
{
s.SchemaName = SchemaName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ public async Task InitializeAsync()
{
// Using Marten, wipe out all data and reset the state
await Store.Advanced.ResetAllData();

// OR if you use the async daemon in your tests, use this
// instead to do the above, but also cleanly stop all projections,
// reset the data, then start all async projections and subscriptions up again
await Host.ResetAllMartenDataAsync();
}

// This is required because of the IAsyncLifetime
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public record AsyncDaemonHealthCheckSettings(int MaxEventLag, TimeSpan? MaxSameL
/// <summary>
/// Tracks projection state across multiple health check invocations
/// </summary>
internal class ProjectionStateTracker
public class ProjectionStateTracker
{
public ConcurrentDictionary<string, (DateTime CheckedAt, long Sequence)> LastProjectionsChecks { get; } = new();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public interface IProjectionCoordinator : IHostedService
/// </summary>
/// <returns></returns>
Task ResumeAsync();

DaemonMode Mode { get; }
}

public interface IProjectionCoordinator<T> : IProjectionCoordinator where T : IDocumentStore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ public ProjectionCoordinator(IDocumentStore documentStore, ILogger<ProjectionCoo
{
var store = (DocumentStore)documentStore;

Mode = store.Options.Projections.AsyncMode;

if (store.Options.Projections.AsyncMode == DaemonMode.Solo)
{
Distributor = new SoloProjectionDistributor(store);
Expand All @@ -60,6 +62,8 @@ public ProjectionCoordinator(IDocumentStore documentStore, ILogger<ProjectionCoo
Store = store;
}

public DaemonMode Mode { get; }

public DocumentStore Store { get; }

public IProjectionDistributor Distributor { get; }
Expand Down
25 changes: 25 additions & 0 deletions src/Marten/HostExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using JasperFx.Events.Daemon;
using Marten.Events;
using Marten.Events.Daemon.Coordination;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -55,6 +56,30 @@ public static T DocumentStore<T>(this IHost host) where T : IDocumentStore
return host.Services.GetRequiredService<T>();
}

/// <summary>
/// Override the main Marten DocumentStore and any registered "ancillary" stores that are using the
/// Async Daemon to run in "Solo" mode for faster and probably more reliable automated testing
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection MartenDaemonModeIsSolo(this IServiceCollection services)
{
services.AddSingleton<IConfigureMarten, OverrideDaemonModeToSolo>();
return services;
}

internal class OverrideDaemonModeToSolo: IGlobalConfigureMarten
{
public void Configure(IServiceProvider services, StoreOptions options)
{
if (options.Projections.AsyncMode == DaemonMode.HotCold)
{
options.Projections.AsyncMode = DaemonMode.Solo;
}
}
}


/// <summary>
/// Clean off all Marten data in the default DocumentStore for this host
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions src/Marten/Internal/SecondaryStoreConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ public StoreOptions BuildStoreOptions(IServiceProvider provider)
var configures = provider.GetServices<IConfigureMarten<T>>();
foreach (var configure in configures) configure.Configure(provider, options);

var globals = provider.GetServices<IConfigureMarten>().OfType<IGlobalConfigureMarten>();
foreach (var configureMarten in globals)
{
configureMarten.Configure(provider, options);
}

options.ReadJasperFxOptions(provider.GetService<JasperFxOptions>());
options.StoreName = typeof(T).Name;
options.ReadJasperFxOptions(provider.GetService<JasperFxOptions>());
Expand Down
2 changes: 2 additions & 0 deletions src/Marten/MartenServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,8 @@ public void Configure(IServiceProvider services, StoreOptions options)
}
}

public interface IGlobalConfigureMarten: IConfigureMarten;

#region sample_IConfigureMarten

/// <summary>
Expand Down
Loading