-
Notifications
You must be signed in to change notification settings - Fork 0
v1.0.38 #101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
v1.0.38 #101
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
7775e9c
feat(polling): add coalescing IPollingWakeSignal for adaptive polling…
yilmaztayfun 7062a3e
feat(outbox): publish OutboxWakeupEvent nudge after commits that stor…
yilmaztayfun 5ce17e0
feat(polling): outbox/inbox background services wake early on IPollin…
yilmaztayfun c2e7f8b
feat(inbox): signal the inbox poller when a delivered event is stored
yilmaztayfun 28aeb17
feat(outbox)!: Outbox.Process roots its own trace and links the origi…
yilmaztayfun bed2d21
fix(outbox): wakeup nudge publishes without ambient trace context
yilmaztayfun f521ed7
Merge pull request #100 from burgan-tech/feature/outbox-wakeup-signal
yilmaztayfun File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
framework/src/BBT.Aether.Core/BBT/Aether/Events/IOutboxWakeupNotifier.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace BBT.Aether.Events; | ||
|
|
||
| /// <summary> | ||
| /// Publishes the <see cref="OutboxWakeupEvent"/> nudge. Implementations must be fire-and-forget | ||
| /// safe: a failed notify is swallowed by callers because polling backstops delivery. | ||
| /// </summary> | ||
| public interface IOutboxWakeupNotifier | ||
| { | ||
| Task NotifyAsync(CancellationToken cancellationToken = default); | ||
| } |
10 changes: 10 additions & 0 deletions
10
framework/src/BBT.Aether.Core/BBT/Aether/Events/OutboxWakeupEvent.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| namespace BBT.Aether.Events; | ||
|
|
||
| /// <summary> | ||
| /// Loss-tolerant wake nudge published directly to pub/sub (never through the outbox) after a unit | ||
| /// of work that stored at least one outbox message commits. Subscribing outbox processors treat it | ||
| /// as "poll now"; the payload is deliberately empty and delivery is best-effort — the adaptive | ||
| /// polling interval remains the safety net for lost or early signals. | ||
| /// </summary> | ||
| [EventName("aether.outbox.wakeup")] | ||
| public sealed class OutboxWakeupEvent; |
24 changes: 24 additions & 0 deletions
24
framework/src/BBT.Aether.Core/BBT/Aether/Polling/IPollingWakeSignal.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| using System; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace BBT.Aether.Polling; | ||
|
|
||
| /// <summary> | ||
| /// A coalescing wake signal for adaptive polling loops. Producers call <see cref="Signal"/> when | ||
| /// new work becomes available; the polling loop awaits <see cref="WaitAsync"/> with its normal | ||
| /// interval as the timeout so a signal cuts the wait short while polling remains the safety net. | ||
| /// The marker type parameter distinguishes independent loops (e.g. outbox vs inbox) in DI. | ||
| /// </summary> | ||
| /// <typeparam name="TMarker">Marker type identifying the loop this signal wakes.</typeparam> | ||
| public interface IPollingWakeSignal<TMarker> | ||
| { | ||
| /// <summary>Wakes the loop. Multiple pending signals coalesce into one.</summary> | ||
| void Signal(); | ||
|
|
||
| /// <summary> | ||
| /// Waits until <see cref="Signal"/> is called or the timeout elapses. | ||
| /// Returns true when woken by a signal, false on timeout. | ||
| /// </summary> | ||
| Task<bool> WaitAsync(TimeSpan timeout, CancellationToken cancellationToken = default); | ||
| } |
30 changes: 30 additions & 0 deletions
30
framework/src/BBT.Aether.Core/BBT/Aether/Polling/PollingWakeSignal.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| using System; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace BBT.Aether.Polling; | ||
|
|
||
| /// <summary> | ||
| /// Default <see cref="IPollingWakeSignal{TMarker}"/> over a bounded <see cref="SemaphoreSlim"/>(0,1): | ||
| /// signals coalesce (a second Signal while one is pending is a no-op), so a burst of producers | ||
| /// causes exactly one early wake. | ||
| /// </summary> | ||
| public sealed class PollingWakeSignal<TMarker> : IPollingWakeSignal<TMarker> | ||
| { | ||
| private readonly SemaphoreSlim _semaphore = new(0, 1); | ||
|
|
||
| public void Signal() | ||
| { | ||
| try | ||
| { | ||
| _semaphore.Release(); | ||
| } | ||
| catch (SemaphoreFullException) | ||
| { | ||
| // A wake is already pending — coalesce. | ||
| } | ||
| } | ||
|
|
||
| public Task<bool> WaitAsync(TimeSpan timeout, CancellationToken cancellationToken = default) | ||
| => _semaphore.WaitAsync(timeout, cancellationToken); | ||
| } |
24 changes: 24 additions & 0 deletions
24
framework/src/BBT.Aether.Infrastructure/BBT/Aether/Events/DaprOutboxWakeupNotifier.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Dapr.Client; | ||
|
|
||
| namespace BBT.Aether.Events; | ||
|
|
||
| /// <summary> | ||
| /// Publishes <see cref="OutboxWakeupEvent"/> straight to the configured pub/sub component, | ||
| /// bypassing the outbox by design (the nudge must not create the work it announces). | ||
| /// </summary> | ||
| public sealed class DaprOutboxWakeupNotifier( | ||
| DaprClient daprClient, | ||
| ITopicNameStrategy topicNameStrategy, | ||
| AetherEventBusOptions eventBusOptions) : IOutboxWakeupNotifier | ||
| { | ||
| private readonly string _topic = topicNameStrategy.GetTopicName(typeof(OutboxWakeupEvent)); | ||
|
|
||
| public Task NotifyAsync(CancellationToken cancellationToken = default) | ||
| => daprClient.PublishEventAsync( | ||
| eventBusOptions.PubSubName, | ||
| _topic, | ||
| new OutboxWakeupEvent(), | ||
| cancellationToken); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
89 changes: 89 additions & 0 deletions
89
framework/src/BBT.Aether.Infrastructure/BBT/Aether/Events/OutboxWakeupCoordinator.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| using System; | ||
| using System.Diagnostics; | ||
| using System.Runtime.CompilerServices; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using BBT.Aether.Uow; | ||
| using Microsoft.Extensions.Logging; | ||
|
|
||
| namespace BBT.Aether.Events; | ||
|
|
||
| /// <summary> | ||
| /// Decides when the outbox wakeup nudge fires: once per unit of work that stored at least one | ||
| /// outbox message, from the UoW's OnCompleted callback — but WITHOUT extending the commit path: | ||
| /// the callback returns immediately and the pub/sub publish runs as an unobserved task with a | ||
| /// bounded timeout. A lost or failed nudge is logged and absorbed by the polling safety net. | ||
| /// </summary> | ||
| public sealed class OutboxWakeupCoordinator( | ||
| AetherOutboxOptions options, | ||
| IUnitOfWorkManager? unitOfWorkManager = null, | ||
| IOutboxWakeupNotifier? wakeupNotifier = null, | ||
| ILogger<OutboxWakeupCoordinator>? logger = null) | ||
| { | ||
| private static readonly TimeSpan NotifyTimeout = TimeSpan.FromSeconds(2); | ||
| private static readonly ConditionalWeakTable<IUnitOfWork, object> WakeupRegistered = new(); | ||
| private static readonly object RegisteredSentinel = new(); | ||
|
|
||
| /// <summary>Call once per stored outbox message; registration collapses to one per UoW.</summary> | ||
| public void OnOutboxMessageStored() | ||
| { | ||
| if (wakeupNotifier is null || !options.WakeupSignalEnabled) | ||
| return; | ||
|
|
||
| var uow = unitOfWorkManager?.Current; | ||
| if (uow is null) | ||
| { | ||
| // No ambient UoW: the caller flushes on its own SaveChanges, which this coordinator | ||
| // cannot observe — the nudge may land BEFORE the row is visible. This branch is an | ||
| // early best-effort hint EXCLUDED from the latency guarantee (the row then waits for | ||
| // normal polling). Every vnext transition path runs with an ambient UoW, so this is | ||
| // never the latency-critical path. | ||
| NotifyFireAndForget(); | ||
| return; | ||
| } | ||
|
|
||
| // Dedupe on the shared transaction root, not the per-call scope object. A `Required` | ||
| // participant scope (UnitOfWorkScope with ownsRoot == false) is a distinct object per | ||
| // nesting level that forwards OnCompleted to the same CompositeUnitOfWork root — keying | ||
| // on `uow` itself would register (and later fire) once per nested scope for a single | ||
| // commit. CompositeUnitOfWork itself already implements IUnitOfWork, so it is a valid | ||
| // ConditionalWeakTable key; a `uow` that is not a UnitOfWorkScope (e.g. the root itself, | ||
| // or a test substitute) dedupes on itself as before. | ||
| var dedupeKey = (uow as UnitOfWorkScope)?.SharedRoot ?? uow; | ||
|
|
||
| lock (RegisteredSentinel) | ||
| { | ||
| if (WakeupRegistered.TryGetValue(dedupeKey, out _)) | ||
| return; | ||
| WakeupRegistered.Add(dedupeKey, RegisteredSentinel); | ||
| } | ||
|
|
||
| // OnCompleted callbacks are awaited inside CommitAsync — return a completed task and let | ||
| // the publish run detached so a slow sidecar can never stretch the commit path. | ||
| uow.OnCompleted(_ => | ||
| { | ||
| NotifyFireAndForget(); | ||
| return Task.CompletedTask; | ||
| }); | ||
| } | ||
|
|
||
| private void NotifyFireAndForget() | ||
| { | ||
| _ = Task.Run(async () => | ||
| { | ||
| // The nudge is infrastructure, not business flow: sever the ambient Activity captured via | ||
| // ExecutionContext so the publish's client span (and the delivery it causes on the worker) | ||
| // can never attach to — or propagate the traceparent of — the committing business trace. | ||
| Activity.Current = null; | ||
| try | ||
| { | ||
| using var cts = new CancellationTokenSource(NotifyTimeout); | ||
| await wakeupNotifier!.NotifyAsync(cts.Token); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| logger?.LogWarning(ex, "Outbox wakeup nudge failed or timed out; polling will pick the work up"); | ||
| } | ||
| }); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (broader_impact): The notifier publishes OutboxWakeupEvent, but the framework registers no handler, subscription endpoint, or callback that converts that event into IPollingWakeSignal. Consequently, the outbox background service is never woken by the published nudge and continues waiting for its normal polling interval.
Triggers: When outbox wakeup is enabled and the outbox processor is expected to run in another process or receive the Dapr pub/sub nudge.
Suggested fix: Register a subscription/handler for OutboxWakeupEvent that resolves IPollingWakeSignal and calls Signal(), or provide an equivalent Dapr delivery endpoint.