Browse Source

fix #1824

Make (*Client).Sessions() lock-free
devel+issue1824_lockfree
Shivaram Lingamneni 2 years ago
parent
commit
28f79f57b2
2 changed files with 38 additions and 27 deletions
  1. 8
    9
      irc/client.go
  2. 30
    18
      irc/getters.go

+ 8
- 9
irc/client.go View File

@@ -15,6 +15,7 @@ import (
15 15
 	"sync"
16 16
 	"sync/atomic"
17 17
 	"time"
18
+	"unsafe"
18 19
 
19 20
 	ident "github.com/ergochat/go-ident"
20 21
 	"github.com/ergochat/irc-go/ircfmt"
@@ -104,7 +105,7 @@ type Client struct {
104 105
 	registrationTimer  *time.Timer
105 106
 	server             *Server
106 107
 	skeleton           string
107
-	sessions           []*Session
108
+	sessions           unsafe.Pointer
108 109
 	stateMutex         sync.RWMutex // tier 1
109 110
 	alwaysOn           bool
110 111
 	username           string
@@ -360,7 +361,7 @@ func (server *Server) RunClient(conn IRCConn) {
360 361
 		isTor:      wConn.Config.Tor,
361 362
 		hideSTS:    wConn.Config.Tor || wConn.Config.HideSTS,
362 363
 	}
363
-	client.sessions = []*Session{session}
364
+	client.setSessions([]*Session{session})
364 365
 
365 366
 	session.resetFakelag()
366 367
 
@@ -1018,9 +1019,7 @@ func (client *Client) FriendsMonitors(capabs ...caps.Capability) (result map[*Se
1018 1019
 
1019 1020
 // helper for Friends
1020 1021
 func addFriendsToSet(set map[*Session]empty, client *Client, capabs ...caps.Capability) {
1021
-	client.stateMutex.RLock()
1022
-	defer client.stateMutex.RUnlock()
1023
-	for _, session := range client.sessions {
1022
+	for _, session := range client.Sessions() {
1024 1023
 		if session.capabilities.HasAll(capabs...) {
1025 1024
 			set[session] = empty{}
1026 1025
 		}
@@ -1174,7 +1173,7 @@ func (client *Client) Quit(message string, session *Session) {
1174 1173
 	if session != nil {
1175 1174
 		sessions = []*Session{session}
1176 1175
 	} else {
1177
-		sessions = client.sessions
1176
+		sessions = client.Sessions()
1178 1177
 	}
1179 1178
 
1180 1179
 	for _, session := range sessions {
@@ -1213,8 +1212,8 @@ func (client *Client) destroy(session *Session) {
1213 1212
 
1214 1213
 	var remainingSessions int
1215 1214
 	if session == nil {
1216
-		sessionsToDestroy = client.sessions
1217
-		client.sessions = nil
1215
+		sessionsToDestroy = client.Sessions()
1216
+		client.setSessions(nil)
1218 1217
 		remainingSessions = 0
1219 1218
 	} else {
1220 1219
 		sessionRemoved, remainingSessions = client.removeSession(session)
@@ -1241,7 +1240,7 @@ func (client *Client) destroy(session *Session) {
1241 1240
 	shouldDestroy := !client.destroyed && remainingSessions == 0 && !alwaysOn
1242 1241
 	// decrement stats on a true destroy, or for the removal of the last connected session
1243 1242
 	// of an always-on client
1244
-	shouldDecrement := shouldDestroy || (alwaysOn && len(sessionsToDestroy) != 0 && len(client.sessions) == 0)
1243
+	shouldDecrement := shouldDestroy || (alwaysOn && len(sessionsToDestroy) != 0 && len(client.Sessions()) == 0)
1245 1244
 	if shouldDestroy {
1246 1245
 		// if it's our job to destroy it, don't let anyone else try
1247 1246
 		client.destroyed = true

+ 30
- 18
irc/getters.go View File

@@ -48,10 +48,11 @@ func (server *Server) SetDefcon(defcon uint32) {
48 48
 }
49 49
 
50 50
 func (client *Client) Sessions() (sessions []*Session) {
51
-	client.stateMutex.RLock()
52
-	sessions = client.sessions
53
-	client.stateMutex.RUnlock()
54
-	return
51
+	ptr := atomic.LoadPointer(&client.sessions)
52
+	if ptr == nil {
53
+		return nil
54
+	}
55
+	return *((*[]*Session)(ptr))
55 56
 }
56 57
 
57 58
 type SessionData struct {
@@ -67,12 +68,14 @@ type SessionData struct {
67 68
 }
68 69
 
69 70
 func (client *Client) AllSessionData(currentSession *Session, hasPrivs bool) (data []SessionData, currentIndex int) {
70
-	currentIndex = -1
71 71
 	client.stateMutex.RLock()
72 72
 	defer client.stateMutex.RUnlock()
73 73
 
74
-	data = make([]SessionData, len(client.sessions))
75
-	for i, session := range client.sessions {
74
+	currentIndex = -1
75
+	sessions := client.Sessions()
76
+
77
+	data = make([]SessionData, len(sessions))
78
+	for i, session := range sessions {
76 79
 		if session == currentSession {
77 80
 			currentIndex = i
78 81
 		}
@@ -107,40 +110,49 @@ func (client *Client) AddSession(session *Session) (success bool, numSessions in
107 110
 		return
108 111
 	}
109 112
 	// success, attach the new session to the client
113
+	sessions := client.Sessions()
110 114
 	session.client = client
111 115
 	session.sessionID = client.nextSessionID
112 116
 	client.nextSessionID++
113
-	newSessions := make([]*Session, len(client.sessions)+1)
114
-	copy(newSessions, client.sessions)
117
+	newSessions := make([]*Session, len(sessions)+1)
118
+	copy(newSessions, sessions)
115 119
 	newSessions[len(newSessions)-1] = session
116 120
 	if client.accountSettings.AutoreplayMissed || session.deviceID != "" {
117 121
 		lastSeen = client.lastSeen[session.deviceID]
118 122
 		client.setLastSeen(time.Now().UTC(), session.deviceID)
119 123
 	}
120
-	client.sessions = newSessions
124
+	client.setSessions(newSessions)
121 125
 	// TODO(#1551) there should be a cap to opt out of this behavior on a session
122 126
 	if persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway) {
123 127
 		client.awayMessage = ""
124
-		if len(client.sessions) == 1 {
128
+		if len(newSessions) == 1 {
125 129
 			back = true
126 130
 		}
127 131
 	}
128
-	return true, len(client.sessions), lastSeen, back
132
+	return true, len(newSessions), lastSeen, back
133
+}
134
+
135
+func (client *Client) setSessions(sessions []*Session) {
136
+	// must be called while holding stateMutex.Lock()
137
+	sessionsPtr := new([]*Session)
138
+	(*sessionsPtr) = sessions
139
+	atomic.StorePointer(&client.sessions, unsafe.Pointer(sessionsPtr))
129 140
 }
130 141
 
131 142
 func (client *Client) removeSession(session *Session) (success bool, length int) {
132
-	if len(client.sessions) == 0 {
143
+	oldSessions := client.Sessions()
144
+	if len(oldSessions) == 0 {
133 145
 		return
134 146
 	}
135
-	sessions := make([]*Session, 0, len(client.sessions)-1)
136
-	for _, currentSession := range client.sessions {
147
+	sessions := make([]*Session, 0, len(oldSessions)-1)
148
+	for _, currentSession := range oldSessions {
137 149
 		if session == currentSession {
138 150
 			success = true
139 151
 		} else {
140 152
 			sessions = append(sessions, currentSession)
141 153
 		}
142 154
 	}
143
-	client.sessions = sessions
155
+	client.setSessions(sessions)
144 156
 	length = len(sessions)
145 157
 	return
146 158
 }
@@ -150,7 +162,7 @@ func (client *Client) getWhoisActually() (ip net.IP, hostname string) {
150 162
 	client.stateMutex.RLock()
151 163
 	defer client.stateMutex.RUnlock()
152 164
 
153
-	for _, session := range client.sessions {
165
+	for _, session := range client.Sessions() {
154 166
 		return session.IP(), session.rawHostname
155 167
 	}
156 168
 	return utils.IPv4LoopbackAddress, client.server.name
@@ -223,7 +235,7 @@ func (client *Client) setAutoAwayNoMutex(config *Config) {
223 235
 	// aggregate the away statuses of the individual sessions:
224 236
 	var globalAwayState string
225 237
 	var awaySetAt time.Time
226
-	for _, cSession := range client.sessions {
238
+	for _, cSession := range client.Sessions() {
227 239
 		if cSession.awayMessage == "" {
228 240
 			// a session is active, we are not auto-away
229 241
 			client.awayMessage = ""

Loading…
Cancel
Save