fix: bound reconnect duration to prevent indefinite lock holding (#113)
ReconnectWithBackoff retries forever with exponential backoff (2s–30s). When sync --follow loses its WhatsApp connection and can't recover, the process sits retrying indefinitely while holding the store lock, blocking all other wacli commands. Add a reconnect() wrapper that applies a deadline to the backoff loop. New --max-reconnect flag on sync (default 5m) controls this. Set to 0 for the old unlimited behavior. Fixes #88. Co-authored-by: Dinakar Sarbada <dinakars777@users.noreply.github.com>
This commit is contained in:
parent
f02ce5d301
commit
a684ff03ae
@ -14,6 +14,7 @@ func newSyncCmd(flags *rootFlags) *cobra.Command {
|
||||
var once bool
|
||||
var follow bool
|
||||
var idleExit time.Duration
|
||||
var maxReconnect time.Duration
|
||||
var downloadMedia bool
|
||||
var refreshContacts bool
|
||||
var refreshGroups bool
|
||||
@ -51,6 +52,7 @@ func newSyncCmd(flags *rootFlags) *cobra.Command {
|
||||
RefreshContacts: refreshContacts,
|
||||
RefreshGroups: refreshGroups,
|
||||
IdleExit: idleExit,
|
||||
MaxReconnect: maxReconnect,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@ -70,6 +72,7 @@ func newSyncCmd(flags *rootFlags) *cobra.Command {
|
||||
cmd.Flags().BoolVar(&once, "once", false, "sync until idle and exit")
|
||||
cmd.Flags().BoolVar(&follow, "follow", true, "keep syncing until Ctrl+C")
|
||||
cmd.Flags().DurationVar(&idleExit, "idle-exit", 30*time.Second, "exit after being idle (once mode)")
|
||||
cmd.Flags().DurationVar(&maxReconnect, "max-reconnect", 5*time.Minute, "give up reconnecting after this duration (0 = unlimited)")
|
||||
cmd.Flags().BoolVar(&downloadMedia, "download-media", false, "download media in the background during sync")
|
||||
cmd.Flags().BoolVar(&refreshContacts, "refresh-contacts", false, "refresh contacts from session store into local DB")
|
||||
cmd.Flags().BoolVar(&refreshGroups, "refresh-groups", false, "refresh joined groups (live) into local DB")
|
||||
|
||||
@ -31,6 +31,7 @@ type SyncOptions struct {
|
||||
RefreshContacts bool
|
||||
RefreshGroups bool
|
||||
IdleExit time.Duration // only used for bootstrap/once
|
||||
MaxReconnect time.Duration // max time to attempt reconnection before giving up (0 = unlimited)
|
||||
Verbosity int // future
|
||||
}
|
||||
|
||||
@ -183,7 +184,7 @@ func (a *App) Sync(ctx context.Context, opts SyncOptions) (SyncResult, error) {
|
||||
return SyncResult{MessagesStored: messagesStored.Load()}, nil
|
||||
case <-disconnected:
|
||||
fmt.Fprintln(os.Stderr, "Reconnecting...")
|
||||
if err := a.wa.ReconnectWithBackoff(ctx, 2*time.Second, 30*time.Second); err != nil {
|
||||
if err := a.reconnect(ctx, opts.MaxReconnect); err != nil {
|
||||
return SyncResult{MessagesStored: messagesStored.Load()}, err
|
||||
}
|
||||
}
|
||||
@ -204,7 +205,7 @@ func (a *App) Sync(ctx context.Context, opts SyncOptions) (SyncResult, error) {
|
||||
return SyncResult{MessagesStored: messagesStored.Load()}, nil
|
||||
case <-disconnected:
|
||||
fmt.Fprintln(os.Stderr, "Reconnecting...")
|
||||
if err := a.wa.ReconnectWithBackoff(ctx, 2*time.Second, 30*time.Second); err != nil {
|
||||
if err := a.reconnect(ctx, opts.MaxReconnect); err != nil {
|
||||
return SyncResult{MessagesStored: messagesStored.Load()}, err
|
||||
}
|
||||
case <-ticker.C:
|
||||
@ -217,6 +218,24 @@ func (a *App) Sync(ctx context.Context, opts SyncOptions) (SyncResult, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// reconnect wraps ReconnectWithBackoff with an optional deadline.
|
||||
// If maxDuration is positive, reconnection gives up after that long.
|
||||
// A zero or negative value means retry indefinitely (until ctx is cancelled).
|
||||
func (a *App) reconnect(ctx context.Context, maxDuration time.Duration) error {
|
||||
rctx := ctx
|
||||
var cancel context.CancelFunc
|
||||
if maxDuration > 0 {
|
||||
rctx, cancel = context.WithTimeout(ctx, maxDuration)
|
||||
defer cancel()
|
||||
}
|
||||
err := a.wa.ReconnectWithBackoff(rctx, 2*time.Second, 30*time.Second)
|
||||
if err != nil && ctx.Err() == nil {
|
||||
// Deadline hit but parent context is still alive — we gave up, not the user.
|
||||
return fmt.Errorf("could not reconnect after %s: %w", maxDuration, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func chatKind(chat types.JID) string {
|
||||
if chat.Server == types.GroupServer {
|
||||
return "group"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user