Merge pull request #53 from slingamn/datarace.1

fix #52
This commit is contained in:
Shivaram Lingamneni 2021-03-17 02:24:30 -04:00 committed by GitHub
commit f3d1c7c294
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 60 additions and 17 deletions

@ -134,7 +134,7 @@ func (irc *Connection) readLoop() {
return return
} }
if irc.batchNegotiated && time.Since(lastExpireCheck) > irc.Timeout { if irc.batchNegotiated() && time.Since(lastExpireCheck) > irc.Timeout {
irc.expireBatches(false) irc.expireBatches(false)
lastExpireCheck = time.Now() lastExpireCheck = time.Now()
} }
@ -376,7 +376,7 @@ func (irc *Connection) Send(command string, params ...string) error {
// If the server fails to respond correctly, the callback will be invoked with `nil` // If the server fails to respond correctly, the callback will be invoked with `nil`
// as the argument. // as the argument.
func (irc *Connection) SendWithLabel(callback func(*Batch), tags map[string]string, command string, params ...string) error { func (irc *Connection) SendWithLabel(callback func(*Batch), tags map[string]string, command string, params ...string) error {
if !irc.labelNegotiated { if !irc.labelNegotiated() {
return CapabilityNotNegotiated return CapabilityNotNegotiated
} }
@ -691,14 +691,7 @@ func (irc *Connection) negotiateCaps() error {
var acknowledgedCaps []string var acknowledgedCaps []string
defer func() { defer func() {
irc.stateMutex.Lock() irc.processAckedCaps(acknowledgedCaps)
defer irc.stateMutex.Unlock()
for _, c := range acknowledgedCaps {
irc.capsAcked[c] = irc.capsAdvertised[c]
}
_, irc.batchNegotiated = irc.capsAcked["batch"]
_, labelNegotiated := irc.capsAcked["labeled-response"]
irc.labelNegotiated = irc.batchNegotiated && labelNegotiated
}() }()
irc.Send("CAP", "LS", "302") irc.Send("CAP", "LS", "302")

@ -325,7 +325,7 @@ func (irc *Connection) runCallbacks(msg ircmsg.Message) {
} }
// handle batch start or end // handle batch start or end
if irc.batchNegotiated { if irc.batchNegotiated() {
if msg.Command == "BATCH" { if msg.Command == "BATCH" {
irc.handleBatchCommand(msg) irc.handleBatchCommand(msg)
return return
@ -336,7 +336,7 @@ func (irc *Connection) runCallbacks(msg ircmsg.Message) {
} }
// handle labeled single command, or labeled ACK // handle labeled single command, or labeled ACK
if irc.labelNegotiated { if irc.labelNegotiated() {
if hasLabel, labelStr := msg.GetTag("label"); hasLabel { if hasLabel, labelStr := msg.GetTag("label"); hasLabel {
var labelCallback LabelCallback var labelCallback LabelCallback
if label := deserializeLabel(labelStr); label != 0 { if label := deserializeLabel(labelStr); label != 0 {

@ -10,6 +10,7 @@ import (
"net" "net"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/goshuirc/irc-go/ircmsg" "github.com/goshuirc/irc-go/ircmsg"
@ -86,11 +87,10 @@ type Connection struct {
// Connect() builds these with sufficient capacity to receive all expected // Connect() builds these with sufficient capacity to receive all expected
// responses during negotiation. Sends to them are nonblocking, so anything // responses during negotiation. Sends to them are nonblocking, so anything
// sent outside of negotiation will not cause the relevant callbacks to block. // sent outside of negotiation will not cause the relevant callbacks to block.
welcomeChan chan empty // signals that we got 001 and we are now connected welcomeChan chan empty // signals that we got 001 and we are now connected
saslChan chan saslResult // transmits the final outcome of SASL negotiation saslChan chan saslResult // transmits the final outcome of SASL negotiation
capsChan chan capResult // transmits the final status of each CAP negotiated capsChan chan capResult // transmits the final status of each CAP negotiated
batchNegotiated bool capFlags uint32
labelNegotiated bool
// callback state // callback state
eventsMutex sync.Mutex eventsMutex sync.Mutex
@ -140,6 +140,56 @@ type Batch struct {
Items []*Batch Items []*Batch
} }
const (
capFlagBatch uint32 = 1 << iota
capFlagMessageTags
capFlagLabeledResponse
capFlagMultiline
)
func (irc *Connection) processAckedCaps(acknowledgedCaps []string) {
irc.stateMutex.Lock()
defer irc.stateMutex.Unlock()
var hasBatch, hasLabel, hasTags, hasMultiline bool
for _, c := range acknowledgedCaps {
irc.capsAcked[c] = irc.capsAdvertised[c]
switch c {
case "batch":
hasBatch = true
case "labeled-response":
hasLabel = true
case "message-tags":
hasTags = true
case "draft/multiline", "multiline":
hasMultiline = true
}
}
var capFlags uint32
if hasBatch {
capFlags |= capFlagBatch
}
if hasBatch && hasLabel {
capFlags |= capFlagLabeledResponse
}
if hasTags {
capFlags |= capFlagMessageTags
}
if hasTags && hasBatch && hasMultiline {
capFlags |= capFlagMultiline
}
atomic.StoreUint32(&irc.capFlags, capFlags)
}
func (irc *Connection) batchNegotiated() bool {
return atomic.LoadUint32(&irc.capFlags)&capFlagBatch != 0
}
func (irc *Connection) labelNegotiated() bool {
return atomic.LoadUint32(&irc.capFlags)&capFlagLabeledResponse != 0
}
func ExtractNick(source string) string { func ExtractNick(source string) string {
nick, _, _ := SplitNUH(source) nick, _, _ := SplitNUH(source)
return nick return nick