Skip to content
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

SendAsync not sending all data fix plus various stabilizations and improvements #160

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

frg-kova
Copy link

Main fix is for issue #159 where SendAsync wouldn't send all the data on a first call. In attempt to hunt down that bug, I've made quite a bit stabilization discoveries that helped to increase rarity of the main bug as well. They are:

Cleanup of reused pooled objects when entering pool

Guaranteed closed bi channel connection fix with socket close not to leave connection socket open and client in disconnected state. A real bug as well we've found.

ability to reset and reuse the writer as it often does not need to go into the pool but single instance can be reused

not doing infinite timeout (a preference, should be config option really)

allocating buffer to include header and avoid header alloc later

... and lots more comments on how code works as I learned it.

There's a lot! I'm open for a call if something needs explanation.

@4real
Copy link
Contributor

4real commented Dec 20, 2022

My concern is the test suite is failing on my local computer, and not just a few, but most of them. (Whereas on the DR2 master, just a few fail.) That doesn't necessarily imply that your changes are bad, but might imply that the tests need to be updated.

Also, the branch needs to be merged with/rebased on master since now I've gotten around to preparing for a new release by merging PRs. But the higher prio is resolving the test suite failures.

An alternative to reduce complexity is to put your changes into multiple isolated PRs/branches.

@frg-kova
Copy link
Author

I see how splitting all the changes into isolated small PRs is great and way to go. Having said that there's a lot and I do not have a few days to spend to pull them out one. My working env. is Unity so I cannot switch easily into it and out without spending some time which I don't have ATM.

@4real
Copy link
Contributor

4real commented Dec 20, 2022

No worries, I started working on it.

A lot of thanks for everything you already did (in your free time!) - your code and documentation is very clear.

@JamJar00
Copy link
Member

I think part of the code is missing here. All you're doing is adding a random 4 byte padding to the start of every message, but then that 4 byte padding hasn't been taken into account in the deserialization Hence it's not passing any of the tests

Given this comment on the issue, I'm guessing that 4 byte padding is actually supposed to be for the length header on TCP messages? (I also vaguely recall that being a change from when I skimmed through this PR ages ago). If that is the case then this change is incompatible with all the other listeners except the bichannel listeners because none of them will be expecting that change so we should definitely not merge.

Let's get the rest of this PR recovered and then work out whether it's compatible of not though!

@frg-kova
Copy link
Author

frg-kova commented Dec 21, 2022

You're right I am working only with BiChannelConnection myself and I have tunnel vision there. Do all messages require headers? If not it's uncompatible, for that specific fix which is a minor thing.

The difference is in BiChannelClientConnection.SendMessageReliable, it used to be forced to expand by 4 bytes and using BufferLists. Receiving remained the same as it's just a matter of buffer space alloc not message structure.

Used to be this

            byte[] header = new byte[4];
            BigEndianHelper.WriteBytes(header, 0, message.Count);

            SocketAsyncEventArgs args = ObjectCache.GetSocketAsyncEventArgs();

            args.SetBuffer(null, 0, 0);
            args.BufferList = new List<ArraySegment<byte>>()
            {
                new ArraySegment<byte>(header),
                new ArraySegment<byte>(message.Buffer, message.Offset, message.Count)
            };

and now is
args.SetBuffer(message.Buffer, message.Offset, message.Count);

@JamJar00
Copy link
Member

Ah I think maybe I just missed the minified diff there.

I can't say I'm a big fan of the change in the protocol. It'll add 4 bytes to every UDP message we send and it'll break compatibility with all other listeners. The change has also only been made to the client side, it should also be applied to the server as well.

I do however think we should distil the changes here to the fixes you've identified, because that's all great investigation work and important to get in. If BufferList is causing us issues lets get rid of it, even if that means nother memory copy or something!

@frg-kova
Copy link
Author

The BufferList isn't a problem, that change is only to get rid of the 4 byte alloc on every send (plus to make things more unified and use SetBuffer as everywhere else). Ignore the change since it breaks other listeners.

Ideally we pass in an argument for additional reserved header size if needed.

try
{
tcpSocket.Shutdown(SocketShutdown.Both);
udpSocket.Shutdown(SocketShutdown.Both);
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it make sense to do this as two separate trys? What if the tcp socket was already shutdown, but udp socket wasn't?

Copy link
Author

Choose a reason for hiding this comment

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

yes, nice catch! pun intended

@@ -361,7 +388,12 @@ private void ReceiveHeaderAndBody(SocketAsyncEventArgs args)
{
headerCompletingAsync = tcpSocket.ReceiveAsync(args);
}
catch (ObjectDisposedException)
catch (ObjectDisposedException)
Copy link
Contributor

Choose a reason for hiding this comment

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

Indentation?

@@ -427,7 +464,12 @@ private void AsyncReceiveBodyCompleted(object sender, SocketAsyncEventArgs args)
{
headerCompletingAsync = tcpSocket.ReceiveAsync(args);
}
catch (ObjectDisposedException)
catch (ObjectDisposedException)
Copy link
Contributor

Choose a reason for hiding this comment

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

Indentation?

@@ -447,23 +489,28 @@ private void AsyncReceiveBodyCompleted(object sender, SocketAsyncEventArgs args)
/// </summary>
/// <param name="args">The socket args used during the operation.</param>
/// <returns>If the whole header has been received.</returns>
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe remove?

}

/// <summary>
/// Checks if a TCP body was received in its entirety.
/// </summary>
/// <param name="args">The socket args used during the operation.</param>
/// <returns>If the whole body has been received.</returns>
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe remove?

Copy link
Author

Choose a reason for hiding this comment

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

left as it doesn't compile, forgot why, and the very next line "[MethodImpl(256)]" is the same thing just with direct value instead of onum so that works. Its more of a comment then code. Could be explained better on why it doesn't work.

@@ -489,20 +531,29 @@ private int ProcessHeader(SocketAsyncEventArgs args)
/// </summary>
/// <param name="args">The socket args used during the operation.</param>
/// <returns>The buffer received.</returns>
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe remove?

}

/// <summary>
/// Invokes message recevied events and cleans up.
/// </summary>
/// <param name="buffer">The TCP body received.</param>
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe remove?

@@ -511,6 +562,8 @@ private void ProcessMessage(MessageBuffer buffer)
/// </summary>
/// <param name="args">The socket args used during the operation.</param>
/// <returns>If the receive completed correctly.</returns>
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe remove?

args.UserToken = bodyBuffer;
//Debug.Log($"Receive buffer created. Size: [{length}], Capacity: [{bodyBuffer.Buffer.Length}], Offset: [{bodyBuffer.Offset}]");
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it make sense to keep unity-specific (I assume) log comments?

Copy link
Author

Choose a reason for hiding this comment

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

I left them for future debug efforts if someone just wants to change the call params would be ready, I think it would be useful

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.

4 participants