Skip to content

Add worker pool event handle - #58

Merged
owent merged 3 commits into
mainfrom
dev
Aug 6, 2026
Merged

Add worker pool event handle#58
owent merged 3 commits into
mainfrom
dev

Conversation

@owent

@owent owent commented Aug 5, 2026

Copy link
Copy Markdown
Owner

No description provided.

Copilot AI lite review requested due to automatic review settings August 5, 2026 15:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 adds a worker-pool event callback mechanism to libatapp so callers can observe worker lifecycle transitions (created/started/exiting/removed), and introduces a per-worker worker_unique_id to correlate events across worker ID reuse/moves.

Changes:

  • Add worker_unique_id to worker_context and propagate it through worker creation/selection paths.
  • Implement worker lifecycle event callback registration/removal APIs in worker_pool_module, plus callback dispatch on lifecycle transitions.
  • Add a new unit test exercising event registration/removal behavior and lifecycle event delivery.

Reviewed changes

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

File Description
test/case/atapp_worker_pool_test.cpp Adds a test validating worker lifecycle event callbacks and handle-removal behaviors.
src/atframe/modules/worker_pool_module.cpp Implements callback storage/dispatch, worker unique ID assignment, and lifecycle hooks firing callbacks.
include/atframe/modules/worker_pool_module.h Exposes the new event callback registration/removal APIs.
include/atframe/modules/worker_context.h Extends worker_context with worker_unique_id and introduces callback/handle types.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 1471 to 1475
std::list<worker_event_callback_internal_data_pointer>* event_on_worker_created_ptr = nullptr;
std::list<worker_event_callback_internal_data_pointer> event_on_worker_created_data;

uint32_t expect_workers = worker_set_->current_expect_workers.load(std::memory_order_acquire);
std::lock_guard<std::recursive_mutex> lg{worker_set_->worker_lock};
Comment on lines +143 to +149
static void internal_remove_event_callback(std::recursive_mutex& lock,
std::list<worker_event_callback_internal_data_pointer>& callback_list,
const worker_event_callback_handle_type& handle) {
// 加锁后再检查状态,handle->owner 可能被其他线程的 remove/cleanup 并发修改
std::lock_guard<std::recursive_mutex> lg{lock};
if (handle->owner != &callback_list) {
return;
Comment on lines 19 to +29
struct UTIL_SYMBOL_VISIBLE worker_context {
// worker id 指示当前是第几个worker,0表示主线程,1表示第一个工作线程,依次类推。
// worker id 可能被复用或转移工作线程,但同时每个 worker id 指向唯一一个线程
uint32_t worker_id = 0;

inline worker_context() noexcept : worker_id(0) {}
explicit inline worker_context(uint32_t id) noexcept : worker_id(id) {}
// worker_unique_id 指示当前worker的唯一标识,不会随着线程转移而变化
uint64_t worker_unique_id = 0;

inline worker_context() noexcept : worker_id(0), worker_unique_id(0) {}
explicit inline worker_context(uint32_t id, uint64_t unique_id = 0) noexcept
: worker_id(id), worker_unique_id(unique_id) {}
@atframework-aicr

atframework-aicr Bot commented Aug 5, 2026

Copy link
Copy Markdown

AI Code Review

Updated: 2026-08-06 10:01:44 UTC | Commit: 9581e6a

AI Code Review Summary

worker_pool: 新增事件回调与 worker_unique_id,foreach_worker_quickly 存在 stable 槽位丢失回归

Target: Add worker pool event handle
Author: @owent
Branch: dev
Reviewers: @owent


审查范围

PR #58 (owent/libatapp) 为 worker pool 新增四类事件回调(on_worker_created / on_worker_started / on_worker_exiting / on_worker_removed)及稳定的 worker_unique_id,并重构了 foreach_worker_quicklydo_scaling_upinternal_reduce_workersinternal_autofix_workersinternal_cleanup 等生命周期路径。

审查基于完整源码(已读取 worker_pool_module.cpp 全文、worker_context.hworker_pool_module.h、测试文件,并对照 base SHA 2f6e91b 验证行为差异)。

问题汇总(1 项,medium)

  • correctness — foreach_worker_quickly stable 槽位丢失回归src/atframe/modules/worker_pool_module.cpp:1280):重构后先输出 real worker(跳过 is_exiting()),再用 last_worker_id+1..min_count 回填 stable 槽位。当某个 stable worker 处于 exiting/exited 被跳过、且其后存在更晚的 worker 时,回填范围无法覆盖该被跳过的早期 stable 槽位,导致该 worker_id 完全不被输出,违反旧版本"stable workers always available"的契约(关停期或崩溃到 autofix 重编号前的窗口可触发)。现有 foreach_stable_workers 测试因在 0 worker 下运行,无法覆盖此回归。

已确认无问题的点

  • 事件回调加锁模型正确:回调在快照上执行、不持有任何锁,reentrant 的 add/remove/spawn/foreach 不会死锁;事件顺序 created→started→exiting→removed 正确。
  • internal_remove_event_callback 对 owner/iter 的双重守卫使重复删除、跨事件类型删除、cleanup 后删除均安全;internal_cleanup 保留 worker_set_ 生命周期,故 cleanup 后调用 remove 是安全的(与新增测试一致)。
  • worker_unique_id 由原子生成器(从 1 起)在构造时一次性写入,autofix 重编号只改 worker_id、不改 worker_unique_id,符合"稳定唯一标识"契约。
  • do_scaling_up/internal_reduce_workers 在触发 created/removed 事件前已释放 worker_lock,不存在 worker_lock 与事件锁嵌套。
  • 新增测试覆盖了空 handle、重复删除、跨类型删除、创建前删除、唯一性、跨事件关联及 cleanup 后删除等边界。

限制

  • 仓库内 foreach_worker_quickly 仅被测试调用;其为导出 API,实际影响取决于下游 atapp 应用是否依赖"stable worker 总可见"的契约按 worker_id 路由。

Problems (1)

# Severity Category Location Message
0 MEDIUM correctness src/atframe/modules/worker_pool_module.cpp:1280-1287 重构 foreach_worker_quickly 后丢失了 "stable workers always available" 契约:当某个 stable worker(worker_id <= min_count)处于 exiting/exited 状态时,第一段循环会因 is_exiting()(line 1255)跳过它;随后回填循环 for (worker_id = last_worker_id + 1; worker_id <= min_count; ++worker_id) 只能补齐最后一个已输出 worker 之后的槽位,无法覆盖位于 last_worker_id 之前被跳过的 stable 槽位。结果该 worker_id 会被完全漏掉。触发场景:关停过程中、或某个 stable worker 崩溃退出后到 internal_autofix_workers 重编号前的窗口,主线程 tick 调用 foreach_worker_quickly。例如 min_count=2 且 workers=[w1(exiting), w2(running), w3(running)] 时,旧版本会输出 worker_id 1、2、3,新版本只输出 2、3,worker_id 1 被丢弃。依赖该契约按 worker_id 向 stable worker 路由任务的调用方会漏掉该槽位。注意现有 foreach_stable_workers 测试在 0 个 worker 时运行,无法覆盖此回归。

Code reference: src/atframe/modules/worker_pool_module.cpp:1280-1287

  // stable workers always available
  for (uint32_t worker_id = last_worker_id + 1; worker_id <= min_count; ++worker_id) {
    worker_meta meta = {};
    meta.scaling_mode = worker_scaling_mode::kStable;
    should_continue = fn(worker_context{worker_id, 0}, meta);
    if (!should_continue) {
      break;
    }
  }

  • Powered by AICodeReviewer*

Open Issues (1)

1. [MEDIUM] correctnesssrc/atframe/modules/worker_pool_module.cpp:1280-1287 (new in 9581e6a)

Resolved (2)

The following previously reported issues are no longer present:

- [MEDIUM] concurrency — src/atframe/modules/worker_pool_module.cpp:1501 ✅ Resolved
- [MEDIUM] concurrency — src/atframe/modules/worker_pool_module.cpp:1625 ✅ Resolved

Copilot AI review requested due to automatic review settings August 5, 2026 15:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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.

Suppressed comments (3)

src/atframe/modules/worker_pool_module.cpp:609

  • User-provided worker_exiting callbacks are invoked on the worker thread without any exception guard. If exceptions are enabled and a callback throws, the exception will escape the thread entry function and trigger std::terminate.
      for (auto& fn : event_on_worker_exiting) {
        if (fn && fn->callback) {
          fn->callback(self->get_context());
        }

src/atframe/modules/worker_pool_module.cpp:150

  • internal_remove_event_callback reads handle->owner while only holding the mutex for the target callback_list. Because each event type uses a different mutex, a mismatched remove (or cleanup) can concurrently write handle->owner under a different mutex, which is a data race even though the function returns early for mismatched lists.
  // 加锁后再检查状态,handle->owner 可能被其他线程的 remove/cleanup 并发修改
  std::lock_guard<std::recursive_mutex> lg{lock};
  if (handle->owner != &callback_list) {
    return;
  }

include/atframe/modules/worker_context.h:30

  • worker_context removed the previous single-argument constructor (worker_context(uint32_t id)), which is a source-level breaking change for existing callers that build contexts by worker_id only. Keeping a 1-arg overload that defaults worker_unique_id to 0 preserves backward compatibility while still supporting the new unique-id field.
  inline worker_context() noexcept : worker_id(0), worker_unique_id(0) {}
  explicit inline worker_context(uint32_t id, uint64_t unique_id) noexcept
      : worker_id(id), worker_unique_id(unique_id) {}

Comment on lines +482 to +486
for (auto& fn : event_on_worker_start) {
if (fn && fn->callback) {
fn->callback(self->get_context());
}
}
Copilot AI review requested due to automatic review settings August 6, 2026 04:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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.

Suppressed comments (1)

include/atframe/modules/worker_context.h:21

  • The comment about worker_id is a bit ambiguous/contradictory ("可能被复用或转移" vs "同时...指向唯一一个线程"). Clarify that uniqueness is at a given point in time to avoid confusing API consumers.
  // worker id 指示当前是第几个worker,0表示主线程,1表示第一个工作线程,依次类推。
  // worker id 可能被复用或转移工作线程,但同时每个 worker id 指向唯一一个线程

Comment on lines +28 to +30
inline worker_context() noexcept : worker_id(0), worker_unique_id(0) {}
explicit inline worker_context(uint32_t id, uint64_t unique_id) noexcept
: worker_id(id), worker_unique_id(unique_id) {}
@owent
owent merged commit 9581e6a into main Aug 6, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants