refactor(siteruntime): scope SiteScriptCompileCache to the ScriptCompilationService singleton — close register row 100
The compile cache was a `static` class, so its entry table and its Hits/Count
counters were one process-global blob every test class in the SiteRuntime
assembly wrote into. Row 91 last wave neutered the resulting flakes (per-test
GUID keys + one-sided counter bounds), but the shared mutable state itself
remained: any test calling Clear() evicted entries other parallel classes had
just inserted, and a Clear() from a class outside the serialized collection
would degrade the others to recompiles and re-open the row-91 flake class.
PREMISE CORRECTION: the register speculated production clears "per-instance or
per-deployment script entries" and offered ClearKeys(prefix) as the natural fit.
There are NO production callers of Clear() at all — grep of src/ finds only
GetOrAdd (ScriptCompilationService.CompileCore). Clear() existed solely so
tests could reset the global. A key-scoped API would therefore have been new
production surface built for nobody, so the instance-scoped seam is the fix and
Clear() is deleted outright rather than migrated.
- SiteScriptCompileCache: `internal static class` -> `internal sealed class`;
Cache/EvictionLock/_hits/_accessSequence become instance fields. LRU batch
eviction, the double-checked sweep and the ConcurrentDictionary.ToArray()
snapshot are byte-for-byte the same policy. Clear() removed.
- ScriptCompilationService owns one cache. Public ctor (the DI path) builds its
own; a new internal ctor takes one for tests. Production N4 sharing is
unchanged and now rests on the DI SINGLETON registration — the one service
instance is threaded to DeployCompileValidator and to every Instance Actor —
so that lifetime is load-bearing and is pinned by a new registration test.
- Tests: all three Clear() callers migrated off it. Each test now owns its cache,
which lets the assertions get STRONGER — the row-91 workarounds are retired:
Hits/Count are exact again (no >= baseline bounds), the per-test GUID key
token is gone, and ForeignEntrySlack (512 entries of allowance on the bound)
drops to the racing-thread count in the concurrency test. New test
SeparateCaches_DoNotShareEntriesOrCounters pins the scoping directly: filling
one cache past MaxEntries leaves another's entry, hit count and identity
intact. DeploymentWarmThenGateTests additionally asserts Count == 1 now that
its counter is private.
- The [Collection("SiteScriptCompileCache")] serialization from row 91 is
RELAXED (removed from all three classes) — they no longer touch any shared
state, so there is nothing left to serialize.
- Docs: Component-SiteRuntime.md gains the ownership/singleton note and drops
the Clear() reference; DeploymentManagerActor / DeployCompileValidator /
ServiceCollectionExtensions comments corrected to say where the cache lives.
Build: 0 warnings. SiteRuntime.Tests 643/643, three consecutive full-parallel
runs. AuditLog.Tests correlation classes (other ScriptCompilationService
consumers) green.
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ZB.MOM.WW.ScadaBridge.SiteRuntime;
|
||||
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Pins the DI lifetimes that carry a behavioural contract rather than a convention.
|
||||
/// </summary>
|
||||
public class SiteRuntimeServiceRegistrationTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Register row 100. <c>ScriptCompilationService</c> owns the
|
||||
/// <c>SiteScriptCompileCache</c>, so "one Roslyn compile per script body per process"
|
||||
/// (N4 / WP3.1's warm-then-gate deploy path) is precisely the statement that DI hands
|
||||
/// out ONE of these per host. The cache stopped being <c>static</c> when row 100 removed
|
||||
/// the process-global state tests were clobbering; this registration is what replaced
|
||||
/// the static as the guarantee, so it is pinned here.
|
||||
///
|
||||
/// <para>Downgrading this to Scoped or Transient would give every consumer — the deploy
|
||||
/// gate, each Instance Actor's PreStart, SharedScriptLibrary — its own empty cache and
|
||||
/// silently reintroduce the recompile storms WP3.1 removed, with no functional test
|
||||
/// failing. This assertion is the only thing that would notice.</para>
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AddSiteRuntime_RegistersScriptCompilationServiceAsSingleton_SoOneCompileCacheExistsPerProcess()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
|
||||
services.AddSiteRuntime();
|
||||
|
||||
var descriptor = Assert.Single(
|
||||
services, d => d.ServiceType == typeof(ScriptCompilationService));
|
||||
Assert.Equal(ServiceLifetime.Singleton, descriptor.Lifetime);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user