From 6e8824a7ceca274317662e979a5795e127fc9ae0 Mon Sep 17 00:00:00 2001 From: Xuezhao Liu Date: Fri, 4 Sep 2026 08:50:11 +0000 Subject: [PATCH 1/2] DAOS-19591 container: two fixes for EC aggregation boundary reporting 1.cont_refresh_vos_agg_eph_one() now creates the VOS container when it does not exist locally (-DER_NONEXIST). That can happen when the container was created while the target was excluded and the following reintegration/extend migrated no record for it. Without the VOS container the target never reports its EC aggregation epoch, so the container service leader keeps the boundary of the whole container pinned. As the leader only refreshes containers still present in RDB, creating the VOS container here is safe. Since container creation can yield and needs a deep stack, switch ds_cont_tgt_refresh_agg_eph() from ds_pool_task_collective() to ds_pool_thread_collective() with DSS_ULT_DEEP_STACK. 2.Track ea_start_ts, the time this leader started tracking a container, and suppress the "Sluggish EC boundary reporting" warning when the boundary jump is larger than the tracking age. Such a jump cannot have been observed by this leader; it comes from the stale epoch loaded from RDB at leader step-up and is not a real stall. Signed-off-by: Xuezhao Liu --- src/container/srv_container.c | 37 ++++++++++++++++++++++++++++------- src/container/srv_internal.h | 5 +++++ 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/container/srv_container.c b/src/container/srv_container.c index bf9ebdeddaa..aca2121457f 100644 --- a/src/container/srv_container.c +++ b/src/container/srv_container.c @@ -1712,10 +1712,12 @@ cont_ec_agg_alloc(struct cont_svc *cont_svc, uuid_t cont_uuid, ec_agg->ea_servers_num = rank_nr; ec_agg->ea_current_eph = 0; ec_agg->ea_rdb_eph = 0; + ec_agg->ea_start_ts = daos_gettime_coarse(); + ec_agg->ea_warn_slug_ts = ec_agg->ea_start_ts; for (i = 0; i < rank_nr; i++) { ec_agg->ea_server_ephs[i].rank = doms[i].do_comp.co_rank; ec_agg->ea_server_ephs[i].eph = 0; - ec_agg->ea_server_ephs[i].ee_update_ts = daos_gettime_coarse(); + ec_agg->ea_server_ephs[i].ee_update_ts = ec_agg->ea_start_ts; } d_list_add(&ec_agg->ea_list, &cont_svc->cs_ec_agg_list); *ec_aggp = ec_agg; @@ -1818,9 +1820,25 @@ cont_refresh_vos_agg_eph_one(void *data) int rc; rc = ds_cont_child_lookup(arg->pool_uuid, arg->cont_uuid, &cont_child); - if (rc) { - DL_CDEBUG(rc != 0 && rc != -DER_SHUTDOWN, DLOG_ERR, DB_MD, rc, - DF_CONT " lookup cont failed", DP_CONT(arg->pool_uuid, arg->cont_uuid)); + if (rc == -DER_NONEXIST) { + /* + * The VOS container can be absent on this target when the container was + * created while the target was excluded, and the following reintegration + * (or extend) did not migrate any record for it. Create it here, otherwise + * this target never reports EC aggregation epoch to the container service + * leader, and the leader keeps the EC aggregation boundary of the whole + * container pinned. The container service leader only refreshes containers + * that still exist in RDB, so it is safe to create the VOS container here. + */ + rc = ds_cont_child_open_create(arg->pool_uuid, arg->cont_uuid, &cont_child); + DL_CDEBUG(rc != 0 && rc != -DER_SHUTDOWN, DLOG_ERR, DLOG_INFO, rc, + DF_CONT " create missing vos container", + DP_CONT(arg->pool_uuid, arg->cont_uuid)); + if (rc != 0) + return rc; + } else if (rc != 0) { + DL_CDEBUG(rc != -DER_SHUTDOWN, DLOG_ERR, DB_MD, rc, DF_CONT " lookup cont failed", + DP_CONT(arg->pool_uuid, arg->cont_uuid)); return rc; } @@ -1850,9 +1868,13 @@ ds_cont_tgt_refresh_agg_eph(uuid_t pool_uuid, uuid_t cont_uuid, uuid_copy(arg.cont_uuid, cont_uuid); arg.min_eph = eph; - rc = ds_pool_task_collective(pool_uuid, PO_COMP_ST_NEW | PO_COMP_ST_DOWN | - PO_COMP_ST_DOWNOUT, cont_refresh_vos_agg_eph_one, - &arg, DSS_ULT_FL_PERIODIC); + /* + * NB: cont_refresh_vos_agg_eph_one() may create the VOS container, which can yield + * and needs a deep stack, so a thread (ULT) collective must be used here. + */ + rc = ds_pool_thread_collective( + pool_uuid, PO_COMP_ST_NEW | PO_COMP_ST_DOWN | PO_COMP_ST_DOWNOUT, + cont_refresh_vos_agg_eph_one, &arg, DSS_ULT_DEEP_STACK | DSS_ULT_FL_PERIODIC); if (rc) { DL_ERROR(rc, DF_CONT ": refresh ec_agg_eph " DF_X64 " failed.", DP_CONT(pool_uuid, cont_uuid), eph); @@ -2094,6 +2116,7 @@ cont_agg_eph_sync(struct ds_pool *pool, struct cont_svc *svc) ec_agg->ea_warn_slug_ts = cur_ts; } else if (cur_eph && new_eph > cur_eph && (new_eph - cur_eph) >= 600 && + (cur_ts - ec_agg->ea_start_ts) >= (new_eph - cur_eph) && (cur_ts - ec_agg->ea_warn_slug_ts) >= 600) { ec_agg->ea_warn_slug_ts = cur_ts; D_WARN(DF_CONT ": Sluggish EC boundary reporting. " diff --git a/src/container/srv_internal.h b/src/container/srv_internal.h index d53205a4be1..593fbc7d3fd 100644 --- a/src/container/srv_internal.h +++ b/src/container/srv_internal.h @@ -69,6 +69,11 @@ struct cont_ec_agg { uuid_t ea_cont_uuid; daos_epoch_t ea_current_eph; daos_epoch_t ea_rdb_eph; + /* time when this leader started tracking the container. A boundary jump larger + * than the tracking age cannot have been observed by this leader, it comes from + * the stale epoch loaded from rdb at leader step-up, so it is not a real stall. + */ + uint64_t ea_start_ts; uint64_t ea_warn_slug_ts; struct ec_eph *ea_server_ephs; d_list_t ea_list; From 93ed6e0efcefe6874cf43b8be33866099d004cff Mon Sep 17 00:00:00 2001 From: Mohamad Chaarawi Date: Fri, 16 Oct 2026 15:58:22 -0500 Subject: [PATCH 2/2] DAOS-19591 container: re-send EC agg boundary to ranks waiting for it (#19304) A reintegrated rank that gets the VOS container after the leader's last boundary refresh never starts EC aggregation: it waits for a refresh to set sc_ec_agg_eph_valid, while its eph 0 report pins the leader's min_eph to ea_rdb_eph == ea_current_eph, so cont_agg_eph_sync() skips the refresh as unchanged. The boundary is stuck for the whole pool and space is not reclaimed until all engines restart. Treat an eph 0 report as a request for the boundary and re-send it in the next cont_agg_eph_sync() round even if min_eph did not change. Also suppress the false "Sluggish EC boundary reporting" warning for boundary jumps that predate this leader (backport of #19015). Features: container rebuild Signed-off-by: Mohamad Chaarawi --- src/container/srv_container.c | 61 +++++++++++++++++++---------------- src/container/srv_internal.h | 6 +++- 2 files changed, 39 insertions(+), 28 deletions(-) diff --git a/src/container/srv_container.c b/src/container/srv_container.c index aca2121457f..9f194969fa8 100644 --- a/src/container/srv_container.c +++ b/src/container/srv_container.c @@ -1778,6 +1778,22 @@ ds_cont_leader_update_agg_eph(uuid_t pool_uuid, uuid_t cont_uuid, if (ec_agg->ea_server_ephs[i].eph < eph) { ec_agg->ea_server_ephs[i].eph = eph; ec_agg->ea_server_ephs[i].ee_update_ts = daos_gettime_coarse(); + } else if (eph == 0 && ec_agg->ea_current_eph != 0) { + /* + * A rank reports zero only while its EC aggregation is paused + * waiting for the boundary (sc_ec_agg_eph_valid == 0), e.g. a + * reintegrated/extended rank that got the VOS container after the + * last refresh. Ask cont_agg_eph_sync() to re-send the current + * boundary even if the minimum epoch did not change, otherwise + * the rank never starts and pins the boundary forever. + * (ea_current_eph == 0 refreshes every round anyway.) + */ + if (!ec_agg->ea_need_refresh) + D_INFO(DF_CONT ": rank %u waits for EC agg boundary, " + "current " DF_X64 "\n", + DP_CONT(pool_uuid, cont_uuid), rank, + ec_agg->ea_current_eph); + ec_agg->ea_need_refresh = 1; } break; } @@ -1820,25 +1836,9 @@ cont_refresh_vos_agg_eph_one(void *data) int rc; rc = ds_cont_child_lookup(arg->pool_uuid, arg->cont_uuid, &cont_child); - if (rc == -DER_NONEXIST) { - /* - * The VOS container can be absent on this target when the container was - * created while the target was excluded, and the following reintegration - * (or extend) did not migrate any record for it. Create it here, otherwise - * this target never reports EC aggregation epoch to the container service - * leader, and the leader keeps the EC aggregation boundary of the whole - * container pinned. The container service leader only refreshes containers - * that still exist in RDB, so it is safe to create the VOS container here. - */ - rc = ds_cont_child_open_create(arg->pool_uuid, arg->cont_uuid, &cont_child); - DL_CDEBUG(rc != 0 && rc != -DER_SHUTDOWN, DLOG_ERR, DLOG_INFO, rc, - DF_CONT " create missing vos container", - DP_CONT(arg->pool_uuid, arg->cont_uuid)); - if (rc != 0) - return rc; - } else if (rc != 0) { - DL_CDEBUG(rc != -DER_SHUTDOWN, DLOG_ERR, DB_MD, rc, DF_CONT " lookup cont failed", - DP_CONT(arg->pool_uuid, arg->cont_uuid)); + if (rc) { + DL_CDEBUG(rc != 0 && rc != -DER_SHUTDOWN, DLOG_ERR, DB_MD, rc, + DF_CONT " lookup cont failed", DP_CONT(arg->pool_uuid, arg->cont_uuid)); return rc; } @@ -1868,13 +1868,9 @@ ds_cont_tgt_refresh_agg_eph(uuid_t pool_uuid, uuid_t cont_uuid, uuid_copy(arg.cont_uuid, cont_uuid); arg.min_eph = eph; - /* - * NB: cont_refresh_vos_agg_eph_one() may create the VOS container, which can yield - * and needs a deep stack, so a thread (ULT) collective must be used here. - */ - rc = ds_pool_thread_collective( - pool_uuid, PO_COMP_ST_NEW | PO_COMP_ST_DOWN | PO_COMP_ST_DOWNOUT, - cont_refresh_vos_agg_eph_one, &arg, DSS_ULT_DEEP_STACK | DSS_ULT_FL_PERIODIC); + rc = ds_pool_task_collective(pool_uuid, + PO_COMP_ST_NEW | PO_COMP_ST_DOWN | PO_COMP_ST_DOWNOUT, + cont_refresh_vos_agg_eph_one, &arg, DSS_ULT_FL_PERIODIC); if (rc) { DL_ERROR(rc, DF_CONT ": refresh ec_agg_eph " DF_X64 " failed.", DP_CONT(pool_uuid, cont_uuid), eph); @@ -2096,7 +2092,14 @@ cont_agg_eph_sync(struct ds_pool *pool, struct cont_svc *svc) if (min_eph < ec_agg->ea_rdb_eph) min_eph = ec_agg->ea_rdb_eph; - if (min_eph == ec_agg->ea_current_eph && ec_agg->ea_current_eph != 0) + /* + * Skip the refresh when nothing changed, unless some rank asked for the + * boundary (see ds_cont_leader_update_agg_eph()). Such a rank has eph 0, + * so min_eph is pinned to ea_rdb_eph == ea_current_eph and it would never + * get the boundary it needs to start reporting. + */ + if (min_eph == ec_agg->ea_current_eph && ec_agg->ea_current_eph != 0 && + !ec_agg->ea_need_refresh) continue; /** @@ -2143,6 +2146,10 @@ cont_agg_eph_sync(struct ds_pool *pool, struct cont_svc *svc) } } + /* clear before the refresh (which yields), so a request arriving meanwhile + * triggers another refresh in the next round. + */ + ec_agg->ea_need_refresh = 0; rc = cont_iv_ec_agg_eph_refresh(pool->sp_iv_ns, ec_agg->ea_cont_uuid, min_eph, svc->cs_ec_leader_ephs_req); if (rc) { diff --git a/src/container/srv_internal.h b/src/container/srv_internal.h index 593fbc7d3fd..bd27d232c0a 100644 --- a/src/container/srv_internal.h +++ b/src/container/srv_internal.h @@ -78,7 +78,11 @@ struct cont_ec_agg { struct ec_eph *ea_server_ephs; d_list_t ea_list; int ea_servers_num; - uint32_t ea_deleted : 1; + uint32_t ea_deleted : 1, + /* some rank reported eph 0 (its EC aggregation waits for the boundary), re-send the + * boundary in the next cont_agg_eph_sync() even if the minimum epoch did not change. + */ + ea_need_refresh : 1; }; /*