Skip to content

[dhcpmon] Write COUNTERS_DB without blocking packet handling - #98

Closed
Xichen96 wants to merge 16 commits into
sonic-net:masterfrom
Xichen96:dev/xichenlin/stable-db-counter-snapshot
Closed

[dhcpmon] Write COUNTERS_DB without blocking packet handling#98
Xichen96 wants to merge 16 commits into
sonic-net:masterfrom
Xichen96:dev/xichenlin/stable-db-counter-snapshot

Conversation

@Xichen96

@Xichen96 Xichen96 commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Description of PR

Write COUNTERS_DB from an immutable in-memory snapshot so packet callbacks are
not blocked during Redis I/O.

Dependency:

Work item tracking
  • Microsoft ADO (number only): 38615655

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Documentation update
  • Test improvement

Approach

What is the motivation for this PR?

Holding the exclusive counter lock through every Redis hset stalls packet
monitoring and can overflow raw-socket buffers under load. Releasing the lock
before taking a coherent snapshot would instead let counter clear or topology
changes race the DB writeback.

How did you do it?

  • Hold counter-state and DB synchronization while validating clear state and
    copying all per-socket counter maps.
  • Release only the counter-state lock before Redis I/O.
  • Retain DB synchronization until snapshot writeback and cleanup finish.
  • Validate snapshot socket keys before reading live socket metadata.

How did you verify/test it?

Azure PR CI passed on amd64, arm64, and armhf. No local compilation was used.
The combined hardware stack passed F2 traffic, counter-clear churn, and
regular master regression.

Any platform specific information?

No.

Back port request

None. This targets master only.

Tested branch

master

Test result

All architecture builds passed. COUNTERS_DB writeback remained stable while
traffic and counter clear overlapped on F2.

Documentation

Not applicable.

Xichen96 added 4 commits July 26, 2026 16:29
Limit each raw-socket callback to 64 packets and reset the recvfrom address length before every receive so the event loop remains responsive.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 39f979be-d826-4d5c-949a-f20abb58bb83
Signed-off-by: Xichen96 <lukelin0907@gmail.com>
Use writer-priority shared/exclusive guards so packet callbacks cannot race health sampling, snapshot, counter-clear, or COUNTERS_DB synchronization.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 39f979be-d826-4d5c-949a-f20abb58bb83
Signed-off-by: Xichen96 <lukelin0907@gmail.com>
Copy counter maps while holding counter-state and DB synchronization locks, then release packet callbacks before Redis I/O while retaining DB serialization through writeback.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 39f979be-d826-4d5c-949a-f20abb58bb83
Signed-off-by: Xichen96 <lukelin0907@gmail.com>
Validate snapshot socket keys before looking up metadata so malformed or stale caller data cannot throw from sock_map.at().

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 39f979be-d826-4d5c-949a-f20abb58bb83
Signed-off-by: Xichen96 <lukelin0907@gmail.com>
Copilot AI review requested due to automatic review settings July 26, 2026 06:36
@mssonicbld

Copy link
Copy Markdown
Collaborator

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@Xichen96 Xichen96 mentioned this pull request Jul 26, 2026
5 tasks
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

Copilot AI 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.

Pull request overview

This PR improves dhcpmon’s runtime behavior under load by writing COUNTERS_DB from an immutable in-memory snapshot, reducing contention between packet-processing threads and Redis I/O.

Changes:

  • Introduces counter-state reader/writer locking primitives (counter_state_read_lock / counter_state_write_lock) to serialize counter access across packet callbacks, health sampling, and DB sync.
  • Adds snapshot-based DB updates via sock_mgr_copy_cache_counters() and sock_mgr_update_db_counters(const socket_counters_t&) to decouple Redis writes from live counter mutation.
  • Limits per-callback packet draining (MAX_PACKETS_PER_CALLBACK) to bound time spent in the packet callback.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
src/sock_mgr.h Adds counter-state lock APIs and introduces a per-socket counters snapshot type.
src/sock_mgr.cpp Implements counter-state lock behavior and snapshot-based COUNTERS_DB writeback.
src/packet_handler.cpp Acquires counter-state read lock during packet callback and bounds packets processed per callback.
src/dhcp_mon.cpp Uses counter-state write locking around status/health/DB-sync paths and writes DB counters from a stable snapshot.
Comments suppressed due to low confidence (2)

