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
5 changes: 2 additions & 3 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@
<PowerCSharpFeaturesVersion>1.0.3</PowerCSharpFeaturesVersion>

<!-- Per-pluggable-feature versions — ship independently. -->
<!-- Cache family bumped to 1.3.0: realigns Cache + BitFaster onto the post-split
PowerCSharp.Feature.Cache.Abstractions ICacheService (stale 1.2.0 predated the split). -->
<PowerCSharpFeatureCacheVersion>1.3.3</PowerCSharpFeatureCacheVersion>

<PowerCSharpFeatureCacheVersion>1.3.4</PowerCSharpFeatureCacheVersion>

<!-- Sanitization family: Feature.Sanitization.Abstractions + Feature.Sanitization -->
<PowerCSharpFeatureSanitizationVersion>1.0.0</PowerCSharpFeatureSanitizationVersion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@ public sealed class NoOpCacheService : ICacheService
/// <summary>Creates the NoOp cache and logs that caching is inert.</summary>
public NoOpCacheService(ILogger<NoOpCacheService> logger)
{
logger.LogInformation("Cache feature is disabled or unconfigured; using NoOp in-memory cache.");
// NOTE: this constructor can run even when a real provider (e.g. BitFaster) is the one
// actually injected as ICacheService — ASP.NET Core's ValidateOnBuild (on by default in
// Development) constructs every registered descriptor, including this shadowed safe-off
// floor, purely to validate the DI graph. Seeing this log does not by itself mean the app
// is using NoOp; check the Cache feature's "Cache feature resolved" diagnostic line for
// the implementation actually bound to ICacheService.
logger.LogInformation("NoOp in-memory cache constructed (ICacheService safe-off floor).");
}

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ public sealed class NoOpDiskCacheService : IDiskCacheService
/// <summary>Creates the NoOp disk cache and logs that disk caching is inert.</summary>
public NoOpDiskCacheService(ILogger<NoOpDiskCacheService> logger)
{
logger.LogInformation("Cache feature is disabled or unconfigured; using NoOp disk cache.");
// NOTE: see the matching comment in NoOpCacheService — this can be constructed by
// ASP.NET Core's ValidateOnBuild even when a real provider (e.g. Disk) is the one
// actually injected as IDiskCacheService. Check the Cache feature's "Cache feature
// resolved" diagnostic line for the implementation actually in use.
logger.LogInformation("NoOp disk cache constructed (IDiskCacheService safe-off floor).");
}

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using BitFaster.Caching.Lru;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using PowerCSharp.Feature.Cache.Abstractions;

Expand All @@ -20,10 +21,16 @@ public sealed class BitFasterCacheService : ICacheService
private readonly ConcurrentDictionary<string, InMemoryCacheEntryMetadata> _metadata = new();

/// <summary>Creates the cache with capacity from <see cref="BitFasterCacheOptions"/>.</summary>
public BitFasterCacheService(IOptions<BitFasterCacheOptions> options)
public BitFasterCacheService(IOptions<BitFasterCacheOptions> options, ILogger<BitFasterCacheService> logger)
{
var capacity = Math.Max(1, options.Value.Capacity);
_cache = new ConcurrentLru<string, object?>(capacity);

logger.LogInformation(
"BitFaster in-memory cache initialized (capacity={Capacity}). This constructor running does not " +
"by itself confirm this instance is the one injected as ICacheService — see the Cache feature's " +
"'Cache feature resolved' diagnostic line for the actually-bound implementation.",
capacity);
}

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<ItemGroup>
<PackageReference Include="BitFaster.Caching" Version="2.5.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
</ItemGroup>

Expand Down
28 changes: 27 additions & 1 deletion src/Features/PowerCSharp.Feature.Cache/CacheFeatureModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,14 @@ public void ConfigureServices(IFeatureRegistrationContext context)
{
// Force-load the abstractions assembly so the CLR can resolve its types during discovery.
_ = Assembly.Load("PowerCSharp.Feature.Cache.Abstractions");

context.Services.Configure<CacheFeatureOptions>(
context.Configuration.GetSection($"PowerFeatures:{Key}"));

// NOTE: context.Logger is NullLogger at this stage (the DI container isn't built yet, so
// no real ILoggerFactory exists). Anything logged here is silently discarded. The
// authoritative "which implementation actually won" diagnostic is logged for real in
// ConfigurePipeline below, once a real logger is available.
if (!context.Flags.IsEnabled(FeatureKey))
{
context.Logger.LogInformation("Cache feature disabled; registering NoOp cache services.");
Expand All @@ -45,4 +49,26 @@ public void ConfigureServices(IFeatureRegistrationContext context)
context.Services.TryAddSingleton<ICacheService, NoOpCacheService>();
context.Services.TryAddSingleton<IDiskCacheService, NoOpDiskCacheService>();
}

/// <inheritdoc />
/// <remarks>
/// Diagnostic-only: resolves the services actually bound to <see cref="ICacheService"/> and
/// <see cref="IDiskCacheService"/> from the fully-built container and logs their concrete
/// types with a real logger. This is the authoritative answer to "which cache implementation
/// is actually active" — unlike constructor-time logs from individual implementations (which
/// can fire for shadowed/overridden registrations too, e.g. under ASP.NET Core's
/// ValidateOnBuild in Development, without ever being the instance that's actually injected).
/// </remarks>
public void ConfigurePipeline(IFeaturePipelineContext context)
{
var services = context.App.ApplicationServices;
var cache = services.GetRequiredService<ICacheService>();
var diskCache = services.GetRequiredService<IDiskCacheService>();

context.Logger.LogInformation(
"Cache feature resolved -> ICacheService: {CacheImplementation}; IDiskCacheService: {DiskCacheImplementation}. " +
"A NoOp implementation here means no provider package overrode the safe-off floor for that contract.",
cache.GetType().FullName,
diskCache.GetType().FullName);
}
}
Loading