Sfoglia il codice sorgente

fix #1387

Instead of building a new serialized message for each recipient,
try to cache them.
tags/v2.5.0-rc1
Shivaram Lingamneni 3 anni fa
parent
commit
ec15d367ba
6 ha cambiato i file con 249 aggiunte e 20 eliminazioni
  1. 12
    14
      irc/channel.go
  2. 12
    5
      irc/client.go
  3. 197
    0
      irc/message_cache.go
  4. 1
    1
      irc/responsebuffer.go
  5. 17
    0
      irc/utils/bitset.go
  6. 10
    0
      irc/utils/bitset_test.go

+ 12
- 14
irc/channel.go Vedi File

@@ -779,6 +779,9 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp
779 779
 		modestr = fmt.Sprintf("+%v", givenMode)
780 780
 	}
781 781
 
782
+	// cache the most common case (JOIN without extended-join)
783
+	var cache MessageCache
784
+	cache.Initialize(channel.server, message.Time, message.Msgid, details.nickMask, details.accountName, nil, "JOIN", chname)
782 785
 	isAway, awayMessage := client.Away()
783 786
 	for _, member := range channel.Members() {
784 787
 		if respectAuditorium {
@@ -799,7 +802,7 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp
799 802
 			if session.capabilities.Has(caps.ExtendedJoin) {
800 803
 				session.sendFromClientInternal(false, message.Time, message.Msgid, details.nickMask, details.accountName, nil, "JOIN", chname, details.accountName, details.realname)
801 804
 			} else {
802
-				session.sendFromClientInternal(false, message.Time, message.Msgid, details.nickMask, details.accountName, nil, "JOIN", chname)
805
+				cache.Send(session)
803 806
 			}
804 807
 			if givenMode != 0 {
805 808
 				session.Send(nil, client.server.name, "MODE", chname, modestr, details.nick)
@@ -933,6 +936,8 @@ func (channel *Channel) Part(client *Client, message string, rb *ResponseBuffer)
933 936
 	}
934 937
 	respectAuditorium := channel.flags.HasMode(modes.Auditorium) &&
935 938
 		clientModes.HighestChannelUserMode() == modes.Mode(0)
939
+	var cache MessageCache
940
+	cache.Initialize(channel.server, splitMessage.Time, splitMessage.Msgid, details.nickMask, details.accountName, nil, "PART", params...)
936 941
 	for _, member := range channel.Members() {
937 942
 		if respectAuditorium {
938 943
 			channel.stateMutex.RLock()
@@ -942,7 +947,9 @@ func (channel *Channel) Part(client *Client, message string, rb *ResponseBuffer)
942 947
 				continue
943 948
 			}
944 949
 		}
945
-		member.sendFromClientInternal(false, splitMessage.Time, splitMessage.Msgid, details.nickMask, details.accountName, nil, "PART", params...)
950
+		for _, session := range member.Sessions() {
951
+			cache.Send(session)
952
+		}
946 953
 	}
947 954
 	rb.AddFromClient(splitMessage.Time, splitMessage.Msgid, details.nickMask, details.accountName, nil, "PART", params...)
948 955
 	for _, session := range client.Sessions() {
@@ -1322,6 +1329,8 @@ func (channel *Channel) SendSplitMessage(command string, minPrefixMode modes.Mod
1322 1329
 	// send echo-message
1323 1330
 	rb.addEchoMessage(clientOnlyTags, details.nickMask, details.accountName, command, chname, message)
1324 1331
 
1332
+	var cache MessageCache
1333
+	cache.InitializeSplitMessage(channel.server, details.nickMask, details.accountName, clientOnlyTags, command, chname, message)
1325 1334
 	for _, member := range channel.Members() {
1326 1335
 		if minPrefixMode != modes.Mode(0) && !channel.ClientIsAtLeast(member, minPrefixMode) {
1327 1336
 			// STATUSMSG or OpModerated
@@ -1337,18 +1346,7 @@ func (channel *Channel) SendSplitMessage(command string, minPrefixMode modes.Mod
1337 1346
 				continue // #753
1338 1347
 			}
1339 1348
 
1340
-			var tagsToUse map[string]string
1341
-			if session.capabilities.Has(caps.MessageTags) {
1342
-				tagsToUse = clientOnlyTags
1343
-			} else if histType == history.Tagmsg {
1344
-				continue
1345
-			}
1346
-
1347
-			if histType == history.Tagmsg {
1348
-				session.sendFromClientInternal(false, message.Time, message.Msgid, details.nickMask, details.accountName, tagsToUse, command, chname)
1349
-			} else {
1350
-				session.sendSplitMsgFromClientInternal(false, details.nickMask, details.accountName, tagsToUse, command, chname, message)
1351
-			}
1349
+			cache.Send(session)
1352 1350
 		}
1353 1351
 	}
1354 1352
 

+ 12
- 5
irc/client.go Vedi File

@@ -1551,8 +1551,12 @@ func (client *Client) destroy(session *Session) {
1551 1551
 	if quitMessage == "" {
1552 1552
 		quitMessage = "Exited"
1553 1553
 	}
1554
+	var cache MessageCache
1555
+	cache.Initialize(client.server, splitQuitMessage.Time, splitQuitMessage.Msgid, details.nickMask, details.accountName, nil, "QUIT", quitMessage)
1554 1556
 	for friend := range friends {
1555
-		friend.sendFromClientInternal(false, splitQuitMessage.Time, splitQuitMessage.Msgid, details.nickMask, details.accountName, nil, "QUIT", quitMessage)
1557
+		for _, session := range friend.Sessions() {
1558
+			cache.Send(session)
1559
+		}
1556 1560
 	}
1557 1561
 
1558 1562
 	if registered {
@@ -1567,7 +1571,7 @@ func (session *Session) sendSplitMsgFromClientInternal(blocking bool, nickmask,
1567 1571
 		session.sendFromClientInternal(blocking, message.Time, message.Msgid, nickmask, accountName, tags, command, target, message.Message)
1568 1572
 	} else {
1569 1573
 		if session.capabilities.Has(caps.Multiline) {
1570
-			for _, msg := range session.composeMultilineBatch(nickmask, accountName, tags, command, target, message) {
1574
+			for _, msg := range composeMultilineBatch(session.generateBatchID(), nickmask, accountName, tags, command, target, message) {
1571 1575
 				session.SendRawMessage(msg, blocking)
1572 1576
 			}
1573 1577
 		} else {
@@ -1614,12 +1618,11 @@ func (session *Session) sendFromClientInternal(blocking bool, serverTime time.Ti
1614 1618
 	return session.SendRawMessage(msg, blocking)
1615 1619
 }
1616 1620
 
1617
-func (session *Session) composeMultilineBatch(fromNickMask, fromAccount string, tags map[string]string, command, target string, message utils.SplitMessage) (result []ircmsg.IrcMessage) {
1618
-	batchID := session.generateBatchID()
1621
+func composeMultilineBatch(batchID, fromNickMask, fromAccount string, tags map[string]string, command, target string, message utils.SplitMessage) (result []ircmsg.IrcMessage) {
1619 1622
 	batchStart := ircmsg.MakeMessage(tags, fromNickMask, "BATCH", "+"+batchID, caps.MultilineBatchType, target)
1620 1623
 	batchStart.SetTag("time", message.Time.Format(IRCv3TimestampFormat))
1621 1624
 	batchStart.SetTag("msgid", message.Msgid)
1622
-	if session.capabilities.Has(caps.AccountTag) && fromAccount != "*" {
1625
+	if fromAccount != "*" {
1623 1626
 		batchStart.SetTag("account", fromAccount)
1624 1627
 	}
1625 1628
 	result = append(result, batchStart)
@@ -1680,6 +1683,10 @@ func (session *Session) SendRawMessage(message ircmsg.IrcMessage, blocking bool)
1680 1683
 		return err
1681 1684
 	}
1682 1685
 
1686
+	return session.sendBytes(line, blocking)
1687
+}
1688
+
1689
+func (session *Session) sendBytes(line []byte, blocking bool) (err error) {
1683 1690
 	if session.client.server.logger.IsLoggingRawIO() {
1684 1691
 		logline := string(line[:len(line)-2]) // strip "\r\n"
1685 1692
 		session.client.server.logger.Debug("useroutput", session.client.Nick(), " ->", logline)

+ 197
- 0
irc/message_cache.go Vedi File

@@ -0,0 +1,197 @@
1
+// Copyright (c) 2020 Shivaram Lingamneni <slingamn@cs.stanford.edu>
2
+// released under the MIT license
3
+
4
+package irc
5
+
6
+import (
7
+	"time"
8
+
9
+	"github.com/goshuirc/irc-go/ircmsg"
10
+
11
+	"github.com/oragono/oragono/irc/caps"
12
+	"github.com/oragono/oragono/irc/utils"
13
+)
14
+
15
+// MessageCache caches serialized IRC messages.
16
+// First call Initialize or InitializeSplitMessage, which records
17
+// the parameters and builds the cache. Then call Send, which will
18
+// either send a cached version of the message or dispatch to another
19
+// send routine that can synthesize the necessary version on the fly.
20
+type MessageCache struct {
21
+	// these cache a single-line message (e.g., JOIN, or PRIVMSG with a 512-byte message)
22
+	// one version is "plain" (legacy clients with no tags) and one is "full" (client has
23
+	// the message-tags cap)
24
+	plain    []byte
25
+	fullTags []byte
26
+	// these cache a multiline message (a PRIVMSG that was sent as a multiline batch)
27
+	// one version is "plain" (legacy clients with no tags) and one is "full" (client has
28
+	// the multiline cap)
29
+	plainMultiline    [][]byte
30
+	fullTagsMultiline [][]byte
31
+
32
+	time        time.Time
33
+	msgid       string
34
+	accountName string
35
+	tags        map[string]string
36
+	source      string
37
+	command     string
38
+
39
+	params []string
40
+
41
+	target       string
42
+	splitMessage utils.SplitMessage
43
+}
44
+
45
+func addAllTags(msg *ircmsg.IrcMessage, tags map[string]string, serverTime time.Time, msgid, accountName string) {
46
+	msg.UpdateTags(tags)
47
+	msg.SetTag("time", serverTime.Format(IRCv3TimestampFormat))
48
+	if accountName != "*" {
49
+		msg.SetTag("account", accountName)
50
+	}
51
+	if msgid != "" {
52
+		msg.SetTag("msgid", msgid)
53
+	}
54
+}
55
+
56
+func (m *MessageCache) handleErr(server *Server, err error) bool {
57
+	if err != nil {
58
+		server.logger.Error("internal", "Error assembling message for sending", err.Error())
59
+		// blank these out so Send will be a no-op
60
+		m.fullTags = nil
61
+		m.fullTagsMultiline = nil
62
+		return true
63
+	}
64
+	return false
65
+}
66
+
67
+func (m *MessageCache) Initialize(server *Server, serverTime time.Time, msgid string, nickmask, accountName string, tags map[string]string, command string, params ...string) (err error) {
68
+	m.time = serverTime
69
+	m.msgid = msgid
70
+	m.source = nickmask
71
+	m.accountName = accountName
72
+	m.tags = tags
73
+	m.command = command
74
+	m.params = params
75
+
76
+	var msg ircmsg.IrcMessage
77
+	config := server.Config()
78
+	if config.Server.Compatibility.forceTrailing && commandsThatMustUseTrailing[command] {
79
+		msg.ForceTrailing()
80
+	}
81
+	msg.Prefix = nickmask
82
+	msg.Command = command
83
+	msg.Params = make([]string, len(params))
84
+	copy(msg.Params, params)
85
+	m.plain, err = msg.LineBytesStrict(false, MaxLineLen)
86
+	if m.handleErr(server, err) {
87
+		return
88
+	}
89
+
90
+	addAllTags(&msg, tags, serverTime, msgid, accountName)
91
+	m.fullTags, err = msg.LineBytesStrict(false, MaxLineLen)
92
+	if m.handleErr(server, err) {
93
+		return
94
+	}
95
+	return
96
+}
97
+
98
+func (m *MessageCache) InitializeSplitMessage(server *Server, nickmask, accountName string, tags map[string]string, command, target string, message utils.SplitMessage) (err error) {
99
+	m.time = message.Time
100
+	m.msgid = message.Msgid
101
+	m.source = nickmask
102
+	m.accountName = accountName
103
+	m.tags = tags
104
+	m.command = command
105
+	m.target = target
106
+	m.splitMessage = message
107
+
108
+	config := server.Config()
109
+	forceTrailing := config.Server.Compatibility.forceTrailing && commandsThatMustUseTrailing[command]
110
+
111
+	if message.Is512() {
112
+		isTagmsg := command == "TAGMSG"
113
+		var msg ircmsg.IrcMessage
114
+		if forceTrailing {
115
+			msg.ForceTrailing()
116
+		}
117
+
118
+		msg.Prefix = nickmask
119
+		msg.Command = command
120
+		if isTagmsg {
121
+			msg.Params = []string{target}
122
+		} else {
123
+			msg.Params = []string{target, message.Message}
124
+		}
125
+		m.params = msg.Params
126
+		if !isTagmsg {
127
+			m.plain, err = msg.LineBytesStrict(false, MaxLineLen)
128
+			if m.handleErr(server, err) {
129
+				return
130
+			}
131
+		}
132
+
133
+		addAllTags(&msg, tags, message.Time, message.Msgid, accountName)
134
+		m.fullTags, err = msg.LineBytesStrict(false, MaxLineLen)
135
+		if m.handleErr(server, err) {
136
+			return
137
+		}
138
+	} else {
139
+		var msg ircmsg.IrcMessage
140
+		if forceTrailing {
141
+			msg.ForceTrailing()
142
+		}
143
+		msg.Prefix = nickmask
144
+		msg.Command = command
145
+		msg.Params = make([]string, 2)
146
+		msg.Params[0] = target
147
+		m.plainMultiline = make([][]byte, len(message.Split))
148
+		for i, pair := range message.Split {
149
+			msg.Params[1] = pair.Message
150
+			m.plainMultiline[i], err = msg.LineBytesStrict(false, MaxLineLen)
151
+			if m.handleErr(server, err) {
152
+				return
153
+			}
154
+		}
155
+
156
+		// we need to send the same batch ID to all recipient sessions;
157
+		// use a uuidv4-alike to ensure that it won't collide
158
+		batch := composeMultilineBatch(utils.GenerateSecretToken(), nickmask, accountName, tags, command, target, message)
159
+		m.fullTagsMultiline = make([][]byte, len(batch))
160
+		for i, msg := range batch {
161
+			if forceTrailing {
162
+				msg.ForceTrailing()
163
+			}
164
+			m.fullTagsMultiline[i], err = msg.LineBytesStrict(false, MaxLineLen)
165
+			if m.handleErr(server, err) {
166
+				return
167
+			}
168
+		}
169
+	}
170
+	return
171
+}
172
+
173
+func (m *MessageCache) Send(session *Session) {
174
+	if m.fullTags != nil {
175
+		if session.capabilities.Has(caps.MessageTags) {
176
+			session.sendBytes(m.fullTags, false)
177
+		} else if !(session.capabilities.Has(caps.ServerTime) || session.capabilities.Has(caps.AccountTag)) {
178
+			if m.plain != nil {
179
+				session.sendBytes(m.plain, false)
180
+			}
181
+		} else {
182
+			session.sendFromClientInternal(false, m.time, m.msgid, m.source, m.accountName, nil, m.command, m.params...)
183
+		}
184
+	} else if m.fullTagsMultiline != nil {
185
+		if session.capabilities.Has(caps.Multiline) {
186
+			for _, line := range m.fullTagsMultiline {
187
+				session.sendBytes(line, false)
188
+			}
189
+		} else if !(session.capabilities.Has(caps.ServerTime) || session.capabilities.Has(caps.AccountTag)) {
190
+			for _, line := range m.plainMultiline {
191
+				session.sendBytes(line, false)
192
+			}
193
+		} else {
194
+			session.sendSplitMsgFromClientInternal(false, m.source, m.accountName, m.tags, m.command, m.target, m.splitMessage)
195
+		}
196
+	}
197
+}

+ 1
- 1
irc/responsebuffer.go Vedi File

@@ -127,7 +127,7 @@ func (rb *ResponseBuffer) AddSplitMessageFromClient(fromNickMask string, fromAcc
127 127
 		}
128 128
 	} else {
129 129
 		if rb.session.capabilities.Has(caps.Multiline) {
130
-			batch := rb.session.composeMultilineBatch(fromNickMask, fromAccount, tags, command, target, message)
130
+			batch := composeMultilineBatch(rb.session.generateBatchID(), fromNickMask, fromAccount, tags, command, target, message)
131 131
 			rb.setNestedBatchTag(&batch[0])
132 132
 			rb.setNestedBatchTag(&batch[len(batch)-1])
133 133
 			rb.messages = append(rb.messages, batch...)

+ 17
- 0
irc/utils/bitset.go Vedi File

@@ -17,6 +17,14 @@ func BitsetGet(set []uint32, position uint) bool {
17 17
 	return (block & (1 << bit)) != 0
18 18
 }
19 19
 
20
+// BitsetGetLocal returns whether a given bit of the bitset is set,
21
+// without synchronization.
22
+func BitsetGetLocal(set []uint32, position uint) bool {
23
+	idx := position / 32
24
+	bit := position % 32
25
+	return (set[idx] & (1 << bit)) != 0
26
+}
27
+
20 28
 // BitsetSet sets a given bit of the bitset to 0 or 1, returning whether it changed.
21 29
 func BitsetSet(set []uint32, position uint, on bool) (changed bool) {
22 30
 	idx := position / 32
@@ -79,6 +87,15 @@ func BitsetCopy(set []uint32, other []uint32) {
79 87
 	}
80 88
 }
81 89
 
90
+// BitsetCopyLocal copies the contents of `other` over `set`,
91
+// without synchronizing the writes to `set`.
92
+func BitsetCopyLocal(set []uint32, other []uint32) {
93
+	for i := 0; i < len(set); i++ {
94
+		data := atomic.LoadUint32(&other[i])
95
+		set[i] = data
96
+	}
97
+}
98
+
82 99
 // BitsetSubtract modifies `set` to subtract the contents of `other`.
83 100
 // Similar caveats about race conditions as with `BitsetUnion` apply.
84 101
 func BitsetSubtract(set []uint32, other []uint32) {

+ 10
- 0
irc/utils/bitset_test.go Vedi File

@@ -80,4 +80,14 @@ func TestSets(t *testing.T) {
80 80
 	if !BitsetGet(t3s, 0) || BitsetGet(t3s, 72) || !BitsetGet(t3s, 74) || BitsetGet(t3s, 71) {
81 81
 		t.Error("subtract doesn't work")
82 82
 	}
83
+
84
+	var tlocal testBitset
85
+	tlocals := tlocal[:]
86
+	BitsetCopyLocal(tlocals, t1s)
87
+	for i = 0; i < 128; i++ {
88
+		expected := (i != 72)
89
+		if BitsetGetLocal(tlocals, i) != expected {
90
+			t.Error("all bits should be set except 72")
91
+		}
92
+	}
83 93
 }

Loading…
Annulla
Salva