mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
fix(whatsapp_native): close TOCTOU race between eventHandler and Stop
Move the stopping check and wg.Add(1) inside reconnectMu in eventHandler, and set the stopping flag under the same lock in Stop(). This makes the two operations atomic with respect to each other, preventing the race where: 1. eventHandler checks stopping (false) 2. Stop() sets stopping=true and enters wg.Wait() (wg is 0) 3. eventHandler calls wg.Add(1) → panic or goroutine leak
This commit is contained in:
@@ -183,8 +183,14 @@ func (c *WhatsAppNativeChannel) Start(ctx context.Context) error {
|
||||
func (c *WhatsAppNativeChannel) Stop(ctx context.Context) error {
|
||||
logger.InfoC("whatsapp", "Stopping WhatsApp native channel")
|
||||
|
||||
// Mark as stopping so no new goroutines are spawned via eventHandler.
|
||||
// Mark as stopping under reconnectMu so the flag is visible to
|
||||
// eventHandler atomically with respect to its wg.Add(1) call.
|
||||
// This closes the TOCTOU window where eventHandler could check
|
||||
// stopping (false), then Stop sets it true + enters wg.Wait,
|
||||
// then eventHandler calls wg.Add(1) — causing a panic.
|
||||
c.reconnectMu.Lock()
|
||||
c.stopping.Store(true)
|
||||
c.reconnectMu.Unlock()
|
||||
|
||||
if c.runCancel != nil {
|
||||
c.runCancel()
|
||||
@@ -236,18 +242,21 @@ func (c *WhatsAppNativeChannel) eventHandler(evt any) {
|
||||
c.handleIncoming(evt.(*events.Message))
|
||||
case *events.Disconnected:
|
||||
logger.InfoCF("whatsapp", "WhatsApp disconnected, will attempt reconnection", nil)
|
||||
// Prevent new goroutines once Stop() has begun.
|
||||
if c.stopping.Load() {
|
||||
return
|
||||
}
|
||||
c.reconnectMu.Lock()
|
||||
if c.reconnecting {
|
||||
c.reconnectMu.Unlock()
|
||||
return
|
||||
}
|
||||
// Check stopping while holding the lock so the check and wg.Add
|
||||
// are atomic with respect to Stop() setting the flag + calling
|
||||
// wg.Wait(). This prevents the TOCTOU race.
|
||||
if c.stopping.Load() {
|
||||
c.reconnectMu.Unlock()
|
||||
return
|
||||
}
|
||||
c.reconnecting = true
|
||||
c.reconnectMu.Unlock()
|
||||
c.wg.Add(1)
|
||||
c.reconnectMu.Unlock()
|
||||
go func() {
|
||||
defer c.wg.Done()
|
||||
c.reconnectWithBackoff()
|
||||
|
||||
Reference in New Issue
Block a user