Skip to content

fix#27

Merged
MichalFrends1 merged 1 commit intomainfrom
fspes-108
Apr 28, 2026
Merged

fix#27
MichalFrends1 merged 1 commit intomainfrom
fspes-108

Conversation

@MatteoDelOmbra
Copy link
Copy Markdown
Contributor

@MatteoDelOmbra MatteoDelOmbra commented Apr 24, 2026

Summary by CodeRabbit

  • Bug Fixes
    • Fixed issues caused by short delays between consecutive checks
    • Added validation to enforce minimum delay parameter value

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 24, 2026

Walkthrough

This release introduces validation requiring a minimum ConsumeAttemptDelay of 0.1 seconds, refactors the event processing loop's stopping logic by moving MaxRunTime validation to the outer loop and adding consumer idle timeout checks, updates documentation, and bumps the package version to 2.7.0.

Changes

Cohort / File(s) Summary
Version Update
Frends.AzureEventHub.Receive.csproj
Package version incremented from 2.6.0 to 2.7.0.
Documentation and Configuration
CHANGELOG.md, Definitions/Options.cs
Added changelog entry for 2.7.0 documenting fix for short delay issues; updated XML documentation to include ConsumeAttemptDelay minimum constraint of 0.1 seconds; removed trailing whitespace; ensured proper newlines.
Core Processing Logic
AzureEventHub.cs
Added runtime validation requiring ConsumeAttemptDelay ≥ 0.1 seconds; refactored event processing loop stopping conditions: ProcessEventHandler now stops only on MaxEvents reached, while outer loop handles MaxRunTime expiry and consumer MaximumWaitTime idle timeout.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Issue 11 #13 — Both PRs modify the Receive processing loop's stopping logic to incorporate Consumer.MaximumWaitTime idle timeout checking and control flow adjustments.

Suggested reviewers

  • MichalFrends1
  • jefim

Poem

🐰 Bouncing through delays we found,
A fix to make the timing sound!
With validation checks so tight,
Our loops now stop just right! ✨
From two-point-six to seven we climb,
EventHub processing, perfectly timed!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'fix' is vague and generic, providing no meaningful information about what was fixed or which component was addressed. Use a more descriptive title that specifies the issue being fixed, such as 'Fix ConsumeAttemptDelay validation and event processing loop timing' or similar.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fspes-108

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Frends.AzureEventHub.Receive/Frends.AzureEventHub.Receive/AzureEventHub.cs (1)

86-97: ⚠️ Potential issue | 🟠 Major

Major: Consumer.MaximumWaitTime now doubles as an outer-loop idle timeout, which is an undocumented behavior change and can cause premature exit.

Two concerns with the new stop condition:

  1. Undocumented semantic overload. Previously Consumer.MaximumWaitTime only influenced the SDK's internal wait behavior (passed to EventProcessorClientOptions.MaximumWaitTime). Now it also terminates the outer loop if DateTime.UtcNow - lastEventTime >= MaximumWaitTime. Users who set MaximumWaitTime to, say, 10 seconds intending only to bound SDK polling will now see the Task exit after 10 idle seconds — a behavior change. The XML docs in Definitions/Consumer.cs (lines 86–94) still describe only the old role. Please update them and add a note to the CHANGELOG.

  2. lastEventTime is initialized before processing starts. lastEventTime = DateTime.UtcNow runs at line 48, before CreateBlobContainerClient, optional container creation, CreateEventProcessorClient, and StartProcessingAsync on lines 74–84. If any of those (especially CreateIfNotExistsAsync on a slow storage account) takes longer than MaximumWaitTime, the outer loop exits immediately on the first iteration without ever receiving an event. Reset lastEventTime immediately after StartProcessingAsync succeeds (or the first time the outer loop runs) to anchor the idle window to "time since processing actually began".

🛠️ Suggested fix
             await processorClient.StartProcessingAsync(cancellationToken);
