Skip to content

Commit 5a2307c

Browse files
NarwhalChenSma1lboyautofix-ci[bot]
authored
feat(backend): backend error handling strategy (#89)
Co-authored-by: Jackson Chen <[email protected]> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 4806829 commit 5a2307c

File tree

18 files changed

+1033
-606
lines changed

18 files changed

+1033
-606
lines changed

backend/src/build-system/__tests__/test.model-provider.spec.ts

Lines changed: 0 additions & 12 deletions
This file was deleted.

backend/src/build-system/__tests__/test.spec.ts

Lines changed: 0 additions & 60 deletions
This file was deleted.

backend/src/build-system/context.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
BuildExecutionState,
3+
BuildHandler,
34
BuildNode,
45
BuildResult,
56
BuildSequence,
@@ -12,6 +13,7 @@ import { ModelProvider } from 'src/common/model-provider';
1213
import { v4 as uuidv4 } from 'uuid';
1314
import { BuildMonitor } from './monitor';
1415
import { BuildHandlerManager } from './hanlder-manager';
16+
import { RetryHandler } from './retry-handler';
1517

1618
/**
1719
* Global data keys used throughout the build process
@@ -50,11 +52,13 @@ export class BuilderContext {
5052
waiting: new Set(),
5153
};
5254

55+
private globalPromises: Set<Promise<any>> = new Set();
5356
private logger: Logger;
5457
private globalContext: Map<GlobalDataKeys | string, any> = new Map();
5558
private nodeData: Map<string, any> = new Map();
5659

5760
private handlerManager: BuildHandlerManager;
61+
private retryHandler: RetryHandler;
5862
private monitor: BuildMonitor;
5963
public model: ModelProvider;
6064
public virtualDirectory: VirtualDirectory;
@@ -63,6 +67,7 @@ export class BuilderContext {
6367
private sequence: BuildSequence,
6468
id: string,
6569
) {
70+
this.retryHandler = RetryHandler.getInstance();
6671
this.handlerManager = BuildHandlerManager.getInstance();
6772
this.model = ModelProvider.getInstance();
6873
this.monitor = BuildMonitor.getInstance();
@@ -157,7 +162,7 @@ export class BuilderContext {
157162
const batch = executableNodes.slice(i, i + concurrencyLimit);
158163

159164
try {
160-
const nodeExecutionPromises = batch.map(async (node) => {
165+
batch.map(async (node) => {
161166
if (this.executionState.completed.has(node.id)) {
162167
return;
163168
}
@@ -169,6 +174,7 @@ export class BuilderContext {
169174
currentStep.id,
170175
);
171176

177+
let res;
172178
try {
173179
if (!this.canExecute(node.id)) {
174180
this.logger.log(
@@ -185,7 +191,8 @@ export class BuilderContext {
185191
}
186192

187193
this.logger.log(`Executing node ${node.id} in parallel batch`);
188-
await this.executeNodeById(node.id);
194+
res = this.executeNodeById(node.id);
195+
this.globalPromises.add(res);
189196

190197
this.monitor.endNodeExecution(
191198
node.id,
@@ -205,8 +212,7 @@ export class BuilderContext {
205212
}
206213
});
207214

208-
await Promise.all(nodeExecutionPromises);
209-
215+
await Promise.all(this.globalPromises);
210216
const activeModelPromises = this.model.getAllActivePromises();
211217
if (activeModelPromises.length > 0) {
212218
this.logger.debug(
@@ -336,7 +342,7 @@ export class BuilderContext {
336342
this.executionState.completed.has(nodeId) ||
337343
this.executionState.pending.has(nodeId)
338344
) {
339-
this.logger.debug(`Node ${nodeId} is already completed or pending.`);
345+
//this.logger.debug(`Node ${nodeId} is already completed or pending.`);
340346
return false;
341347
}
342348

@@ -361,6 +367,7 @@ export class BuilderContext {
361367
this.executionState.pending.add(nodeId);
362368
const result = await this.invokeNodeHandler<T>(node);
363369
this.executionState.completed.add(nodeId);
370+
this.logger.log(`${nodeId} is completed`);
364371
this.executionState.pending.delete(nodeId);
365372

366373
this.nodeData.set(node.id, result.data);
@@ -438,10 +445,23 @@ export class BuilderContext {
438445

439446
private async invokeNodeHandler<T>(node: BuildNode): Promise<BuildResult<T>> {
440447
const handler = this.handlerManager.getHandler(node.id);
448+
this.logger.log(`sovling ${node.id}`);
441449
if (!handler) {
442450
throw new Error(`No handler found for node: ${node.id}`);
443451
}
444-
445-
return handler.run(this, node.options);
452+
try {
453+
return await handler.run(this, node.options);
454+
} catch (e) {
455+
this.logger.error(`retrying ${node.id}`);
456+
const result = await this.retryHandler.retryMethod(
457+
e,
458+
(node) => this.invokeNodeHandler(node),
459+
[node],
460+
);
461+
if (result === undefined) {
462+
throw e;
463+
}
464+
return result as unknown as BuildResult<T>;
465+
}
446466
}
447467
}

backend/src/build-system/errors.ts

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// error.ts
2+
3+
/**
4+
* Base class representing retryable errors.
5+
* Inherits from JavaScript's built-in Error class.
6+
* Suitable for errors where the system can attempt to retry the operation.
7+
*/
8+
export class RetryableError extends Error {
9+
constructor(message: string) {
10+
super(message);
11+
this.name = 'RetryableError';
12+
Object.setPrototypeOf(this, new.target.prototype); // Fixes the inheritance chain for instanceof checks
13+
}
14+
}
15+
16+
/**
17+
* Base class representing non-retryable errors.
18+
* Inherits from JavaScript's built-in Error class.
19+
* Suitable for errors where the system should not attempt to retry the operation.
20+
*/
21+
export class NonRetryableError extends Error {
22+
constructor(message: string) {
23+
super(message);
24+
this.name = 'NonRetryableError';
25+
Object.setPrototypeOf(this, new.target.prototype); // Fixes the inheritance chain for instanceof checks
26+
}
27+
}
28+
29+
// Below are specific error classes inheriting from the appropriate base classes
30+
31+
/**
32+
* Error: File Not Found.
33+
* Indicates that a required file could not be found during file operations.
34+
* Non-retryable error.
35+
*/
36+
export class FileNotFoundError extends NonRetryableError {
37+
constructor(message: string) {
38+
super(message);
39+
this.name = 'FileNotFoundError';
40+
}
41+
}
42+
43+
/**
44+
* Error: File Modification Failed.
45+
* Indicates issues encountered while modifying a file, such as insufficient permissions or disk errors.
46+
* Non-retryable error.
47+
*/
48+
export class FileModificationError extends NonRetryableError {
49+
constructor(message: string) {
50+
super(message);
51+
this.name = 'FileModificationError';
52+
}
53+
}
54+
55+
/**
56+
* Error: Model Service Unavailable.
57+
* Indicates that the underlying model service cannot be reached or is down.
58+
* Retryable error, typically temporary.
59+
*/
60+
export class ModelUnavailableError extends RetryableError {
61+
constructor(message: string) {
62+
super(message);
63+
this.name = 'ModelUnavailableError';
64+
}
65+
}
66+
67+
/**
68+
* Error: Response Parsing Failed.
69+
* Indicates that the system could not properly parse the response data.
70+
* Retryable error, possibly due to temporary data format issues.
71+
*/
72+
export class ResponseParsingError extends RetryableError {
73+
constructor(message: string) {
74+
super(message);
75+
this.name = 'ResponseParsingError';
76+
}
77+
}
78+
79+
/**
80+
* Error: Response Tag Error.
81+
* Indicates that expected tags in the response are missing or invalid during content generation or parsing.
82+
* Non-retryable error.
83+
*/
84+
export class ResponseTagError extends NonRetryableError {
85+
constructor(message: string) {
86+
super(message);
87+
this.name = 'ResponseTagError';
88+
}
89+
}
90+
91+
/**
92+
* Error: Temporary Service Unavailable.
93+
* Indicates that the service is unavailable due to temporary issues like server overload or maintenance.
94+
* Retryable error, typically temporary.
95+
*/
96+
export class TemporaryServiceUnavailableError extends RetryableError {
97+
constructor(message: string) {
98+
super(message);
99+
this.name = 'TemporaryServiceUnavailableError';
100+
}
101+
}
102+
103+
/**
104+
* Error: Rate Limit Exceeded.
105+
* Indicates that too many requests have been sent within a given time frame.
106+
* Retryable error, may require waiting before retrying.
107+
*/
108+
export class RateLimitExceededError extends RetryableError {
109+
constructor(message: string) {
110+
super(message);
111+
this.name = 'RateLimitExceededError';
112+
}
113+
}
114+
115+
/**
116+
* Error: Missing Configuration.
117+
* Indicates issues with system setup or missing configuration parameters.
118+
* Non-retryable error, typically requires manual configuration fixes.
119+
*/
120+
export class MissingConfigurationError extends NonRetryableError {
121+
constructor(message: string) {
122+
super(message);
123+
this.name = 'MissingConfigurationError';
124+
}
125+
}
126+
127+
/**
128+
* Error: Invalid Parameter.
129+
* Indicates that a function argument or configuration parameter is invalid.
130+
* Non-retryable error, typically requires correcting the input parameters.
131+
*/
132+
export class InvalidParameterError extends NonRetryableError {
133+
constructor(message: string) {
134+
super(message);
135+
this.name = 'InvalidParameterError';
136+
}
137+
}
138+
139+
/**
140+
* Error: File Write Failed.
141+
* Indicates issues encountered while writing to a file, such as insufficient permissions or disk errors.
142+
* Non-retryable error.
143+
*/
144+
export class FileWriteError extends NonRetryableError {
145+
constructor(message: string) {
146+
super(message);
147+
this.name = 'FileWriteError';
148+
}
149+
}

0 commit comments

Comments
 (0)