src/dhcp_mon.cpp:277

  • Same lock-order issue as SIGUSR1: update_cache_counter_callback acquires counter_state_write_lock and then takes db_sync_mutex. If db_update_callback is holding db_sync_mutex during Redis I/O, this callback can block on db_sync_mutex while still holding the counter-state lock, which stalls packet callbacks. Take db_sync_mutex first, then take counter_state_write_lock before touching cache counters / pause flags.
    counter_state_write_lock counter_lock;
    if (!counter_lock.owns_lock()) {
        return;
    }
    std::lock_guard<std::mutex> lock(db_sync_mutex);

src/dhcp_mon.cpp:462

  • db_update_callback currently acquires counter_state_write_lock and then db_sync_mutex. To avoid deadlock after changing other call sites to db_sync_mutex -> counter_state_write_lock, and to ensure writers never hold counter-state while waiting on a long-held db_sync_mutex, this callback should also lock db_sync_mutex first and only then take counter_state_write_lock briefly to validate/clone the snapshot. This keeps packet callbacks running while db_sync_mutex is contended and maintains a consistent global lock order.
    socket_counters_t counters_by_socket;
    std::unique_lock<std::mutex> lock;
    {
        counter_state_write_lock counter_lock;
        if (!counter_lock.owns_lock()) {
            return;
        }
        lock = std::unique_lock<std::mutex>(db_sync_mutex);
        // If there is clear counter going on and its been longer than expected
        // consider the clear counter operation failed so we don't block db update forever
        if (!sock_mgr_pause_write_cache_to_db_all_cleared() && last_update_time != default_time_point) {
            auto now = std::chrono::steady_clock::now();
            auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - last_update_time);
            if (elapsed.count() >= clear_counter_timeout) {
                syslog(LOG_WARNING, "Clear counter going on for too long, abort clear counter");
                sock_mgr_clear_pause_write_cache_to_db();
            } else {
                syslog(LOG_INFO, "Clear counter is ongoing, skip syncing write cache counter to DB counter");
                return;
            }
        }
        counters_by_socket = sock_mgr_copy_cache_counters();
    }
    last_update_time = std::chrono::steady_clock::now();
    sock_mgr_update_db_counters(counters_by_socket);

Comment thread src/dhcp_mon.cpp Outdated
Acquire DB serialization before the counter writer guard and document snapshot preconditions, preserving one lock order while packet callbacks continue during Redis waits and writes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 39f979be-d826-4d5c-949a-f20abb58bb83
Signed-off-by: Xichen96 <lukelin0907@gmail.com>
Copilot AI review requested due to automatic review settings July 26, 2026 07:37
@mssonicbld

Copy link
Copy Markdown
Collaborator

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

Copilot AI 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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/dhcp_mon.cpp:291

  • update_cache_counter_callback() now holds counter_state_write_lock across Redis I/O (e.g., mCountersDbPtr->keys(...)), which can still stall packet callbacks during Redis access. To better match the goal of avoiding packet stalls on Redis operations, consider acquiring counter_state_write_lock only after the Redis reads, and keeping it only for applying updates to the in-memory counters.
    std::lock_guard<std::mutex> lock(db_sync_mutex);
    counter_state_write_lock counter_lock;
    if (!counter_lock.owns_lock()) {
        return;
    }

    // can only sync db to cache counter and db updater is paused, otherwise its unexpected
    if (!sock_info.pause_write_cache_to_db) {
        syslog(LOG_WARNING, "Failed to update cache counter from DB counter on %s because pause_write_cache_to_db is not set", sock_info.name);
        return;
    }

    std::string match_pattern = (sock_info.is_v6 ? COUNTERS_DB_COUNTER_TABLE_V6_PREFIX : COUNTERS_DB_COUNTER_TABLE_PREFIX)
                                + downstream_ifname + "*";
    auto keys = mCountersDbPtr->keys(match_pattern);

    // Store interfaces in DB counter table
    std::unordered_set<std::string> updated_intfs;
    all_counters_t &all_counters = sock_info.all_counters;