+            lastEventTime = DateTime.UtcNow;
 
             while (!stopProcessing)
             {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Frends.AzureEventHub.Receive/Frends.AzureEventHub.Receive/AzureEventHub.cs`
around lines 86 - 97, The outer-loop stop condition incorrectly reuses
Consumer.MaximumWaitTime as an idle-exit timeout and uses lastEventTime
initialized before setup, causing premature exits; update the XML docs in
Definitions/Consumer.cs and CHANGELOG to document the behavior change or note
that MaximumWaitTime is only for the SDK (preferred), then modify the loop logic
so it does NOT treat MaximumWaitTime as the outer-loop idle timeout (remove the
DateTime.UtcNow - lastEventTime check) or, if you intend to keep the idle-exit
feature, reinitialize lastEventTime immediately after StartProcessingAsync
succeeds (or on the first loop iteration) to anchor the idle window to the
actual start of processing; locate symbols: Consumer.MaximumWaitTime,
lastEventTime, CreateBlobContainerClient, CreateIfNotExistsAsync,
CreateEventProcessorClient, and StartProcessingAsync to implement and document
the chosen fix.
🧹 Nitpick comments (3)
Frends.AzureEventHub.Receive/Frends.AzureEventHub.Receive/Definitions/Options.cs (1)

20-20: Nit: include the unit in the minimum-value note.

The preceding line says "in seconds" but the constraint line doesn't, which can be misread once quoted out of context (e.g., in generated docs or validation error text).

📝 Suggested wording
-    /// NOTE: Must be at least 0.1
+    /// NOTE: Must be at least 0.1 seconds.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@Frends.AzureEventHub.Receive/Frends.AzureEventHub.Receive/Definitions/Options.cs`
at line 20, The comment for the minimum value in Options.cs is ambiguous—update
the note next to the relevant property (the "NOTE: Must be at least 0.1" line)
to include the unit "seconds" (e.g., "NOTE: Must be at least 0.1 seconds") so it
clearly matches the preceding "in seconds" description and prevents
misinterpretation in docs or validation messages.
Frends.AzureEventHub.Receive/Frends.AzureEventHub.Receive/AzureEventHub.cs (2)

48-56: Minor: lastEventTime is read/written from different threads without synchronization.

lastEventTime is written from ProcessEventHandler (SDK callback thread) and read from the outer loop on line 90. DateTime is an 8-byte struct and the CLR does not guarantee atomic reads/writes of 64-bit values on all platforms — a torn read is theoretically possible, which could make the idle-timeout check use a garbage value. On x64 this is in practice atomic, so impact is low, but consider switching to Interlocked.Exchange on long ticks, or marking both accesses with Volatile.Read/Volatile.Write of the ticks.

🔒 Sketch
-        var lastEventTime = DateTime.UtcNow;
+        long lastEventTicks = DateTime.UtcNow.Ticks;
...
-                lastEventTime = DateTime.UtcNow;
+                Interlocked.Exchange(ref lastEventTicks, DateTime.UtcNow.Ticks);
...
-                    (maximumWaitTime.HasValue &&
-                     DateTime.UtcNow - lastEventTime >=
-                     maximumWaitTime.Value))
+                    (maximumWaitTime.HasValue &&
+                     DateTime.UtcNow - new DateTime(Interlocked.Read(ref lastEventTicks), DateTimeKind.Utc) >=
+                     maximumWaitTime.Value))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Frends.AzureEventHub.Receive/Frends.AzureEventHub.Receive/AzureEventHub.cs`
around lines 48 - 56, The field local variable lastEventTime is accessed from
different threads (written inside ProcessEventHandler and read in the outer
idle-timeout loop) and may suffer torn 64-bit reads; change lastEventTime from
DateTime to a long representing DateTime.UtcNow.Ticks and use
Interlocked.Exchange to set it in ProcessEventHandler and Interlocked.Read (or
Volatile.Read) to read it in the idle-checking loop, converting ticks back to
DateTime when needed (reference symbols: lastEventTime, ProcessEventHandler, and
the idle-timeout loop that reads lastEventTime).

39-40: Optional: prefer ArgumentOutOfRangeException for parameter validation.

throw new Exception(...) for input validation is an anti-pattern — it forces callers to catch the base type and loses semantic meaning. The rest of this method already throws bare Exception for validation (lines 35, 37), so this is consistent in-file, but consider migrating all three to ArgumentException/ArgumentOutOfRangeException in a follow-up.

-        if (options.ConsumeAttemptDelay < 0.1)
-            throw new Exception("Options.ConsumeAttemptDelay must be at least 0.1 seconds.");
+        if (options.ConsumeAttemptDelay < 0.1)
+            throw new ArgumentOutOfRangeException(
+                nameof(options.ConsumeAttemptDelay),
+                options.ConsumeAttemptDelay,
+                "Options.ConsumeAttemptDelay must be at least 0.1 seconds.");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Frends.AzureEventHub.Receive/Frends.AzureEventHub.Receive/AzureEventHub.cs`
around lines 39 - 40, Replace the generic Exception thrown for the
ConsumeAttemptDelay validation with an ArgumentOutOfRangeException: in the
method that checks options.ConsumeAttemptDelay, throw new
ArgumentOutOfRangeException("ConsumeAttemptDelay", "Options.ConsumeAttemptDelay
must be at least 0.1 seconds.") (or use nameof(ConsumeAttemptDelay) if
accessible) so callers receive a semantically correct error type for invalid
range; update the throw in the block that currently reads throw new
Exception("Options.ConsumeAttemptDelay must be at least 0.1 seconds.").
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Frends.AzureEventHub.Receive/CHANGELOG.md`:
- Around line 3-8: Update the 2.7.0 changelog entry to explicitly list the two
functional changes: (1) runtime validation now rejects ConsumeAttemptDelay
values < 0.1 — mark this as a potential breaking change and add an upgrade note
advising users to set ConsumeAttemptDelay >= 0.1, and (2) the event-loop stop
behavior changed so MaxRunTime expiry is checked in the outer loop and
Consumer.MaximumWaitTime also acts as an idle timeout that can terminate the
outer loop when no events arrive; explain how this affects Task exit timing;
also remove the trailing whitespace on the existing line with the short-delay
note.

---

Outside diff comments:
In `@Frends.AzureEventHub.Receive/Frends.AzureEventHub.Receive/AzureEventHub.cs`:
- Around line 86-97: The outer-loop stop condition incorrectly reuses
Consumer.MaximumWaitTime as an idle-exit timeout and uses lastEventTime
initialized before setup, causing premature exits; update the XML docs in
Definitions/Consumer.cs and CHANGELOG to document the behavior change or note
that MaximumWaitTime is only for the SDK (preferred), then modify the loop logic
so it does NOT treat MaximumWaitTime as the outer-loop idle timeout (remove the
DateTime.UtcNow - lastEventTime check) or, if you intend to keep the idle-exit
feature, reinitialize lastEventTime immediately after StartProcessingAsync
succeeds (or on the first loop iteration) to anchor the idle window to the
actual start of processing; locate symbols: Consumer.MaximumWaitTime,
lastEventTime, CreateBlobContainerClient, CreateIfNotExistsAsync,
CreateEventProcessorClient, and StartProcessingAsync to implement and document
the chosen fix.

---

Nitpick comments:
In `@Frends.AzureEventHub.Receive/Frends.AzureEventHub.Receive/AzureEventHub.cs`:
- Around line 48-56: The field local variable lastEventTime is accessed from
different threads (written inside ProcessEventHandler and read in the outer
idle-timeout loop) and may suffer torn 64-bit reads; change lastEventTime from
DateTime to a long representing DateTime.UtcNow.Ticks and use
Interlocked.Exchange to set it in ProcessEventHandler and Interlocked.Read (or
Volatile.Read) to read it in the idle-checking loop, converting ticks back to
DateTime when needed (reference symbols: lastEventTime, ProcessEventHandler, and
the idle-timeout loop that reads lastEventTime).
- Around line 39-40: Replace the generic Exception thrown for the
ConsumeAttemptDelay validation with an ArgumentOutOfRangeException: in the
method that checks options.ConsumeAttemptDelay, throw new
ArgumentOutOfRangeException("ConsumeAttemptDelay", "Options.ConsumeAttemptDelay
must be at least 0.1 seconds.") (or use nameof(ConsumeAttemptDelay) if
accessible) so callers receive a semantically correct error type for invalid
range; update the throw in the block that currently reads throw new
Exception("Options.ConsumeAttemptDelay must be at least 0.1 seconds.").

In
`@Frends.AzureEventHub.Receive/Frends.AzureEventHub.Receive/Definitions/Options.cs`:
- Line 20: The comment for the minimum value in Options.cs is ambiguous—update
the note next to the relevant property (the "NOTE: Must be at least 0.1" line)
to include the unit "seconds" (e.g., "NOTE: Must be at least 0.1 seconds") so it
clearly matches the preceding "in seconds" description and prevents
misinterpretation in docs or validation messages.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 677eb32f-f4a3-4912-8a28-630e3c95fab0

📥 Commits

Reviewing files that changed from the base of the PR and between 9aa6fdc and c1da0de.

📒 Files selected for processing (4)
  • Frends.AzureEventHub.Receive/CHANGELOG.md
  • Frends.AzureEventHub.Receive/Frends.AzureEventHub.Receive/AzureEventHub.cs
  • Frends.AzureEventHub.Receive/Frends.AzureEventHub.Receive/Definitions/Options.cs
  • Frends.AzureEventHub.Receive/Frends.AzureEventHub.Receive/Frends.AzureEventHub.Receive.csproj

Comment on lines +3 to +8
## [2.7.0] - 2026-04-26

### Fixed

- Resolve the problem with short delays between checks

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Expand the 2.7.0 entry — current note doesn't describe the actual changes or potential breaking behavior.

Two functional changes land in this release, neither of which is captured by "Resolve the problem with short delays between checks":

  1. A new runtime validation rejects ConsumeAttemptDelay < 0.1. This is a behavior change and potentially a breaking change for any caller that previously configured a smaller value — it should have an upgrade note.
  2. The event-loop stop condition changed: MaxRunTime expiry is now evaluated in the outer loop, and Consumer.MaximumWaitTime now also acts as an idle timeout that terminates the outer loop when no events arrive within that window. This alters when the Task exits and should be documented.

Also, line 7 has trailing whitespace.

As per coding guidelines: "Include all functional changes and indicate breaking changes with upgrade notes. Avoid notes like 'refactored xyz' unless it affects functionality."

📝 Suggested rewording
 ## [2.7.0] - 2026-04-26
-
-### Fixed
-
-- Resolve the problem with short delays between checks 
+
+### Changed
+
+- `Options.ConsumeAttemptDelay` now enforces a minimum of `0.1` seconds; values below this throw at runtime.
+  Upgrade note: review pipelines that set a smaller delay and raise the value to at least `0.1`.
+- Event-loop stop conditions reworked: `Options.MaxRunTime` is now checked in the outer loop, and
+  `Consumer.MaximumWaitTime` additionally terminates the loop if no event is received within that window.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Frends.AzureEventHub.Receive/CHANGELOG.md` around lines 3 - 8, Update the
2.7.0 changelog entry to explicitly list the two functional changes: (1) runtime
validation now rejects ConsumeAttemptDelay values < 0.1 — mark this as a
potential breaking change and add an upgrade note advising users to set
ConsumeAttemptDelay >= 0.1, and (2) the event-loop stop behavior changed so
MaxRunTime expiry is checked in the outer loop and Consumer.MaximumWaitTime also
acts as an idle timeout that can terminate the outer loop when no events arrive;
explain how this affects Task exit timing; also remove the trailing whitespace
on the existing line with the short-delay note.

@MichalFrends1 MichalFrends1 merged commit 0c71f8f into main Apr 28, 2026
7 checks passed
@MichalFrends1 MichalFrends1 deleted the fspes-108 branch April 28, 2026 10:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants