-
Notifications
You must be signed in to change notification settings - Fork 581
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
Reduce lock contention for frame writes (with extra tests) #354
Reduce lock contention for frame writes (with extra tests) #354
Conversation
Details discussed on Google Groups https://groups.google.com/forum/?nomobile=true#!topic/rabbitmq-users/OyoYTkAyCSk
Updated code to remove lock contention
Updating to configure await on connectasync as per trunk
This reverts commit 3aecf44.
…ient into YulerB-master
@michaelklishin I've been meaning to try out these changes, but haven't gotten to them yet. The only way I can think to make trying it out easier would be for your to publish some sort of prerelease package, perhaps to a custom MyGet feed instead of nuget.org. Did you see @danielmarbach's request on the other PR about adding tests with several different payload sizes? Seems like a good idea to validate this continues to be better as message size increases. |
@bording OK, we had an AppVeyor feed for development releases but I'm afraid that thing hasn't been updated in a while. I am going to add more tests with different payload sizes, very good idea @danielmarbach. Any particular values you have in mind? |
I did some benchmarking with 20 runs of the concurrent connection access test suite on two machines (from 2017 and 2013). In one case, median went down from 1.35s ( |
@michaelklishin That sounds really good! I spent some time today getting some of my testing setup ready to try out the changes here, so I should have something to report back tomorrow. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok this generally looks good. Just a few formatting, tidy up changes. Naturally it is almost impossible to say this is definitely safe but it looks ok to me.
@@ -1099,7 +1099,7 @@ public void HeartbeatWriteTimerCallback(object state) | |||
if (!m_closed) | |||
{ | |||
WriteFrame(m_heartbeatFrame); | |||
m_frameHandler.Flush(); | |||
//m_frameHandler.Flush(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove commented code
@@ -465,11 +464,11 @@ public void ModelSend(MethodBase method, ContentHeaderBase header, byte[] body) | |||
{ | |||
if (method.HasContent) | |||
{ | |||
lock (m_flowSendLock) | |||
{ | |||
//lock (m_flowSendLock) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove commented code
@@ -908,11 +912,11 @@ public void HandleChannelFlow(bool active) | |||
} | |||
else | |||
{ | |||
lock (m_flowSendLock) | |||
{ | |||
//lock (m_flowSendLock) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove commented code
@@ -767,7 +767,7 @@ public void MainLoopIteration() | |||
} | |||
} | |||
|
|||
public void NotifyHeartbeatListener() | |||
public void NotifyHeartbeatListener() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove double space
@@ -88,7 +87,7 @@ public abstract class ModelBase : IFullModel, IRecoverable | |||
|
|||
private EventHandler<EventArgs> m_recovery; | |||
|
|||
public IConsumerDispatcher ConsumerDispatcher { get; private set; } | |||
public IConsumerDispatcher ConsumerDispatcher { get; private set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove extra space
{ | ||
throw new UnexpectedFrameException(f); | ||
} | ||
byte[] fragment = f.Payload; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there real value in removing this variable? I would have thought (hoped) the compiler would optimise this away anyway.
Since the frame sizes are calculable, might be wothwhile setting the capacity on the memory streams to avoid resizing and memory fragmentation. For example,
|
@YulerB if you worry about allocs then better no use Linq ;) |
@danielmarbach Linq gets a bad rap, but I know what you mean. Many of the Framesets with 4k, which would require 2 re-allocs of the byte buffer in the MemoryStream. Many of the message like Ack are much smaller than the default 1k, so we have over-allocation of memory . This might avoid some of the memory thrashing. To avoid linq, here is an extension method.
|
@bording @michaelklishin I've set our ci to push each PR build to myget - would this work for testing this PR? https://www.myget.org/feed/Packages/rabbitmq |
@kjnilsson Yes, having a MyGet feed that the results of each PR will be helpful for the future. FYI, assuming your MyGet account name is |
I have done some testing of these changes with a locally built copy of the client. I am seeing a small benefit on some tests that specifically ensure that we're publishing messages with multiple channels from the same connection. I'm also not seeing any performance regressions, so overall it seems like this PR is 👍 from me, with the caveat that I haven't spent a lot of time actually looking at the content of the changes. |
@bording my personal concerns were about concurrency hazard safety but I assume if there were any failures in your test suites you'd have mentioned it. I'll do some last round naming and test structure changes before merging. |
As integration test case subclasses are meant to do. Squashes a warning.
@michaelklishin I'll try and run a few more rounds of tests to be sure, but I've not seen any problems so far. |
There are no "read frames" and "write frames" in the protocol. "Incoming" and "outgoing" frames are also not well established terms but at least they hint at what the difference is and when they are used. There are existing methods with identical names (ReadFrame, WriteFrame) as well, which isn't helping.
Addressed Karl's feedback.
We published a 5.1.0-pre1 to nuget.org for others to try this change out. |
Thanks for you help on this @michaelklishin, @bording, @danielmarbach, @kjnilsson, @lukebakken, @vendre21. |
when transmitting multiple frames at once. While changes in #350, #354 are supposed to be safe, there's some evidence (#681) that sometimes they are not which leads to incorrect byte stream interleaving on the wire. Update API approval test expectations Re-introduce lock for all socket writes The official `NetworkStream` docs are confusing: https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.networkstream?view=netframework-4.5.1 > Read and write operations can be performed simultaneously on an instance of the NetworkStream class without the need for synchronization. As long as there is one unique thread for the write operations and one unique thread for the read operations, there will be no cross-interference between read and write threads and no synchronization is required. This is a poorly-written way to say "multiple threads must be synchronized". Fixes #681 Update API approval test expectations
This is #350 by @YulerB and @vendre21 with some additional tests from our team. See #350 for the original discussion.
The idea is the following:
BufferedStream
is no longer used (it was not efficiently used for writes anyway)This PR was created to simplify the final rounds of QA: