Skip to content

Commit 1a37014

Browse files
upgrade to Akka.NET v1.5.46 (#495)
1 parent 64c3545 commit 1a37014

File tree

4 files changed

+91
-9
lines changed

4 files changed

+91
-9
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,3 +206,4 @@ build/
206206
.idea
207207
/.cursor
208208
BenchmarkDotNet.Artifacts/
209+
/.claude

Directory.Packages.props

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22
<PropertyGroup>
33
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
44
<!-- Nuget package versions -->
5-
<AkkaVersion>1.5.42</AkkaVersion>
5+
<AkkaVersion>1.5.46</AkkaVersion>
66
<!-- Unit test Nuget package versions -->
77
<NBenchVersion>1.2.2</NBenchVersion>
8-
<TestSdkVersion>17.4.1</TestSdkVersion>
98
<AspireVersion>9.3.0</AspireVersion>
109
</PropertyGroup>
1110
<!-- App dependencies -->

examples/EventHub.Consumer/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ public static async Task Main(string[] args)
5555

5656
var committerDefaults = CommitterSettings.Create(system);
5757

58-
// Comment for simple no-commit consumer
58+
// Comment for committable consumer
5959
var control = KafkaConsumer.CommittableSource(consumerSettings, subscription)
6060
.SelectAsync(1, msg =>
61-
Business(msg.Record).ContinueWith(done => (ICommittable)msg.CommitableOffset))
61+
Business(msg.Record).ContinueWith(ICommittable (_) => msg.CommitableOffset))
6262
.ToMaterialized(
6363
Committer.Sink(committerDefaults.WithMaxBatch(1)),
6464
DrainingControl<NotUsed>.Create)

src/Akka.Streams.Kafka/Stages/Consumers/Abstract/SubSourceLogic.cs

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Collections.Generic;
99
using System.Collections.Immutable;
1010
using System.Linq;
11+
using System.Threading;
1112
using System.Threading.Tasks;
1213
using Akka.Actor;
1314
using Akka.Streams.Dsl;
@@ -140,6 +141,21 @@ private readonly Option<Func<IImmutableSet<TopicPartition>, Task<IImmutableSet<T
140141
/// </summary>
141142
private IImmutableSet<TopicPartition> _partitionsToRevoke = ImmutableHashSet<TopicPartition>.Empty;
142143

144+
/// <summary>
145+
/// Cancellation token source for shutting down seek operations when stage is stopping
146+
/// </summary>
147+
private readonly CancellationTokenSource _shutdownTokenSource = new();
148+
149+
/// <summary>
150+
/// Track when the stage started to detect race conditions
151+
/// </summary>
152+
private readonly DateTime _startTime = DateTime.UtcNow;
153+
154+
/// <summary>
155+
/// Track if seek operation is in progress to detect race conditions
156+
/// </summary>
157+
private volatile bool _seekInProgress = false;
158+
143159
protected StageActor SourceActor { get; private set; } = null!;
144160
public IActorRef ConsumerActor { get; private set; } = null!;
145161

@@ -295,9 +311,15 @@ public override void PreStart()
295311

296312
public override void PostStop()
297313
{
314+
// Cancel any ongoing seek operations
315+
_shutdownTokenSource.Cancel();
316+
298317
ConsumerActor.Tell(new KafkaConsumerActorMetadata.Internal.StopFromStage(_shape.ToString()), SourceActor.Ref);
299318

300319
Control.OnShutdown();
320+
321+
// Clean up cancellation token
322+
_shutdownTokenSource.Dispose();
301323

302324
base.PostStop();
303325
}
@@ -379,17 +401,58 @@ private void SeekAndEmitSubSources(IImmutableSet<TopicPartition> formerlyUnknown
379401

380402
async Task AskToSeekOffsets()
381403
{
404+
_seekInProgress = true;
405+
406+
if (Log.IsDebugEnabled)
407+
Log.Debug("#{0} Starting seek operation for partitions: {1}", _actorNumber, offsets.JoinToString(", "));
408+
382409
try
383410
{
384411
await ConsumerActor.Ask(new KafkaConsumerActorMetadata.Internal.Seek(offsets),
385-
TimeSpan.FromSeconds(10));
412+
TimeSpan.FromSeconds(10), _shutdownTokenSource.Token);
413+
414+
if (Log.IsDebugEnabled)
415+
Log.Debug("#{0} Seek operation completed successfully", _actorNumber);
416+
386417
_updatePendingPartitionsAndEmitSubSourcesCallback(formerlyUnknown);
387418
}
388-
catch (Exception)
419+
catch (OperationCanceledException) when (_shutdownTokenSource.IsCancellationRequested)
389420
{
390-
// only exceptions that can be thrown here are related to TCS cancellation / timeout
391-
_stageFailCallback(new ConsumerFailed(
392-
$"{_actorNumber} Consumer failed during seek, Ask timed out. Partitions: {offsets.JoinToString(", ")}"));
421+
// Stage is shutting down, this is expected - don't treat as fatal error
422+
if (Log.IsDebugEnabled)
423+
Log.Debug("#{0} Seek operation cancelled due to stage shutdown (expected). Partitions: {1}",
424+
_actorNumber, offsets.JoinToString(", "));
425+
}
426+
catch (Exception ex)
427+
{
428+
var timeSinceStart = DateTime.UtcNow.Subtract(_startTime);
429+
430+
// Other exceptions (timeout, etc.) - check if we're shutting down before failing
431+
if (_shutdownTokenSource.IsCancellationRequested)
432+
{
433+
// Stage is shutting down, timeout is expected
434+
if (Log.IsDebugEnabled)
435+
Log.Debug("#{0} Seek operation timed out during stage shutdown (expected). Partitions: {1}",
436+
_actorNumber, offsets.JoinToString(", "));
437+
}
438+
else
439+
{
440+
// Check for race condition pattern - very fast failure
441+
if (timeSinceStart.TotalMilliseconds < 100)
442+
{
443+
Log.Warning("#{0} Seek operation failed very quickly ({1}ms after start) - possible race condition. " +
444+
"Error: {2}, Partitions: {3}",
445+
_actorNumber, timeSinceStart.TotalMilliseconds, ex.Message, offsets.JoinToString(", "));
446+
}
447+
448+
// Unexpected timeout while stage is still active
449+
_stageFailCallback(new ConsumerFailed(
450+
$"{_actorNumber} Consumer failed during seek, Ask timed out. Partitions: {offsets.JoinToString(", ")}", ex));
451+
}
452+
}
453+
finally
454+
{
455+
_seekInProgress = false;
393456
}
394457
}
395458
}
@@ -497,9 +560,28 @@ private void PerformStop()
497560

498561
private void PerformShutdown()
499562
{
563+
var timeSinceStart = DateTime.UtcNow.Subtract(_startTime);
564+
565+
// Only log if this looks like the race condition (very fast shutdown with seek in progress)
566+
if (timeSinceStart.TotalMilliseconds < 100 && _seekInProgress)
567+
{
568+
Log.Warning("#{0} Potential race condition detected: PerformShutdown called {1}ms after start " +
569+
"with seek operation still in progress. Pending: {2}, InStartup: {3}, SubSources: {4}",
570+
_actorNumber, timeSinceStart.TotalMilliseconds, _pendingPartitions.Count,
571+
_partitionsInStartup.Count, _subSources.Count);
572+
}
573+
else if (Log.IsDebugEnabled)
574+
{
575+
Log.Debug("#{0} PerformShutdown called normally after {1}ms. SeekInProgress: {2}",
576+
_actorNumber, timeSinceStart.TotalMilliseconds, _seekInProgress);
577+
}
578+
500579
Log.Info("Completing. Partitions [{0}], StageActor {1}", string.Join(", ", _subSources.Keys), SourceActor.Ref);
501580
SetKeepGoing(true);
502581

582+
// Cancel any ongoing seek operations immediately to avoid timeouts
583+
_shutdownTokenSource.Cancel();
584+
503585
// TODO from alpakka: we should wait for subsources to be shutdown and next shutdown main stage
504586
_subSources.Values.Select(c => c.ControlAndStageActor.Control).ForEach(control => control.Shutdown());
505587

0 commit comments

Comments
 (0)