Skip to content

Commit 4bb851a

Browse files
Merge pull request erikdarlingdata#2669 from erikdarlingdata/dev
Release 3.6.0
2 parents c5fbd52 + 157c4d7 commit 4bb851a

597 files changed

Lines changed: 103073 additions & 6851 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitattributes

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,14 @@
1616
*.parquet binary
1717
*.zip binary
1818
*.duckdb binary
19+
20+
# A captured PostgreSQL log is a FIXTURE of real bytes: the parser keys on the exact line endings
21+
# and the tab-indented continuation, so line-ending normalisation would change what it is evidence of.
22+
Darling/Darling.Tests/Fixtures/auto_explain_real_block.txt -text
23+
24+
# The PostgreSQL verification rig runs INSIDE Linux containers. A Dockerfile whose RUN lines carry a
25+
# trailing CR breaks the shell that executes them, and the seed is fed to psql in the container the same
26+
# way - so these keep LF regardless of the repo-wide eol=crlf default.
27+
tools/pg-verification-rig/Dockerfile text eol=lf
28+
tools/pg-verification-rig/*.yml text eol=lf
29+
tools/pg-verification-rig/*.sql text eol=lf

.github/workflows/build.yml

Lines changed: 926 additions & 876 deletions
Large diffs are not rendered by default.

.github/workflows/nightly.yml

Lines changed: 524 additions & 487 deletions
Large diffs are not rendered by default.

CHANGELOG.md

Lines changed: 179 additions & 7 deletions
Large diffs are not rendered by default.

Darling/Darling.Tests/AgAlertPolicyTests.cs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
77
*/
88

9+
using System;
910
using PerformanceMonitor.Common;
1011
using Xunit;
1112

@@ -81,6 +82,102 @@ public void DecideConnection_AlreadyDisconnectedAtFirstSighting_StaysSilent_ButI
8182
Assert.Equal(AgConnectionDecision.Reconnected, AgAlertPolicy.DecideConnection("DISCONNECTED", "CONNECTED"));
8283
}
8384

85+
/* ---------------- #2426: the disconnect re-fire ---------------- */
86+
87+
private static readonly DateTime Noon = new(2026, 8, 20, 12, 0, 0, DateTimeKind.Utc);
88+
89+
[Theory]
90+
[InlineData("CONNECTED", "DISCONNECTED", AgConnectionDecision.Disconnected)]
91+
[InlineData("DISCONNECTED", "CONNECTED", AgConnectionDecision.Reconnected)]
92+
[InlineData("DISCONNECTED", "DISCONNECTED", AgConnectionDecision.None)]
93+
[InlineData("CONNECTED", "CONNECTED", AgConnectionDecision.None)]
94+
[InlineData(null, "DISCONNECTED", AgConnectionDecision.None)]
95+
[InlineData("CONNECTED", null, AgConnectionDecision.None)]
96+
public void DecideConnection_RefireOff_IsTheEdgeOnlyOverloadExactly(
97+
string? previous, string? current, AgConnectionDecision expected)
98+
{
99+
/* The shipped default, and the whole matrix rather than one case: null, zero and a negative all
100+
mean OFF, and off has to be byte-for-byte the two-argument behavior or an upgrade would start
101+
re-alerting on a knob nobody set. */
102+
Assert.Equal(expected, AgAlertPolicy.DecideConnection(previous, current, null, null, Noon));
103+
Assert.Equal(expected, AgAlertPolicy.DecideConnection(previous, current, TimeSpan.Zero, null, Noon));
104+
Assert.Equal(expected, AgAlertPolicy.DecideConnection(previous, current, TimeSpan.FromMinutes(-5), null, Noon));
105+
}
106+
107+
[Fact]
108+
public void DecideConnection_StillDisconnected_WaitsOutTheWindow_ThenSaysItAgain()
109+
{
110+
var refire = TimeSpan.FromMinutes(10);
111+
112+
/* Announced at noon: inside the window there is nothing new to say. */
113+
Assert.Equal(
114+
AgConnectionDecision.None,
115+
AgAlertPolicy.DecideConnection("DISCONNECTED", "DISCONNECTED", refire, Noon, Noon.AddMinutes(9)));
116+
117+
/* At the boundary, and still hours later — the point of the knob is that a week-long outage does
118+
not read like a blip. */
119+
Assert.Equal(
120+
AgConnectionDecision.StillDisconnected,
121+
AgAlertPolicy.DecideConnection("DISCONNECTED", "DISCONNECTED", refire, Noon, Noon.AddMinutes(10)));
122+
Assert.Equal(
123+
AgConnectionDecision.StillDisconnected,
124+
AgAlertPolicy.DecideConnection("DISCONNECTED", "DISCONNECTED", refire, Noon, Noon.AddHours(8)));
125+
}
126+
127+
[Fact]
128+
public void DecideConnection_ARealEdgeOutranksARefire()
129+
{
130+
var refire = TimeSpan.FromMinutes(10);
131+
132+
/* The edge that OPENS the outage announces as Disconnected even though the window is trivially due
133+
on it, or the caller would have two reasons to announce the same sweep. */
134+
Assert.Equal(
135+
AgConnectionDecision.Disconnected,
136+
AgAlertPolicy.DecideConnection("CONNECTED", "DISCONNECTED", refire, null, Noon));
137+
138+
/* And a recovery is a recovery whatever the clock says. */
139+
Assert.Equal(
140+
AgConnectionDecision.Reconnected,
141+
AgAlertPolicy.DecideConnection("DISCONNECTED", "CONNECTED", refire, Noon.AddHours(-9), Noon));
142+
}
143+
144+
[Fact]
145+
public void DecideConnection_NoStampIsDueNow_SoARestartMidOutageStillReAnnounces()
146+
{
147+
var refire = TimeSpan.FromMinutes(10);
148+
149+
/* Both apps hold this edge state in memory, so a restart during a week-long outage sees a replica
150+
already DISCONNECTED with no record of it ever having been announced. Rule 1's silent baseline
151+
would make that silence permanent, which is the exact failure the knob exists to prevent — so
152+
with re-fire ON, and only then, a first sighting announces. */
153+
Assert.Equal(
154+
AgConnectionDecision.StillDisconnected,
155+
AgAlertPolicy.DecideConnection(null, "DISCONNECTED", refire, null, Noon));
156+
Assert.Equal(
157+
AgConnectionDecision.StillDisconnected,
158+
AgAlertPolicy.DecideConnection("DISCONNECTED", "DISCONNECTED", refire, null, Noon));
159+
160+
/* With it off, rule 1 governs unchanged. */
161+
Assert.Equal(
162+
AgConnectionDecision.None,
163+
AgAlertPolicy.DecideConnection(null, "DISCONNECTED", null, null, Noon));
164+
}
165+
166+
[Fact]
167+
public void DecideConnection_ARefireStillNeedsAnExactDisconnected()
168+
{
169+
var refire = TimeSpan.FromMinutes(10);
170+
171+
/* Same rule the edge follows, and it matters more here: a re-fire pages repeatedly, so a state
172+
string the product never learned to interpret must not become a standing page. */
173+
Assert.Equal(
174+
AgConnectionDecision.None,
175+
AgAlertPolicy.DecideConnection("SOMETHING_NEW", "SOMETHING_NEW", refire, null, Noon));
176+
Assert.Equal(
177+
AgConnectionDecision.None,
178+
AgAlertPolicy.DecideConnection("CONNECTED", null, refire, null, Noon));
179+
}
180+
84181
/* ---------------- suspension ---------------- */
85182

