Destroying Dynamic, Replicated Components Causes Client Connection Loss

I have a ‘Weapon’ Actor Component class, which is spawned at runtime by it’s owning actor and added to it’s inventory. These Components replicate, and are correctly created on the client and behave as they’re supposed to.

However, if I try to destroy this component on the Server WITHOUT destroying the actor that owns it (e.g, to replace the weapon via a powerup) - the client is kicked out of the session. This is the log:

[2016.07.07-18.08.36:084][779]LogNetPackageMap:Warning: GetObjectFromNetGUID: Server re-loading object (might have been GC'd). FullNetGUIDPath: [30]EMPTY
[2016.07.07-18.08.36:084][779]LogNetTraffic:Error: ReadContentBlockHeader: Client attempted to create sub-object. Actor: BP_Sabre_C_1
[2016.07.07-18.08.36:084][779]LogNet:Error: UActorChannel::ReadContentBlockPayload: ReadContentBlockHeader FAILED. Bunch.IsError() == TRUE. Closing connection. RepObj: NULL, Channel: 30
[2016.07.07-18.08.36:084][779]LogNet:Error: UActorChannel::ReceivedBunch: ReadContentBlockPayload FAILED. Bunch.IsError() == TRUE. Closing connection. RepObj: NULL, Channel: 30
[2016.07.07-18.08.36:084][779]LogNet: UNetConnection::Close: [UNetConnection] RemoteAddr: 127.0.0.1:64492, Name: IpConnection_3, Driver: GameNetDriver IpNetDriver_2, IsServer: YES, PC: BP_BZPlayerController_C_1, Owner: BP_BZPlayerController_C_1, Channels: 32, Time: 2016.07.07-18.08.36
[2016.07.07-18.08.36:084][779]LogNet: UChannel::Close: Sending CloseBunch. ChIndex == 0. Name: [UChannel] ChIndex: 0, Closing: 0 [UNetConnection] RemoteAddr: 127.0.0.1:64492, Name: IpConnection_3, Driver: GameNetDriver IpNetDriver_2, IsServer: YES, PC: BP_BZPlayerController_C_1, Owner: BP_BZPlayerController_C_1
[2016.07.07-18.08.36:085][779]LogNetTraffic:Error: UChannel::ReceivedRawBunch: Bunch.IsError() after ReceivedNextBunch 1
[2016.07.07-18.08.36:085][779]LogNetTraffic:Error: Received corrupted packet data from client 127.0.0.1.  Disconnecting.
[2016.07.07-18.08.36:087][779]LogNet: UChannel::ReceivedSequencedBunch: Bunch.bClose == true. ChIndex == 0. Calling ConditionalCleanUp.
[2016.07.07-18.08.36:087][779]LogNet: UChannel::CleanUp: ChIndex == 0. Closing connection. [UChannel] ChIndex: 0, Closing: 0 [UNetConnection] RemoteAddr: 127.0.0.1:7777, Name: IpConnection_2, Driver: GameNetDriver IpNetDriver_3, IsServer: NO, PC: BP_BZPlayerController_C_0, Owner: BP_BZPlayerController_C_0
[2016.07.07-18.08.36:087][779]LogNet: UNetConnection::Close: [UNetConnection] RemoteAddr: 127.0.0.1:7777, Name: IpConnection_2, Driver: GameNetDriver IpNetDriver_3, IsServer: NO, PC: BP_BZPlayerController_C_0, Owner: BP_BZPlayerController_C_0, Channels: 32, Time: 2016.07.07-18.08.36
[2016.07.07-18.08.36:087][779]LogNet: UChannel::Close: Sending CloseBunch. ChIndex == 0. Name: [UChannel] ChIndex: 0, Closing: 0 [UNetConnection] RemoteAddr: 127.0.0.1:7777, Name: IpConnection_2, Driver: GameNetDriver IpNetDriver_3, IsServer: NO, PC: BP_BZPlayerController_C_0, Owner: BP_BZPlayerController_C_0
[2016.07.07-18.08.36:087][779]LogNet:Warning: Network Failure: GameNetDriver[ConnectionLost]: Your connection to the host has been lost.
[2016.07.07-18.08.36:088][779]LogNet:Warning: Network Failure: GameNetDriver[ConnectionLost]: Your connection to the host has been lost.

This is how I create the weapon objects. I wonder if perhaps I need to set some custom UObject flags on them or something? I literally can’t work this out at all since there’s no reason this should happen.

Finally tracked it down. Posting the result for those who may come accross this.

Turns out I was calling a Server function on the Client, after the component had been destroyed by the Server. The Server would call DestroyComponent() (which by default also happens on all connected clients), and when the client called it, they would also call a Server function.

Of course by the time the RPC gets to the Server, the Server has already called ‘Destroy’, so it doesn’t exist and thinks the client is trying to create a component. Now it all makes sense…

In OPs case it may be obvious that it does not work if you e.g. send an RPC on destroy. However, consider the general case where the server decides to destroy a component and a client at the same time decides to send an RPC to the same component. What will happen here?

Is this inherently a race condition between the server’s DestroyComponent() and an RPC from the client to the same component? What is the solution? As I see it, either you never destroy components or you never use client->server RPCs.

Have I perhaps misunderstood something?

Correct, that is a race condition considering RPCs are asynchronous calls.

The solution is to build a resilient system that doesn’t fire RPCs on objects getting destroyed, but instead make some other object do that. In this instance I would make the component’s owner fire that RPC when the component is destroyed. Or maybe I wouldn’t fire RPCs at all, and so it depends on the given situation.

This bad case reappear when I call the RPC function in the Client BeginPlay of ActorComponent.

1 Like