Skip to content

fix(instance): prevent duplicate runtimes and harden QR lifecycle - #145

Open
joldmarfilho wants to merge 2 commits into
evolution-foundation:mainfrom
joldmarfilho:fix-instance-lifecycle-qr
Open

fix(instance): prevent duplicate runtimes and harden QR lifecycle #145
joldmarfilho wants to merge 2 commits into
evolution-foundation:mainfrom
joldmarfilho:fix-instance-lifecycle-qr

Conversation

@joldmarfilho

@joldmarfilho joldmarfilho commented Jul 29, 2026

Copy link
Copy Markdown

Description

Related Issue

Closes #(issue_number)

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Performance improvement

Testing

  • Manual testing completed
  • Functionality verified in development environment
  • No breaking changes introduced

Screenshots (if applicable)

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have tested my changes thoroughly
  • Any dependent changes have been merged and published

Additional Notes

Summary by Sourcery

Improve WhatsApp instance runtime lifecycle to prevent concurrent clients and uncontrolled reconnect loops while making QR-based sessions safer.

Bug Fixes:

  • Ensure only one active runtime client exists per instance to avoid shared killChannel conflicts and unintended teardown of live sessions.
  • Prevent automatic reconnection and QR loops for unpaired or QR-exhausted instances, requiring explicit connect requests instead.
  • Fix reconnection behavior by introducing a serialized, backoff-based scheduler instead of recursive or concurrent reconnect attempts.
  • Avoid starting QR runtimes for already logged-in sessions and return a consistent session-already-logged-in error.

Enhancements:

  • Add controlled auto-reconnect with exponential backoff and jitter for paired instances, with state tracking of failures and successes.
  • Refine runtime cleanup and cache clearing using synchronization and completion signaling to make teardown deterministic and race-free.
  • Centralize device store container creation with optional shared Postgres-backed auth container and proper lifecycle management.
  • Adjust kill channel buffering and done signaling across services to make runtime stop requests non-blocking and idempotent.

Tests:

  • Add unit tests for runtime reservation and release semantics to guarantee at most one runtime per instance and safe stale-token handling.
  • Add lifecycle tests covering auto-reconnect eligibility, reconnect deduplication, and retry jitter bounds.
  • Add tests for QR runtime decision logic to verify correct behavior for missing, waiting, and logged-in clients.

root and others added 2 commits July 29, 2026 16:32
A QR request could spawn a runtime for an instance that already had one:
GetQr started a client whenever clientPointer was nil or the client was
logged in. Two clients on the same instance share one killChannel and one
database row, so when the spare one exhausted its QR cycle it tore the
runtime down and flagged the instance as disconnected while the paired
session stayed online. Callers polling for a QR then kept feeding the loop.

StartClient now admits a single runtime per instance, guarded by a token so
a late cleanup cannot free the reservation of its replacement, and GetQr
refuses to restart a logged-in session instead of racing it.
@sourcery-ai

sourcery-ai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Reviewer's Guide

Introduces a more robust instance runtime and reconnect lifecycle for WhatsApp clients: enforces a single runtime per instance with token-based reservations, adds a deduplicated auto-reconnect scheduler with backoff and jitter, separates QR teardown from automatic reconnection for unpaired sessions, and adds tests around runtime/reconnect coordination and QR runtime decisions.

Sequence diagram for controlled reconnect lifecycle

sequenceDiagram
    participant MyClient
    participant WhatsmeowService as whatsmeowService
    participant WAClient

    MyClient->>WAClient: myEventHandler(events.Disconnected)
    MyClient->>WhatsmeowService: ScheduleReconnect(userID)

    activate WhatsmeowService
    WhatsmeowService->>WhatsmeowService: GetInstanceByID(instanceId)
    WhatsmeowService->>WhatsmeowService: canAutoReconnect(instance.Jid)
    WhatsmeowService->>WhatsmeowService: reserveReconnect(instanceId)
    deactivate WhatsmeowService

    Note over WhatsmeowService: goroutine with delay from reconnectDelay

    WhatsmeowService->>WhatsmeowService: ReconnectClient(instanceId)
    activate WhatsmeowService
    WhatsmeowService->>WhatsmeowService: reserveRuntime(instanceId)
    WhatsmeowService->>WhatsmeowService: deviceContainer(dbLog)
    WhatsmeowService->>WhatsmeowService: StartClient(ClientData)
    deactivate WhatsmeowService

    WAClient-->>MyClient: events.Connected
    MyClient->>WhatsmeowService: markReconnectSuccess(userID)
    WhatsmeowService->>WhatsmeowService: finishReconnect(instanceId, connected=true)