Comment thread src/sock_mgr.cpp Outdated
Expose only the immutable-snapshot writeback API so callers cannot copy live counter maps without the required counter-state guard.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 39f979be-d826-4d5c-949a-f20abb58bb83
Signed-off-by: Xichen96 <lukelin0907@gmail.com>
Copilot AI review requested due to automatic review settings July 26, 2026 07:51
@mssonicbld

Copy link
Copy Markdown
Collaborator

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

Copilot AI 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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Document that the 64-packet batch keeps periodic health and DB events responsive during bursts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 39f979be-d826-4d5c-949a-f20abb58bb83
Signed-off-by: Xichen96 <lukelin0907@gmail.com>
Copilot AI review requested due to automatic review settings July 26, 2026 08:13
@mssonicbld

Copy link
Copy Markdown
Collaborator

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

Keep DB serialization while limiting the counter writer guard to the in-memory pause-flag update in SIGUSR1 handling.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 39f979be-d826-4d5c-949a-f20abb58bb83
Signed-off-by: Xichen96 <lukelin0907@gmail.com>
@mssonicbld

Copy link
Copy Markdown
Collaborator

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

Copilot AI 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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment thread src/sock_mgr.cpp Outdated
Comment thread src/sock_mgr.cpp
Copilot AI review requested due to automatic review settings July 26, 2026 09:23

Copilot AI 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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

src/sock_mgr.cpp:21

  • The new language-version guard claims C++14 is sufficient, but this translation unit already uses C++17-only features (e.g., structured bindings like for (auto &[sock, info] : sock_map)), so compiling with C++14 would still fail later with a less clear error. Make the guard match the actual minimum standard to fail fast with an accurate message.
#if __cplusplus < 201402L
#error "dhcpmon counter synchronization requires C++14 or newer"
#endif

src/sock_mgr.cpp:154

  • counter_state_read_lock::counter_state_read_lock() calls shared_lock::unlock() in the retry loop without catching std::system_error. If an unlock failure occurs, an exception would escape into the packet callback path.
        lock.unlock();

Comment thread src/sock_mgr.cpp Outdated
Let unique/shared lock destructors release mutex ownership and use local candidate read locks, avoiding manual unlock exception paths while preserving writer priority.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 39f979be-d826-4d5c-949a-f20abb58bb83
Signed-off-by: Xichen96 <lukelin0907@gmail.com>
Copilot AI review requested due to automatic review settings July 26, 2026 09:50
@mssonicbld

Copy link
Copy Markdown
Collaborator

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

Copilot AI 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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment thread src/sock_mgr.cpp
Reserve the known socket count before copying counter maps to avoid rehashing while the counter writer guard is held.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 39f979be-d826-4d5c-949a-f20abb58bb83
Signed-off-by: Xichen96 <lukelin0907@gmail.com>
Copilot AI review requested due to automatic review settings July 26, 2026 10:02
@mssonicbld

Copy link
Copy Markdown
Collaborator

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

Copilot AI 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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Compile C++ sources explicitly as gnu++17 and include utility directly for std::move, making the counter guard build requirements deterministic.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 39f979be-d826-4d5c-949a-f20abb58bb83
Signed-off-by: Xichen96 <lukelin0907@gmail.com>
Copilot AI review requested due to automatic review settings July 26, 2026 10:13
@mssonicbld

Copy link
Copy Markdown
Collaborator

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

Copilot AI 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.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/sock_mgr.cpp:749

  • This per-socket log message says "from cache counter", but the writeback source is now the passed-in snapshot map (all_counters from counters_by_socket), not the live cache map. Tweaking the wording avoids confusion when correlating logs with lock scopes.
        syslog_debug(LOG_INFO, "Start updating socket %d %s DB counter from cache counter", sock, info.name);

Comment thread src/sock_mgr.cpp
@Xichen96 Xichen96 changed the title [dhcpmon] Write DB counters from a stable snapshot [dhcpmon] Write COUNTERS_DB without blocking packet handling Jul 27, 2026
@Xichen96

Copy link
Copy Markdown
Contributor Author

Removed with #97. This snapshot-writeback change exists to reduce packet blocking introduced by that synchronization layer; without #97 it is not an independent F2 repair.

@Xichen96 Xichen96 closed this Jul 27, 2026
@Xichen96
Xichen96 deleted the dev/xichenlin/stable-db-counter-snapshot branch July 27, 2026 02:25
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.

3 participants