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
25 changes: 22 additions & 3 deletions src/core/Akka.TestKit/Internal/InternalTestActor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Channels;
using Akka.Actor;
using Akka.Event;
Expand All @@ -21,17 +22,35 @@ namespace Akka.TestKit.Internal;
internal sealed class InternalTestActor : UntypedActor
{
private readonly ChannelWriter<MessageEnvelope> _queue;
private readonly ManualResetEventSlim? _initComplete;
private TestActor.Ignore? _ignore;
private AutoPilot? _autoPilot;
private readonly DelegatingSupervisorStrategy _supervisorStrategy = new();

/// <summary>
/// TBD
/// Creates a new internal test actor.
/// </summary>
/// <param name="queue">TBD</param>
public InternalTestActor(ChannelWriter<MessageEnvelope> queue)
/// <param name="queue">The message queue to write received messages to.</param>
public InternalTestActor(ChannelWriter<MessageEnvelope> queue) : this(queue, null)
{
}

/// <summary>
/// Creates a new internal test actor with initialization synchronization.
/// </summary>
/// <param name="queue">The message queue to write received messages to.</param>
/// <param name="initComplete">Event to signal when actor initialization is complete.</param>
public InternalTestActor(ChannelWriter<MessageEnvelope> queue, ManualResetEventSlim? initComplete)
{
_queue = queue;
_initComplete = initComplete;
}

protected override void PreStart()
{
base.PreStart();
// Signal that actor initialization is complete
_initComplete?.Set();
}

/// <summary>
Expand Down
18 changes: 14 additions & 4 deletions src/core/Akka.TestKit/TestKitBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -721,17 +721,27 @@ private IActorRef CreateInitialTestActor(ActorSystem system, string name)
// Fix both serialization and deadlock issues:
// 1. Use isSystemService=true to skip serialization checks
// 2. Use isAsync=false to create LocalActorRef synchronously (avoids RepointableActorRef deadlock)
var testActorProps = Props.Create(() => new InternalTestActor(_testState.Queue))
// 3. Use ManualResetEventSlim to ensure PreStart completes before returning (fixes parallel init race)
using var initComplete = new ManualResetEventSlim(false);

var testActorProps = Props.Create(() => new InternalTestActor(_testState.Queue, initComplete))
.WithDispatcher("akka.test.test-actor.dispatcher");

var systemImpl = system.AsInstanceOf<ActorSystemImpl>();
// Use the new AttachChildWithAsync method to create TestActor synchronously
var testActor = systemImpl.Provider.SystemGuardian.Cell.AttachChildWithAsync(
testActorProps,
testActorProps,
isSystemService: true, // Skip serialization checks
isAsync: false, // Create synchronously to avoid deadlock
name: name);


// Wait for TestActor.PreStart() to complete before returning
// This ensures the actor is fully initialized and ready to receive messages
if (!initComplete.Wait(TimeSpan.FromSeconds(10)))
{
throw new TimeoutException($"TestActor '{name}' failed to initialize within 10 seconds");
}

return testActor;
}

Expand Down