Loading

File-Level Changes

Change Details Files
Enforce a single WhatsApp runtime per instance with safe teardown and shared kill/done channels.
  • Add runtime reservation bookkeeping via runtimeToken and runtimeCounter with mutex protection and token-based release semantics to avoid late-cleanup races.
  • Introduce doneChannel per instance and adjust StartClient and ReconnectClient to coordinate shutdown and startup, waiting for cleanup where required.
  • Refactor StartClient cleanup: centralize map deletions, killChannel creation, and runtime release in a deferred section and simplify kill signal handling to a single blocking receive followed by orderly disconnect and state update.
  • Change killChannel creation across services to buffered channels to avoid blocking senders and make kill operations idempotent.
pkg/whatsmeow/service/whatsmeow.go
pkg/instance/service/instance_service.go
pkg/whatsmeow/service/runtime_test.go
Implement controlled, deduplicated auto-reconnect with exponential backoff and jitter for paired instances.
  • Introduce instanceReconnectState, reconnectDelay with FNV-based jitter, and canAutoReconnect helper that only allows auto-reconnect for paired JIDs.
  • Add reserveReconnect and finishReconnect to serialize reconnect attempts per instance and track failure counts, with a cap on backoff growth.
  • Implement ScheduleReconnect to orchestrate timed reconnect attempts, probe for successful connection within a short window, and recursively reschedule on failure while respecting the per-instance reservation.
  • Expose markReconnectSuccess and wire it into event handling so successful connections reset failure counters.
pkg/whatsmeow/service/whatsmeow.go
pkg/whatsmeow/service/lifecycle_test.go
Refine QR lifecycle so logged-in sessions do not start extra runtimes and QR exhaustion stops unpaired runtimes without forced logout.
  • Add ErrSessionAlreadyLoggedIn error and qrNeedsNewRuntime helper to encapsulate QR startup decisions based on client existence and login state.
  • Update GetQr to use qrNeedsNewRuntime, returning ErrSessionAlreadyLoggedIn when QR is requested for a logged-in session and only starting a new instance when needed.
  • Adjust handleQRCodes to stop unpaired runtimes on QR max-count without forcing logout and to avoid automatic reconnection, requiring explicit connect.
  • Modify teardownQR to send non-blocking kill signals on buffered killChannel and rely on the centralized StartClient cleanup path instead of an internal restart loop.
pkg/instance/service/instance_service.go
pkg/whatsmeow/service/whatsmeow.go
pkg/instance/service/qr_runtime_test.go
Refactor device auth container creation and lifecycle and standardize method receivers.
  • Extract deviceContainer helper to initialize sqlstore.Container for either Postgres or SQLite, upgrade the schema once with sync.Once, and reuse the container across clients when using PostgresAuthDB.
  • Ensure owned containers (SQLite) are closed via deferred cleanup in StartClient with logging on failure.
  • Convert several whatsmeowService methods (e.g., StartClient, StartInstance, ReconnectClient, ClearInstanceCache and settings updates) to pointer receivers and adjust MyClient.service assignment accordingly.
pkg/whatsmeow/service/whatsmeow.go
Add unit tests around runtime reservation, reconnect scheduling, and QR runtime decisions.
  • Create runtime_test.go to validate that reserveRuntime allows only one concurrent runtime per instance, is per-instance, and that releaseRuntime ignores stale tokens while allowing current tokens to free reservations.
  • Create lifecycle_test.go to cover canAutoReconnect behavior, reconnect deduplication and retry, and bounds/jitter characteristics of reconnectDelay across multiple tenants.
  • Create qr_runtime_test.go to verify qrNeedsNewRuntime behavior for missing, waiting-for-QR, and logged-in clients, asserting that logged-in cases return ErrSessionAlreadyLoggedIn.
pkg/whatsmeow/service/runtime_test.go
pkg/whatsmeow/service/lifecycle_test.go
pkg/instance/service/qr_runtime_test.go

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@joldmarfilho joldmarfilho changed the title Fix instance lifecycle qr fix(instance): prevent duplicate runtimes and harden QR lifecycle Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant