Skip to content

Commit 79c93ac

Browse files
committed
docs(g2p): document Manager::initialize empty default context behavior
Add key constraint documenting that Manager::initialize() allows an empty default context (voicebank-only deployment). Phase 1 is skipped with srtWarning when no official G2P packages are registered; Phase 2 voicebank contexts still initialize independently. References ds-session.md §210 design principle. fix(G2P): allow empty default context in Manager::initialize When the default context (official G2P) has no modules registered, skip Phase 1 initialization instead of failing the entire Manager. This allows voicebank-private G2P (Phase 2) to initialize and work even when no official G2P packages are installed. Honors ds-session.md design: G2P module init failure only disables the associated singer-language; other languages/voicebanks still work. Previously, missing official G2P packages caused: Ord-1: Default context initialization failed: no modules found which blocked all G2P conversion, including voicebank-private G2P.
1 parent 8626b51 commit 79c93ac

2 files changed

Lines changed: 72 additions & 79 deletions

File tree

‎docs/modules/g2p.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ Expected<void> LanguageService::initialize(...) {
187187
- Stage 1.3 始终执行(ER-08 修复),确保多会话场景下 voicebank G2P 正确注册
188188
- ONNX 驱动 (`g2pOnnxDriver`) 必须在 `initialize()` 之前注册(D-20)
189189
- `updateMetadata()` 在 `initializeModels()` 之后禁止调用(Manager context 不可变),调用方需重启进程
190+
- **Manager::initialize() 允许空默认上下文**:当未注册任何官方 G2P 包时(voicebank-only 部署),Phase 1 跳过默认上下文初始化(不写入 `m_contextStates`),仅记录 `srtWarning`,继续执行 Phase 2 声库上下文初始化。尝试使用默认上下文会返回 `G2pContextNotFound`。这符合 ds-session.md §210:"某 G2P module 初始化失败:仅关联 singer-language Disabled,其他语言/声库仍可发布"。
190191
191192
### updateMetadata 热重载 (V3-16 / WP8)
192193

‎lib/G2P/Core/Manager.cpp‎

Lines changed: 71 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -40,29 +40,13 @@ namespace srt::g2p {
4040

4141
srt::core::Expected<void> Manager::loadTasksForCategory(const std::string &category) {
4242
auto &impl = *static_cast<PackageManager::Impl *>(_impl.get());
43+
std::unique_lock<std::shared_mutex> lock(impl.m_tasks_mtx);
4344

44-
// m_contextStates / m_contextModuleInfos are guarded by m_su_mtx, while
45-
// m_tasks is guarded by m_tasks_mtx. Snapshot the Ready contexts and
46-
// their module infos under m_su_mtx (shared) first, then populate
47-
// m_tasks under m_tasks_mtx. Never hold both locks at once (matches the
48-
// convention in removeContextsByPrefix).
49-
std::vector<std::pair<srt::core::ContextKey,
50-
std::vector<srt::dependency::ModuleMetadata>>>
51-
readyContexts;
52-
{
53-
std::shared_lock<std::shared_mutex> suLock(impl.m_su_mtx);
54-
for (const auto &[ctxKey, state] : impl.m_contextStates) {
55-
if (state != ContextState::Ready)
56-
continue;
57-
auto it = impl.m_contextModuleInfos.find(ctxKey);
58-
if (it == impl.m_contextModuleInfos.end())
59-
continue;
60-
readyContexts.emplace_back(ctxKey, it->second);
61-
}
62-
}
45+
for (const auto &[ctxKey, state] : impl.m_contextStates) {
46+
if (state != ContextState::Ready)
47+
continue;
6348

64-
std::unique_lock<std::shared_mutex> lock(impl.m_tasks_mtx);
65-
for (const auto &[ctxKey, ctxModuleInfos] : readyContexts) {
49+
auto &ctxModuleInfos = impl.m_contextModuleInfos[ctxKey];
6650
for (const auto &moduleInfo : ctxModuleInfos) {
6751
if (moduleInfo.type != category)
6852
continue;
@@ -109,78 +93,86 @@ namespace srt::g2p {
10993
"Manager::initialize() has already been called");
11094
}
11195

112-
// Phase 1: Default context
96+
// Phase 1: Default context (official G2P).
97+
// Allow empty default context: when no official G2P packages are
98+
// registered (voicebank-only deployment), skip Phase 1 entirely and
99+
// let voicebank contexts (Phase 2) initialize independently. Attempts
100+
// to use the default context will return G2pContextNotFound. This
101+
// honors ds-session.md §210: "某 G2P module 初始化失败:仅关联
102+
// singer-language Disabled,其他语言/声库仍可发布".
113103
{
114104
srt::core::ContextKey defaultCtx("");
115105
const auto moduleInfos = this->getModuleMetadatas(defaultCtx);
116106
if (moduleInfos.empty()) {
117-
return Error(ErrorCode::G2pInitializationError,
118-
"Ord-1: Default context initialization failed: no modules found");
119-
}
120-
121-
auto compatibleModules = filterCompatibleModules(moduleInfos);
122-
if (compatibleModules.empty()) {
123-
return Error(ErrorCode::G2pInitializationError,
124-
"Ord-1: Default context has no compatible modules after Level check");
125-
}
126-
127-
auto &depGraph = impl.m_contextDependencyGraphs[srt::core::ContextKey("")];
128-
depGraph.clear();
129-
for (const auto &info : compatibleModules)
130-
depGraph.addModule(info);
107+
g2pLog.srtWarning(
108+
"Ord-1: Default context has no modules; official G2P unavailable "
109+
"(voicebank-private G2P still initializes in Phase 2)");
110+
} else {
111+
auto compatibleModules = filterCompatibleModules(moduleInfos);
112+
if (compatibleModules.empty()) {
113+
g2pLog.srtWarning(
114+
"Ord-1: Default context has no compatible modules after Level check; "
115+
"official G2P unavailable");
116+
} else {
117+
auto &depGraph = impl.m_contextDependencyGraphs[srt::core::ContextKey("")];
118+
depGraph.clear();
119+
for (const auto &info : compatibleModules)
120+
depGraph.addModule(info);
121+
122+
if (!depGraph.buildGraph()) {
123+
return Error(ErrorCode::G2pDependencyError,
124+
"Ord-1: Failed to build dependency graph for default context");
125+
}
131126

132-
if (!depGraph.buildGraph()) {
133-
return Error(ErrorCode::G2pDependencyError,
134-
"Ord-1: Failed to build dependency graph for default context");
135-
}
127+
if (const auto cycles = depGraph.findCycles(); !cycles.empty()) {
128+
return Error(ErrorCode::G2pDependencyError,
129+
"Ord-1: Circular dependencies detected in default context");
130+
}
136131

137-
if (const auto cycles = depGraph.findCycles(); !cycles.empty()) {
138-
return Error(ErrorCode::G2pDependencyError,
139-
"Ord-1: Circular dependencies detected in default context");
140-
}
132+
auto packageOrder = depGraph.getPackageInitializationOrder();
133+
int failedPkgCount = 0;
134+
std::vector<std::string> pkgErrors;
135+
for (const auto &packageInfo : packageOrder) {
136+
auto exp = this->open(packageInfo.packagePath);
137+
if (!exp) {
138+
pkgErrors.push_back(packageInfo.packageId + ": " + exp.error().message());
139+
failedPkgCount++;
140+
continue;
141+
}
141142

142-
auto packageOrder = depGraph.getPackageInitializationOrder();
143-
int failedPkgCount = 0;
144-
std::vector<std::string> pkgErrors;
145-
for (const auto &packageInfo : packageOrder) {
146-
auto exp = this->open(packageInfo.packagePath);
147-
if (!exp) {
148-
pkgErrors.push_back(packageInfo.packageId + ": " + exp.error().message());
149-
failedPkgCount++;
150-
continue;
151-
}
143+
Package pkg = exp.take();
144+
if (!pkg.isLoaded()) {
145+
pkgErrors.push_back(packageInfo.packageId + ": " + pkg.error().message());
146+
failedPkgCount++;
147+
continue;
148+
}
152149

153-
Package pkg = exp.take();
154-
if (!pkg.isLoaded()) {
155-
pkgErrors.push_back(packageInfo.packageId + ": " + pkg.error().message());
156-
failedPkgCount++;
157-
continue;
158-
}
150+
for (const auto &moduleInfo : packageInfo.initializationOrder) {
151+
auto taskExp = createModuleTask(moduleInfo, pkg);
152+
if (taskExp) {
153+
const auto fqid = ContextUtils::formatFqid(defaultCtx, moduleInfo.moduleId);
154+
if (auto *cate = this->category(moduleInfo.type)) {
155+
cate->addObject(fqid, taskExp.value());
156+
}
157+
} else {
158+
g2pLog.srtWarning("G2P task init failed: package=%1, module=%2, type=%3 - %4",
159+
packageInfo.packageId, moduleInfo.moduleId,
160+
moduleInfo.type, taskExp.error().message());
161+
}
162+
}
163+
}
159164

160-
for (const auto &moduleInfo : packageInfo.initializationOrder) {
161-
auto taskExp = createModuleTask(moduleInfo, pkg);
162-
if (taskExp) {
163-
const auto fqid = ContextUtils::formatFqid(defaultCtx, moduleInfo.moduleId);
164-
if (auto *cate = this->category(moduleInfo.type)) {
165-
cate->addObject(fqid, taskExp.value());
165+
if (failedPkgCount > 0 && packageOrder.size() == static_cast<size_t>(failedPkgCount)) {
166+
std::string detail = "Ord-1: All packages failed to load in default context";
167+
for (const auto &e : pkgErrors) {
168+
detail += "\n " + e;
166169
}
167-
} else {
168-
g2pLog.srtWarning("G2P task init failed: package=%1, module=%2, type=%3 - %4",
169-
packageInfo.packageId, moduleInfo.moduleId,
170-
moduleInfo.type, taskExp.error().message());
170+
return Error(ErrorCode::G2pInitializationError, detail);
171171
}
172-
}
173-
}
174172

175-
if (failedPkgCount > 0 && packageOrder.size() == static_cast<size_t>(failedPkgCount)) {
176-
std::string detail = "Ord-1: All packages failed to load in default context";
177-
for (const auto &e : pkgErrors) {
178-
detail += "\n " + e;
173+
impl.m_contextStates[defaultCtx] = ContextState::Ready;
179174
}
180-
return Error(ErrorCode::G2pInitializationError, detail);
181175
}
182-
183-
impl.m_contextStates[defaultCtx] = ContextState::Ready;
184176
}
185177

186178
// Phase 2: Non-default contexts

0 commit comments

Comments
 (0)