Skip to content

Commit 3a9721a

Browse files
chmjkbmsluszniakbenITo47
authored
feat: improve error handling (#715)
## Description Previously, errors were thrown as plain strings or unstructured exceptions, making it impossible for users to programmatically handle different error types. This PR introduces type-safe, structured error handling across the entire codebase. ### Changes #### 1. Codegen Approach - Added single source of truth for error codes in `scripts/errors.config.ts` - Generates matching C++ (`ErrorCodes.h`) and TypeScript (`ErrorCodes.ts`) enums - Preserves JSDoc comments from config to generated files - Run `yarn codegen:errors` to regenerate after modifying error definitions #### 2. Error Structure All errors now follow a consistent format: `{code: number, message: string}` **C++ side:** - Throw `RnExecutorchError(ErrorCode, "message")` instead of `std::runtime_error` - Use `RnExecutorchInternalError::ErrorName` for internal errors - ExecuTorch runtime errors are passed directly via the variant - Updated all model files: `BaseModel.cpp`, `LLM.cpp`, `Detector.cpp`, `Recognizer.cpp`, `VerticalOCR.cpp`, etc. **TypeScript side:** - Throw `ExecutorchError(ETErrorCode.ErrorName, "message")` instead of plain `Error` - Use `parseUnknownError(err)` in catch blocks to convert unknown errors - Updated all modules, controllers, and hooks #### 3. Error Code Ranges Currently that's used pretty frivolously and this needs to be fixed. #### 4. Key Quirk: Different Enums for C++ vs TypeScript - **C++ enum** (`RnExecutorchInternalError`): Only includes internal errors, excludes ExecuTorch runtime errors - **TypeScript enum** (`ETErrorCode`): Includes all errors (internal + ExecuTorch mapped) - Reason: C++ handles ExecuTorch runtime errors via `ErrorVariant = std::variant<RnExecutorchInternalError, executorch::runtime::Error>` #### 5. Documentation Added error handling documentation at `docs/docs/03-typescript-api/04-error-handling.md`: - Overview with example code - Complete reference of all error codes organized by category - "When It Occurs" and "How to Handle" guidance for each error - Best practices for retry logic, input validation, and state management ### Important: From Now On **When throwing errors from the native side, always use `RnExecutorchError` with appropriate error codes from `RnExecutorchInternalError` enum.** This ensures errors are properly serialized across the JSI boundary and can be handled by users. ### Introduces a breaking change? - [x] Yes - [ ] No ### Type of change - [ ] Bug fix (change which fixes an issue) - [x] New feature (change which adds functionality) - [ ] Documentation update (improves or adds clarity to existing documentation) - [ ] Other (chores, tests, code style improvements etc.) ### Tested on - [ ] iOS - [ ] Android ### Testing instructions <!-- Provide step-by-step instructions on how to test your changes. Include setup details if necessary. --> ### Screenshots <!-- Add screenshots here, if applicable --> ### Related issues <!-- Link related issues here using #issue-number --> ### Checklist - [ ] I have performed a self-review of my code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have updated the documentation accordingly - [ ] My changes generate no new warnings ### Additional notes <!-- Include any additional information, assumptions, or context that reviewers might need to understand this PR. --> --------- Co-authored-by: Mateusz Sluszniak <56299341+msluszniak@users.noreply.github.com> Co-authored-by: Bartek <116185412+benITo47@users.noreply.github.com>
1 parent 8800e00 commit 3a9721a

76 files changed

Lines changed: 1488 additions & 404 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.cspell-wordlist.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,7 @@ phonemizers
9090
phonemis
9191
Español
9292
Français
93-
Português
93+
Português
94+
codegen
95+
cstdint
96+
ocurred

apps/text-embeddings/utils/math.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1+
import {
2+
RnExecutorchError,
3+
RnExecutorchErrorCode,
4+
} from 'react-native-executorch';
5+
16
export const dotProduct = (a: Float32Array, b: Float32Array) => {
27
if (a.length !== b.length) {
3-
throw new Error('Vectors must be of the same length');
8+
throw new RnExecutorchError(
9+
RnExecutorchErrorCode.WrongDimensions,
10+
`dotProduct needs both vector to have the same length: got a: ${a.length}, b: ${b.length}`
11+
);
412
}
513

614
let sum = 0;
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
---
2+
title: Error handling
3+
---
4+
5+
## Overview
6+
7+
In order to handle different types of errors, you can use `instanceof` with our exported class `RnExecutorchError` and its `code` property. This allows you to check what exactly went wrong and act accordingly.
8+
9+
This example uses the `LLMModule`, and then tries to change its `generationConfig`. As the topp param has to be a value between 0 and 1 (inclusive), the `.configure()` method will throw an error with a code InvalidConfig.
10+
11+
```typescript
12+
import {
13+
LLMModule,
14+
LLAMA3_2_1B_QLORA,
15+
RnExecutorchError,
16+
RnExecutorchErrorCode,
17+
} from 'react-native-executorch';
18+
19+
const llm = new LLMModule({
20+
tokenCallback: (token) => console.log(token),
21+
messageHistoryCallback: (messages) => console.log(messages),
22+
});
23+
24+
await llm.load(LLAMA3_2_1B_QLORA, (progress) => console.log(progress));
25+
26+
// Try to set an invalid configuration
27+
try {
28+
await llm.configure({ topp: 1.5 }); // This will throw InvalidConfig error
29+
} catch (err) {
30+
if (
31+
err instanceof RnExecutorchError &&
32+
err.code === RnExecutorchErrorCode.InvalidConfig
33+
) {
34+
console.error('Invalid configuration:', err.message);
35+
// Handle the invalid config - set default values
36+
await llm.configure({ topp: 0.9 });
37+
} else {
38+
throw err;
39+
}
40+
}
41+
42+
// Running the model
43+
try {
44+
await llm.sendMessage('Hello, World!');
45+
} catch (err) {
46+
if (err instanceof RnExecutorchError) {
47+
if (err.code === RnExecutorchErrorCode.ModuleNotLoaded) {
48+
console.error('Model not loaded:', err.message);
49+
// Load the model first
50+
} else if (err.code === RnExecutorchErrorCode.ModelGenerating) {
51+
console.error('Model is already generating:', err.message);
52+
// Wait for current generation to complete
53+
} else {
54+
console.error('Generation error:', err.message);
55+
throw err;
56+
}
57+
} else {
58+
throw err;
59+
}
60+
}
61+
62+
// Interrupting the model (to actually interrupt the generation it has to be called when sendMessage or generate is running)
63+
llm.interrupt();
64+
65+
// Deleting the model from memory
66+
llm.delete();
67+
```
68+
69+
## Reference
70+
71+
All errors in React Native ExecuTorch inherit from `RnExecutorchError` and include a `code` property from the `RnExecutorchErrorCode` enum. Below is a comprehensive list of all possible errors, organized by category.
72+
73+
### Module State Errors
74+
75+
These errors occur when trying to perform operations on a model in an invalid state.
76+
77+
| Error Code | Description | When It Occurs | How to Handle |
78+
| ----------------- | ------------------------------------- | ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
79+
| `ModuleNotLoaded` | Model is not loaded into memory | Calling `forward()`, `generate()`, or other inference methods before calling `load()` | Load the model first with `load()` |
80+
| `ModelGenerating` | Model is already processing a request | Calling `generate()` or `forward()` while another inference is running | Wait for current generation to complete, or use `interrupt()` to cancel it |
81+
82+
### Configuration Errors
83+
84+
These errors occur when invalid configuration or input is provided.
85+
86+
| Error Code | Description | When It Occurs | How to Handle |
87+
| ---------------------- | ------------------------------------ | ------------------------------------------------------------------------- | ---------------------------------------------------- |
88+
| `InvalidConfig` | Configuration parameters are invalid | Setting parameters outside valid ranges (e.g., `topp` outside [0, 1]) | Check parameter constraints and provide valid values |
89+
| `InvalidUserInput` | Input provided to API is invalid | Passing empty arrays, null values, or malformed data to methods | Validate input before calling methods |
90+
| `InvalidModelSource` | Model source type is invalid | Providing wrong type for model source (e.g., object when string expected) | Ensure model source matches expected type |
91+
| `LanguageNotSupported` | Language not supported by model | Passing unsupported language to multilingual OCR or Speech-to-Text models | Use a supported language or different model |
92+
| `WrongDimensions` | Input tensor dimensions don't match | Providing input with incorrect shape for the model | Check model's expected input dimensions |
93+
| `UnexpectedNumInputs` | Wrong number of inputs provided | Passing more or fewer inputs than model expects | Match the number of inputs to model metadata |
94+
95+
### File Operations Errors
96+
97+
These errors occur during file read/write operations.
98+
99+
| Error Code | Description | When It Occurs | How to Handle |
100+
| ----------------- | --------------------------- | ------------------------------------------------------------ | --------------------------------------------- |
101+
| `FileReadFailed` | File read operation failed | Invalid image URL, unsupported format, or file doesn't exist | Verify file path and format are correct |
102+
| `FileWriteFailed` | File write operation failed | Saving result image or output file fails | Check storage permissions and available space |
103+
104+
### Download & Resource Fetcher Errors
105+
106+
These errors occur during model download and resource management.
107+
108+
| Error Code | Description | When It Occurs | How to Handle |
109+
| ----------------------------------- | -------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------- |
110+
| `DownloadInterrupted` | Download was interrupted | Not all files were downloaded successfully | Retry the download |
111+
| `ResourceFetcherDownloadFailed` | Resource download failed | Network error, invalid URL, or server error | Check network connection and URL validity, retry with exponential backoff |
112+
| `ResourceFetcherDownloadInProgress` | Download already in progress | Calling `fetch()` for same resource while downloading | Wait for current download to complete |
113+
| `ResourceFetcherAlreadyPaused` | Download already paused | Calling `pauseFetching()` on already paused download | Check download state before pausing |
114+
| `ResourceFetcherAlreadyOngoing` | Download already ongoing | Calling `resumeFetching()` on active download | No action needed, download is already running |
115+
| `ResourceFetcherNotActive` | No active download found | Calling pause/resume/cancel on non-existent download | Verify download was started before trying to control it |
116+
| `ResourceFetcherMissingUri` | Required URI information missing | Internal state error during download operations | Restart the download from beginning |
117+
118+
### Speech-to-Text Streaming Errors
119+
120+
These errors are specific to streaming transcription operations.
121+
122+
| Error Code | Description | When It Occurs | How to Handle |
123+
| --------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
124+
| `MultilingualConfiguration` | Multilingual configuration mismatch | Setting language on non-multilingual model, or not setting language on multilingual model | Check if model is multilingual and provide language accordingly |
125+
| `MissingDataChunk` | Audio data chunk missing | Streaming transcription without providing audio data | Ensure audio data is provided to streaming methods |
126+
| `StreamingNotStarted` | Stream not started | Calling `stop()` or `insertData()` without calling `start()` first | Call `start()` before other streaming operations |
127+
| `StreamingInProgress` | Stream already in progress | Calling `start()` while another stream is active | Stop current stream before starting a new one |
128+
129+
### Model Execution Errors
130+
131+
These errors come from the ExecuTorch runtime during model execution.
132+
133+
| Error Code | Description | When It Occurs | How to Handle |
134+
| -------------------- | ---------------------------- | ---------------------------------------------- | ---------------------------------------------- |
135+
| `InvalidModelOutput` | Model output size unexpected | Model produces output of wrong size | Verify model is compatible with the library |
136+
| `ThreadPoolError` | Threadpool operation failed | Internal threading issue | Restart the model or app |
137+
| `UnknownError` | Unexpected error occurred | 3rd-party library error or unhandled exception | Check logs for details, report if reproducible |
138+
139+
### ExecuTorch Runtime Errors
140+
141+
These errors are mapped directly from the ExecuTorch runtime. They typically indicate lower-level execution issues.
142+
143+
#### System Errors
144+
145+
| Error Code | Description |
146+
| -------------- | ----------------------------------- |
147+
| `Ok` | Operation successful (not an error) |
148+
| `Internal` | Internal ExecuTorch error |
149+
| `InvalidState` | Operation called in invalid state |
150+
| `EndOfMethod` | End of method reached |
151+
152+
#### Logical Errors
153+
154+
| Error Code | Description |
155+
| ----------------- | ------------------------------------ |
156+
| `NotSupported` | Operation not supported by model |
157+
| `NotImplemented` | Feature not implemented |
158+
| `InvalidArgument` | Invalid argument passed to operation |
159+
| `InvalidType` | Type mismatch in operation |
160+
| `OperatorMissing` | Required operator missing from model |
161+
162+
#### Resource Errors
163+
164+
| Error Code | Description |
165+
| ------------------------ | -------------------------- |
166+
| `NotFound` | Resource not found |
167+
| `MemoryAllocationFailed` | Memory allocation failed |
168+
| `AccessFailed` | Access to resource failed |
169+
| `InvalidProgram` | Model program is invalid |
170+
| `InvalidExternalData` | External data is invalid |
171+
| `OutOfResources` | System resources exhausted |
172+
173+
#### Delegate Errors
174+
175+
| Error Code | Description |
176+
| -------------------------------- | ---------------------------------- |
177+
| `DelegateInvalidCompatibility` | Delegate not compatible with model |
178+
| `DelegateMemoryAllocationFailed` | Delegate memory allocation failed |
179+
| `DelegateInvalidHandle` | Invalid delegate handle |

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
},
1111
"scripts": {
1212
"lint": "yarn workspaces foreach --all --parallel run lint",
13-
"typecheck": "yarn workspaces foreach --all --parallel run typecheck"
13+
"typecheck": "yarn workspaces foreach --all --parallel run typecheck",
14+
"codegen:errors": "npx ts-node scripts/generate-errors.ts"
1415
},
1516
"private": true,
1617
"devDependencies": {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#pragma once
2+
3+
#include <executorch/runtime/core/error.h>
4+
#include <stdexcept>
5+
#include <string>
6+
#include <variant>
7+
8+
#include <rnexecutorch/ErrorCodes.h>
9+
10+
namespace rnexecutorch {
11+
12+
using ErrorVariant =
13+
std::variant<RnExecutorchErrorCode, executorch::runtime::Error>;
14+
15+
class RnExecutorchError : public std::runtime_error {
16+
public:
17+
ErrorVariant errorCode;
18+
19+
RnExecutorchError(ErrorVariant code, const std::string &message)
20+
: std::runtime_error(message), errorCode(code) {}
21+
22+
int32_t getNumericCode() const noexcept {
23+
return std::visit(
24+
[](auto &&arg) -> int32_t { return static_cast<int32_t>(arg); },
25+
errorCode);
26+
}
27+
28+
bool isRnExecutorchError() const noexcept {
29+
return std::holds_alternative<RnExecutorchErrorCode>(errorCode);
30+
}
31+
32+
bool isExecuTorchRuntimeError() const noexcept {
33+
return std::holds_alternative<executorch::runtime::Error>(errorCode);
34+
}
35+
};
36+
37+
} // namespace rnexecutorch
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#pragma once
2+
3+
// Auto-generated from scripts/errors.config.ts
4+
// DO NOT EDIT MANUALLY - Run 'yarn codegen:errors' to regenerate
5+
6+
#include <cstdint>
7+
8+
namespace rnexecutorch {
9+
10+
enum class RnExecutorchErrorCode : int32_t {
11+
/**
12+
* An umbrella-error that is thrown usually when something unexpected happens,
13+
* for example a 3rd-party library error.
14+
*/
15+
UnknownError = 101,
16+
/**
17+
* Thrown when a user tries to run a model that is not yet downloaded or
18+
* loaded into memory.
19+
*/
20+
ModuleNotLoaded = 102,
21+
/**
22+
* An error ocurred when saving a file. This could be, for instance a result
23+
* image from an image model.
24+
*/
25+
FileWriteFailed = 103,
26+
/**
27+
* Thrown when a user tries to run a model that is currently processing. It is
28+
* only allowed to run a single model prediction at a time.
29+
*/
30+
ModelGenerating = 104,
31+
/**
32+
* Thrown when a language is passed to a multi-language model that is not
33+
* supported. For example OCR or Speech To Text.
34+
*/
35+
LanguageNotSupported = 105,
36+
/**
37+
* Thrown when config parameters passed to a model are invalid. For example,
38+
* when LLM's topp is outside of range [0, 1].
39+
*/
40+
InvalidConfig = 112,
41+
/**
42+
* Thrown when the type of model source passed by the user is invalid.
43+
*/
44+
InvalidModelSource = 255,
45+
/**
46+
* Thrown when the number of passed inputs to the model is different than the
47+
* model metadata specifies.
48+
*/
49+
UnexpectedNumInputs = 97,
50+
/**
51+
* Thrown when React Native ExecuTorch threadpool problem occurs.
52+
*/
53+
ThreadPoolError = 113,
54+
/**
55+
* Thrown when a file read operation failed. This could be invalid image url
56+
* passed to image models, or unsupported format.
57+
*/
58+
FileReadFailed = 114,
59+
/**
60+
* Thrown when the size of model output is unexpected.
61+
*/
62+
InvalidModelOutput = 115,
63+
/**
64+
* Thrown when the dimensions of input tensors don't match the model's
65+
* expected dimensions.
66+
*/
67+
WrongDimensions = 116,
68+
/**
69+
* Thrown when the input passed to our APIs is invalid, for example when
70+
* passing an empty message aray to LLM's generate().
71+
*/
72+
InvalidUserInput = 117,
73+
/**
74+
* Thrown when the number of downloaded files is unexpected, due to download
75+
* interruptions.
76+
*/
77+
DownloadInterrupted = 118,
78+
/**
79+
* Thrown when there's a configuration mismatch between multilingual and
80+
* language settings in Speech-to-Text models.
81+
*/
82+
MultilingualConfiguration = 160,
83+
/**
84+
* Thrown when streaming transcription is attempted but audio data chunk is
85+
* missing.
86+
*/
87+
MissingDataChunk = 161,
88+
/**
89+
* Thrown when trying to stop or insert data into a stream that hasn't been
90+
* started.
91+
*/
92+
StreamingNotStarted = 162,
93+
/**
94+
* Thrown when trying to start a new streaming session while another is
95+
* already in progress.
96+
*/
97+
StreamingInProgress = 163,
98+
/**
99+
* Thrown when a resource fails to download. This could be due to invalid URL,
100+
* or for example a network problem.
101+
*/
102+
ResourceFetcherDownloadFailed = 180,
103+
/**
104+
* Thrown when a user tries to trigger a download that's already in progress.
105+
*/
106+
ResourceFetcherDownloadInProgress = 181,
107+
/**
108+
* Thrown when trying to pause a download that is already paused.
109+
*/
110+
ResourceFetcherAlreadyPaused = 182,
111+
/**
112+
* Thrown when trying to resume a download that is already ongoing.
113+
*/
114+
ResourceFetcherAlreadyOngoing = 183,
115+
/**
116+
* Thrown when trying to pause, resume, or cancel a download that is not
117+
* active.
118+
*/
119+
ResourceFetcherNotActive = 184,
120+
/**
121+
* Thrown when required URI information is missing for a download operation.
122+
*/
123+
ResourceFetcherMissingUri = 185,
124+
};
125+
126+
} // namespace rnexecutorch

0 commit comments

Comments
 (0)