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
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public static void Handle(Bug1933Message message)
// 2, and 5.5m for the class. The broker starts fine now; the survivor is
// should_receive_message_when_published_without_tenant_id, which fails as a TrackedSession timeout
// after 4m28s -- the message is Sent and never Received. That is real multi-tenant routing
// behaviour, not provisioning. Needs its own issue.
// behaviour, not provisioning. Tracked as GH-3826.
[Trait("Category", "Flaky")]
public class Bug_1933_multi_tenant_conventional_routing : IAsyncLifetime
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace Wolverine.AzureServiceBus.Tests.Bugs;
//
// The listener IS created for the batch element type (that much of GH-2307 works) -- its
// EndpointName is just the raw type name rather than the sanitized queue name. A naming bug in the
// GH-2307 fix, not a routing or provisioning failure. Needs its own issue.
// GH-2307 fix, not a routing or provisioning failure. Tracked as GH-3827.
[Trait("Category", "Flaky")]
public class Bug_2307_batching_with_conventional_routing : IAsyncLifetime
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace Wolverine.AzureServiceBus.Tests;
// no broker-initialization timeout anywhere in it -- the two failures are
// send_and_receive_multiple_messages_to_queue_with_session_identifier and
// split_messages_with_different_sessionids_into_separate_batches, i.e. SESSION handling, which
// conventional routing never touched. The other 4 pass. Needs its own issue; do not fold it back
// conventional routing never touched. The other 4 pass. Tracked as GH-3825; do not fold it back
// into GH-3786.
[Trait("Category", "Flaky")]
public class end_to_end : IAsyncLifetime
Expand Down
30 changes: 16 additions & 14 deletions src/Transports/RabbitMQ/Wolverine.RabbitMQ.Tests/end_to_end.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,23 +61,25 @@ public static string NextListenerName()
}
}

// GH-3763: stays tagged, but the earlier triage on this class was wrong in two ways and is replaced.
// GH-3824: UNTAGGED. The last remaining failures in this class were a Wolverine bug, not flakiness.
//
// It cited "the exchange/binding-declaration race described in #2618" -- #2618 is a closed CI-stabilisation
// tracer PR that describes nothing of the sort -- and it recorded
// send_message_to_and_receive_through_rabbitmq_with_routing_key as failing 3 of 3. That test passes now.
// The previous triage got as far as "use_fan_out_exchange and use_direct_exchange_with_binding_key each
// pass ALONE and fail inside the class, in ~500ms on a null ColorHistory" -- all correct -- and then
// inferred the cause: that WaitForMessageToBeReceivedAt is satisfied by MessageFailed as well as
// MessageSucceeded, so a message that arrived and then failed ended the session early. That inference was
// wrong. Dumping the session instead of inferring from the assertion (which is exactly what that triage
// said to do next) showed status=Completed, ZERO exceptions, and the message marked successful -- but at
// only ONE of the three receivers.
//
// What is actually true, measured 2026-08-03: use_fan_out_exchange and use_direct_exchange_with_binding_key
// each pass ALONE (3 consecutive runs) and fail inside the class. They fail in ~500ms, nowhere near their
// 30s tracked-session timeout, on a null ColorHistory -- which is possible because
// WaitForMessageToBeReceivedAt is satisfied by MessageFailed as well as MessageSucceeded, so a message that
// arrives and then fails completes the session and the test falls through to the assertion.
// The real cause was in TrackedSession.IsCompleted(): it short-circuited on the first satisfied condition
// (Any) rather than requiring all of them, which made the All(...) check on its own last line unreachable.
// Both of these tests chain three WaitForMessageToBeReceivedAt calls for a fan-out, so the session returned
// as soon as the FIRST receiver handled the message and the assertions raced the other two handlers. Alone
// on an idle machine all three finish inside the same millisecond, which is why it only failed in-class.
//
// Two real causes were found and fixed in this pass (see RabbitTesting above, and the GH-3521 pins on the
// receiver hosts), and they are not enough on their own: both tests still fail in-class. What remains is
// unidentified cross-test state, and the next step is to dump the tracked session on failure rather than
// infer from the assertion.
[Trait("Category", "Flaky")]
// Two further real causes were found and fixed earlier and were prerequisites, not red herrings: the
// process-unique naming in RabbitTesting above, and the GH-3521 ApplicationAssembly pins on the receiver
// hosts.
public class end_to_end
{
private readonly ITestOutputHelper _output;
Expand Down
19 changes: 11 additions & 8 deletions src/Wolverine/Tracking/TrackedSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -594,17 +594,20 @@ public bool IsCompleted()
{
if (!_executionComplete) return false;

if (_conditions.Any(x => x.IsCompleted()))
{
return true;
}

if (!_envelopes.All(x => x.IsComplete()))
if (_conditions.Any())
{
return false;
// ALL of the conditions, not ANY. This used to short-circuit on the first satisfied
// condition, which made the final All(...) check below unreachable and silently broke
// every session that registers more than one condition -- e.g. three chained
// WaitForMessageToBeReceivedAt calls for a fan-out exchange returned as soon as the
// FIRST receiver handled the message, so assertions against the other two receivers'
// state raced the handlers that were still running. With a single condition ANY and ALL
// are the same, which is why this survived: almost every session registers just one.
// GH-3824.
return _conditions.All(x => x.IsCompleted());
}

return !_conditions.Any() || _conditions.All(x => x.IsCompleted());
return _envelopes.All(x => x.IsComplete());
}

public void LogException(Exception exception, string? serviceName)
Expand Down
Loading