diff --git a/irc/client.go b/irc/client.go index ef15e237..ee553a33 100644 --- a/irc/client.go +++ b/irc/client.go @@ -820,13 +820,13 @@ func (client *Client) SendRawMessage(message ircmsg.IrcMessage) error { // assemble message maxlenTags, maxlenRest := client.maxlens() - line, err := message.LineMaxLen(maxlenTags, maxlenRest) + line, err := message.LineMaxLenBytes(maxlenTags, maxlenRest) if err != nil { logline := fmt.Sprintf("Error assembling message for sending: %v\n%s", err, debug.Stack()) client.server.logger.Error("internal", logline) message = ircmsg.MakeMessage(nil, client.server.name, ERR_UNKNOWNERROR, "*", "Error assembling message for sending") - line, _ := message.Line() + line, _ := message.LineBytes() client.socket.Write(line) return err @@ -834,10 +834,14 @@ func (client *Client) SendRawMessage(message ircmsg.IrcMessage) error { // if we used the trailing hack, we need to strip the final space we appended earlier on if usedTrailingHack { - line = line[:len(line)-3] + "\r\n" + copy(line[len(line)-3:], []byte{'\r', '\n'}) + line = line[:len(line)-1] } - client.server.logger.Debug("useroutput", client.nick, " ->", strings.TrimRight(line, "\r\n")) + if client.server.logger.IsLoggingRawIO() { + logline := string(line[:len(line)-2]) // strip "\r\n" + client.server.logger.Debug("useroutput", client.nick, " ->", logline) + } client.socket.Write(line) diff --git a/irc/socket.go b/irc/socket.go index 6382e498..663377d3 100644 --- a/irc/socket.go +++ b/irc/socket.go @@ -34,7 +34,8 @@ type Socket struct { // this is a trylock enforcing that only one goroutine can write to `conn` at a time writerSemaphore Semaphore - buffer []byte + buffers [][]byte + totalLength int closed bool sendQExceeded bool finalData string // what to send when we die @@ -121,15 +122,23 @@ func (socket *Socket) Read() (string, error) { // 2. MUST NOT reorder messages // 3. MUST provide mutual exclusion for socket.conn.Write // 4. SHOULD NOT tie up additional goroutines, beyond the one blocked on socket.conn.Write -func (socket *Socket) Write(data string) (err error) { +func (socket *Socket) Write(data []byte) (err error) { + if len(data) == 0 { + return + } + socket.Lock() if socket.closed { err = io.EOF - } else if len(data)+len(socket.buffer) > socket.maxSendQBytes { - socket.sendQExceeded = true - err = errSendQExceeded } else { - socket.buffer = append(socket.buffer, data...) + prospectiveLen := socket.totalLength + len(data) + if prospectiveLen > socket.maxSendQBytes { + socket.sendQExceeded = true + err = errSendQExceeded + } else { + socket.buffers = append(socket.buffers, data) + socket.totalLength = prospectiveLen + } } socket.Unlock() @@ -165,7 +174,7 @@ func (socket *Socket) readyToWrite() bool { socket.Lock() defer socket.Unlock() // on the first time observing socket.closed, we still have to write socket.finalData - return !socket.finalized && (len(socket.buffer) > 0 || socket.closed || socket.sendQExceeded) + return !socket.finalized && (socket.totalLength > 0 || socket.closed || socket.sendQExceeded) } // send actually writes messages to socket.Conn; it may block @@ -193,11 +202,13 @@ func (socket *Socket) send() { func (socket *Socket) performWrite() { // retrieve the buffered data, clear the buffer socket.Lock() - buffer := socket.buffer - socket.buffer = nil + buffers := socket.buffers + socket.buffers = nil + socket.totalLength = 0 socket.Unlock() - _, err := socket.conn.Write(buffer) + // on Linux, the runtime will optimize this into a single writev(2) call: + _, err := (*net.Buffers)(&buffers).WriteTo(socket.conn) socket.Lock() shouldClose := (err != nil) || socket.closed || socket.sendQExceeded