ScuttleBot
perf: async mode grants in topology provisioning Mode grants (AMODE/SAMODE) now fire in a goroutine instead of blocking the provisioning loop. Provisioning 6 channels with 12 bots was taking ~5 minutes synchronously, blocking all API requests that touch the topology manager. Now provisioning returns immediately and modes are applied in the background. Also reduced the ChanServ registration wait from 600ms to 200ms.
Commit
7b2b457b594374dc8a95323296f04cdf81fdc287aac80c28693a98760522ff3c
Parent
4e1758ef682c597…
1 file changed
+17
-24
+17
-24
| --- internal/topology/topology.go | ||
| +++ internal/topology/topology.go | ||
| @@ -205,43 +205,36 @@ | ||
| 205 | 205 | } |
| 206 | 206 | |
| 207 | 207 | func (m *Manager) provision(ch ChannelConfig) error { |
| 208 | 208 | // Register with ChanServ (idempotent — fails silently if already registered). |
| 209 | 209 | m.chanserv("REGISTER %s", ch.Name) |
| 210 | - // Give ChanServ time to process the registration before issuing follow-up | |
| 211 | - // commands. Retry the sleep up to 3 times so transient load doesn't cause | |
| 212 | - // TOPIC/ACCESS commands to fire before registration completes. | |
| 213 | - for range 3 { | |
| 214 | - time.Sleep(200 * time.Millisecond) | |
| 215 | - if m.client.IsConnected() { | |
| 216 | - break | |
| 217 | - } | |
| 218 | - } | |
| 210 | + time.Sleep(200 * time.Millisecond) // one short wait for ChanServ to process | |
| 219 | 211 | |
| 220 | 212 | if ch.Topic != "" { |
| 221 | 213 | m.chanserv("TOPIC %s %s", ch.Name, ch.Topic) |
| 222 | 214 | } |
| 223 | 215 | |
| 224 | - // Set persistent auto-modes. Use ChanServ AMODE when possible, | |
| 225 | - // and SAMODE (oper) as immediate fallback. | |
| 226 | - for _, nick := range ch.Ops { | |
| 227 | - m.chanserv("AMODE %s +o %s", ch.Name, nick) | |
| 228 | - if m.operPass != "" { | |
| 229 | - m.client.Cmd.SendRawf("SAMODE %s +o %s", ch.Name, nick) | |
| 230 | - } | |
| 231 | - } | |
| 232 | - for _, nick := range ch.Voice { | |
| 233 | - m.chanserv("AMODE %s +v %s", ch.Name, nick) | |
| 234 | - if m.operPass != "" { | |
| 235 | - m.client.Cmd.SendRawf("SAMODE %s +v %s", ch.Name, nick) | |
| 236 | - } | |
| 237 | - } | |
| 238 | - | |
| 239 | 216 | // Apply channel modes (e.g. +m for moderated). |
| 240 | 217 | for _, mode := range ch.Modes { |
| 241 | 218 | m.client.Cmd.Mode(ch.Name, mode) |
| 242 | 219 | } |
| 220 | + | |
| 221 | + // Fire mode grants asynchronously — don't block provisioning. | |
| 222 | + go func(name string, ops, voice []string) { | |
| 223 | + for _, nick := range ops { | |
| 224 | + m.chanserv("AMODE %s +o %s", name, nick) | |
| 225 | + if m.operPass != "" && m.client != nil { | |
| 226 | + m.client.Cmd.SendRawf("SAMODE %s +o %s", name, nick) | |
| 227 | + } | |
| 228 | + } | |
| 229 | + for _, nick := range voice { | |
| 230 | + m.chanserv("AMODE %s +v %s", name, nick) | |
| 231 | + if m.operPass != "" && m.client != nil { | |
| 232 | + m.client.Cmd.SendRawf("SAMODE %s +v %s", name, nick) | |
| 233 | + } | |
| 234 | + } | |
| 235 | + }(ch.Name, ch.Ops, ch.Voice) | |
| 243 | 236 | |
| 244 | 237 | if len(ch.Autojoin) > 0 { |
| 245 | 238 | m.Invite(ch.Name, ch.Autojoin) |
| 246 | 239 | } |
| 247 | 240 | |
| 248 | 241 |
| --- internal/topology/topology.go | |
| +++ internal/topology/topology.go | |
| @@ -205,43 +205,36 @@ | |
| 205 | } |
| 206 | |
| 207 | func (m *Manager) provision(ch ChannelConfig) error { |
| 208 | // Register with ChanServ (idempotent — fails silently if already registered). |
| 209 | m.chanserv("REGISTER %s", ch.Name) |
| 210 | // Give ChanServ time to process the registration before issuing follow-up |
| 211 | // commands. Retry the sleep up to 3 times so transient load doesn't cause |
| 212 | // TOPIC/ACCESS commands to fire before registration completes. |
| 213 | for range 3 { |
| 214 | time.Sleep(200 * time.Millisecond) |
| 215 | if m.client.IsConnected() { |
| 216 | break |
| 217 | } |
| 218 | } |
| 219 | |
| 220 | if ch.Topic != "" { |
| 221 | m.chanserv("TOPIC %s %s", ch.Name, ch.Topic) |
| 222 | } |
| 223 | |
| 224 | // Set persistent auto-modes. Use ChanServ AMODE when possible, |
| 225 | // and SAMODE (oper) as immediate fallback. |
| 226 | for _, nick := range ch.Ops { |
| 227 | m.chanserv("AMODE %s +o %s", ch.Name, nick) |
| 228 | if m.operPass != "" { |
| 229 | m.client.Cmd.SendRawf("SAMODE %s +o %s", ch.Name, nick) |
| 230 | } |
| 231 | } |
| 232 | for _, nick := range ch.Voice { |
| 233 | m.chanserv("AMODE %s +v %s", ch.Name, nick) |
| 234 | if m.operPass != "" { |
| 235 | m.client.Cmd.SendRawf("SAMODE %s +v %s", ch.Name, nick) |
| 236 | } |
| 237 | } |
| 238 | |
| 239 | // Apply channel modes (e.g. +m for moderated). |
| 240 | for _, mode := range ch.Modes { |
| 241 | m.client.Cmd.Mode(ch.Name, mode) |
| 242 | } |
| 243 | |
| 244 | if len(ch.Autojoin) > 0 { |
| 245 | m.Invite(ch.Name, ch.Autojoin) |
| 246 | } |
| 247 | |
| 248 |
| --- internal/topology/topology.go | |
| +++ internal/topology/topology.go | |
| @@ -205,43 +205,36 @@ | |
| 205 | } |
| 206 | |
| 207 | func (m *Manager) provision(ch ChannelConfig) error { |
| 208 | // Register with ChanServ (idempotent — fails silently if already registered). |
| 209 | m.chanserv("REGISTER %s", ch.Name) |
| 210 | time.Sleep(200 * time.Millisecond) // one short wait for ChanServ to process |
| 211 | |
| 212 | if ch.Topic != "" { |
| 213 | m.chanserv("TOPIC %s %s", ch.Name, ch.Topic) |
| 214 | } |
| 215 | |
| 216 | // Apply channel modes (e.g. +m for moderated). |
| 217 | for _, mode := range ch.Modes { |
| 218 | m.client.Cmd.Mode(ch.Name, mode) |
| 219 | } |
| 220 | |
| 221 | // Fire mode grants asynchronously — don't block provisioning. |
| 222 | go func(name string, ops, voice []string) { |
| 223 | for _, nick := range ops { |
| 224 | m.chanserv("AMODE %s +o %s", name, nick) |
| 225 | if m.operPass != "" && m.client != nil { |
| 226 | m.client.Cmd.SendRawf("SAMODE %s +o %s", name, nick) |
| 227 | } |
| 228 | } |
| 229 | for _, nick := range voice { |
| 230 | m.chanserv("AMODE %s +v %s", name, nick) |
| 231 | if m.operPass != "" && m.client != nil { |
| 232 | m.client.Cmd.SendRawf("SAMODE %s +v %s", name, nick) |
| 233 | } |
| 234 | } |
| 235 | }(ch.Name, ch.Ops, ch.Voice) |
| 236 | |
| 237 | if len(ch.Autojoin) > 0 { |
| 238 | m.Invite(ch.Name, ch.Autojoin) |
| 239 | } |
| 240 | |
| 241 |