Make all write operations atomic#562
Conversation
ef993a8 to
f759d87
Compare
There was a problem hiding this comment.
This is still a todo though. Someday...
There was a problem hiding this comment.
What's preventing us from addressing it?
There was a problem hiding this comment.
Debugging. All I know is calling WriteAsync on SslStream didn't work when last I tried it. I think the call never completed, but I'm not 100% on that. You are definitely right that I should have left a better comment when I first wrote this code.
There was a problem hiding this comment.
K, so tests fail changing this to async. Will dig in and do a different PR if I get a resolution. Added issue #569
f759d87 to
aa49548
Compare
|
I don't think this is right. If you write to any stream in Also this is making |
|
Also a |
There was a problem hiding this comment.
We don't want to block libuv threads at all if we can avoid it.
|
However... SocketOutput does do leasing blocks and whatnot; so this change might be sensible to protect itself, rather than necessarily providing upstream benefit (or throwing a don't do concurrent writes execption?) |
|
The question is what's better? Current - unknown output, unknown state (I'd suggest this is bad) |
|
@benaadams I was/am also a big proponent for throwing much like we do for ReadAsync. The filter point you made is interesting, but currently mitigated by the lock I added to SocketOutputStream. The point you make about CopyTo is also a good one, you'll see similar issues if you use JsonSerializer to write to the response streams. Still, my experience with SignalR has shown me that people will try to write concurrently, and won't see that it's a problem until their app is under stress even if we do throw. And in this case, I think it's important that our internal data structures are in a good sate (i.e. our linked blocks), especially if we can ensure this without much of a perf hit. |
I like this to. It could very well be achieved with a |
|
Doing a guard may not cause a throw, so it could still do interleaving as per moving write into lock; but it might be good to have it as an api that can throw on concurrent (public api-wise)? A "be careful" modifier :) |
- _tasksCompleted no longer helps avoid locking longer
aa49548 to
85269b4
Compare
|
@benaadams I don't think we should remove the other changes just because we decide to throw instead of expand the lock. Moving the chunking logic down to the SocketOutput layer isn't strictly necessary, but it moves a single logical write down to a lower level which I think is a good thing. I also think rescheduling instead of blocking waiting for locks on the libuv thread is solid. It removes the need for multiple queues just to move work outside of the lock. What do you think? |
|
Yeh, think that's good - especially since throwing wasn't the cure all I thought it was going to be :( |
85269b4 to
ab5ef54
Compare
|
So, after further discussion with @lodejard and @CesarBS, I've flip-flopped back to not throwing for concurrent writes. The argument that won the day was that when the would-be-exception was thrown under load it would still only be one of two failure modes. There would still be a potential for out-of-order writes assuming order is actually important, but there is also a small chance you get an exception thrown as well. Also, unlike with concurrent reads, it's plausible that your app could work fine depending on the design even though it writes concurrently. The example given was logging, and how things like If we change our mind (and that's definitely possible), it should be relatively simple to change it to make it throw again. |
|
👍 makes sense. Rebase time 😝 |
This PR ensures that simultaneous calls to
SocketOutput.WriteAsyncwon't result in a corrupted write.This was mainly achieved by moving the
CopyFromcall inside ofSocketOutput.WriteAsyncinside of the_contenxtLock. I also moved chunking down a layer inside ofSocketOutput, so it could be protected by the same lock.I tested this change using both the plaintext benchmark and a modified LargeResponseSample that used small chunking, and in both cases the performance was nearly identical <1% to dev.
When I first tried only moving
CopyFrominside of the lock, I saw performance degradation of a few percent. I was able to remedy this by usingMonitor.TryEnterto avoid stalling libuv threads.