86183
[Theory]

Darling/Darling.Tests/AlertEngineTests.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,6 +1052,12 @@ public async Task TempDb_FiresAtThreshold_AndResolutionCarriesTheCurrentPercent(
10521052
h.Settings.TempDbSpaceEnabled = true;
10531053
var engine = h.Build();
10541054

1055+
/* #2515: 1000 MB total and NO MaxSizeMb, which is deliberate and stays that way. The fixture predates
1056+
the ceiling, so it describes a tempdb whose cap was never measured — and that is exactly the case
1057+
where the denominator remains the allocation. It does NOT imply a 1000 MB cap; if it did, this pin
1058+
would have to move, and the fact that it does not is the guarantee that no existing on-prem or RDS
1059+
target with an unlimited (or uncollected) tempdb sees its number change. The capped case gets its
1060+
own test below rather than being folded in here. */
10551061
h.Adapter.TempDb = new TempDbSpaceInfo { TotalReservedMb = 800, UnallocatedMb = 200 }; /* 80% used */
10561062
await engine.EvaluateServerAsync(Harness.Snapshot());
10571063
var fired = Assert.Single(h.Deliverer.Outcomes);
@@ -1066,6 +1072,36 @@ public async Task TempDb_FiresAtThreshold_AndResolutionCarriesTheCurrentPercent(
10661072
Assert.Equal("SRV-A: tempdb usage back to 20%", resolution.Message); /* :461,:464 */
10671073
}
10681074

1075+
/// <summary>
1076+
/// #2515, through the ENGINE rather than the arithmetic: the Azure shape from the issue must not fire, and
1077+
/// the same allocation without a ceiling must. Same 59.75 MB of reserved tempdb in both, same 80% default —
1078+
/// the only difference is whether the collector could see how far the files are allowed to grow.
1079+
///
1080+
/// <para>This is the assertion the whole change exists for. <see cref="TempDbCeilingStoreTests"/> proves
1081+
/// the arithmetic and the store round-trip; this proves the alert engine's decision follows it, which is
1082+
/// what actually pages someone.</para>
1083+
/// </summary>
1084+
[Fact]
1085+
public async Task TempDb_TheAzureCeiling_SuppressesTheAlert_ThatTheAllocationWouldFire()
1086+
{
1087+
var h = new Harness();
1088+
h.Settings.TempDbSpaceEnabled = true;
1089+
Assert.Equal(80, h.Settings.TempDbSpaceThresholdPercent);
1090+
var engine = h.Build();
1091+
1092+
/* GP_S_Gen5_2 with one ~57 MB #temp table: 62.44 MB allocated, 65,536 MB of headroom behind it. */
1093+
h.Adapter.TempDb = new TempDbSpaceInfo { TotalReservedMb = 59.75, UnallocatedMb = 2.69, MaxSizeMb = 65_536 };
1094+
await engine.EvaluateServerAsync(Harness.Snapshot());
1095+
Assert.Empty(h.Deliverer.Outcomes);
1096+
1097+
/* The identical snapshot with the ceiling unmeasured is the pre-#2515 reading, and it pages. */
1098+
h.Adapter.TempDb = new TempDbSpaceInfo { TotalReservedMb = 59.75, UnallocatedMb = 2.69 };
1099+
await engine.EvaluateServerAsync(Harness.Snapshot());
1100+
var fired = Assert.Single(h.Deliverer.Outcomes);
1101+
Assert.Equal("tempdb Space", fired.MetricName);
1102+
Assert.Equal("96% used (60 MB)", fired.CurrentValue);
1103+
}
1104+
10691105
/* ---------------- low disk ---------------- */
10701106

10711107
[Fact]

0 commit comments

Comments
 (0)