-
Notifications
You must be signed in to change notification settings - Fork 363
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
Use Message Context #69
Conversation
@liggitt PTAL |
Will look, thanks! |
@@ -276,9 +293,11 @@ func (l *Conn) finishMessage(messageID int64) { | |||
} | |||
l.messageMutex.Unlock() | |||
|
|||
close(msgCtx.done) |
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.
shouldn't this happen before the the isClosing
check? otherwise we may still have potential for deadlocks
looks good, just a few comments. can you add a test that tries to trigger this as well (simulated request that returns multiple response packets when only one is expected)? ideally, it would fail without this patch in place (though the test might race with the finishRequest() message) |
e609659
to
f3851a1
Compare
@liggitt Thanks for the quick review, I have updated the PR per your comments and added a new // packetTranslatorConn is a helful type which can be used with various tests
// in this package. It implements the net.Conn interface to be used as an
// underlying connection for a *ldap.Conn. Most methods are no-ops but the
// Read() and Write() methods are able to translate ber-encoded packets for
// testing LDAP requests and responses.
//
// Test cases can simulate an LDAP server sending a response by calling the
// SendResponse() method with a ber-encoded LDAP response packet. Test cases
// can simulate an LDAP server receiving a request from a client by calling the
// ReceiveRequest() method which returns a ber-encoded LDAP request packet.
type packetTranslatorConn struct {
sync.Mutex
isClosed bool
responseCond sync.Cond
requestCond sync.Cond
responseBuf bytes.Buffer
requestBuf bytes.Buffer
} You can use it in tests like this: func TestSomething(t *testing.T) {
ptc := newPacketTranslatorConn()
defer ptc.Close()
conn := NewConn(ptc, false)
conn.Start()
defer conn.Close()
// Create an LDAP request packet and send it as usual.
requestPacket := makeRequestPacket()
msgCtx, _ := conn.sendMessage()
// Decode the request packet on the other side of the connection.
serverRequest, _ := ptc.ReceiveRequest()
// Create a response packet and send it.
serverResponse := makeResponsePacket()
ptc.SendResponse(serverResponse)
// Handle the response as usual.
responsePacket <- msgCtx.responses
} |
18ac5f3
to
b8ec9f1
Compare
err error | ||
) | ||
|
||
runWithTimeout(t, time.Millisecond, func() { |
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.
millisecond timing is way too tight for something depending on a loop inside a separate goroutine... can lead to test flake failures if anything else is hitting the CPU while the test is running. If the main purpose is to prevent a test hanging forever, I'd do something like a second
@liggitt thanks for the comments. I've updated the branch again.
I forgot to mention that the new test doesn't build on master without a few modifications. |
This patch introduces a new type called `massageContext` which is now the return value of `(*Conn).sendMessage()`. The message context object still contains a channel from which methods like, Add(), Bind(), Search(), etc., will receive response packets. It also has a field which holds the message ID as well as a `done` channel which is used to prevent deadlock in the `processMessages()` goroutine. This is accomplished by also changing the `(*Conn).finishMessage()` method to take a message context and close this `done` channel before sending a `MessageFinish` packet to the `processMessages()` goroutine. The `processMessages()` goroutine now has a `massageContexts` map which replaces the `chanResults` map. Now, rather than sending response packets only on the response channels, the `messageContext` has its own `sendResponse()` method which uses a switch that blocks on sending a response packet *or* waiting for its `done` channel to be closed by `finishMessage()`. Docker-DCO-1.1-Signed-off-by: Josh Hawn <[email protected]> (github: jlhawn)
} | ||
|
||
func (l *Conn) finishMessage(messageID int64) { | ||
func (l *Conn) finishMessage(msgCtx *messageContext) { | ||
close(msgCtx.done) |
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.
The best way to see the new tests fail is to just comment out this line.
thanks for the report and fix |
This patch introduces a new type called
messageContext
which is now thereturn value of
(*Conn).sendMessage()
. The message context object stillcontains a channel from which methods like, Add(), Bind(), Search(), etc.,
will receive response packets. It also has a field which holds the message
ID as well as a
done
channel which is used to prevent deadlock in theprocessMessages()
goroutine.This is accomplished by also changing the
(*Conn).finishMessage()
method totake a message context and close this
done
channel before sending aMessageFinish
packet to theprocessMessages()
goroutine.The
processMessages()
goroutine now has amessageContexts
map whichreplaces the
chanResults
map. Now, rather than sending response packets onlyon the response channels, the
messageContext
has its ownsendResponse()
method which uses a switch that blocks on sending a response packet or
waiting for its
done
channel to be closed byfinishMessage()
.fixes #68