Nadro/4857/wasm panic catching errors (#4901)
* chore: skeleton code to initialize and detect the global WASM panic * chore: implementing a reimport method to fix the wasm instance being bricked * fix: cleaning up tsc/lint * fix: renaming file to be more accurate * fix: added toast message * fix: types... * fix: typed the functions with arg spreads
This commit is contained in:
@ -10,8 +10,11 @@ import { AppStreamProvider } from 'AppState'
|
||||
import { ToastUpdate } from 'components/ToastUpdate'
|
||||
import { markOnce } from 'lib/performance'
|
||||
import { AUTO_UPDATER_TOAST_ID } from 'lib/constants'
|
||||
import { initializeWindowExceptionHandler } from 'lib/exceptions'
|
||||
|
||||
markOnce('code/willAuth')
|
||||
initializeWindowExceptionHandler()
|
||||
|
||||
// uncomment for xstate inspector
|
||||
// import { DEV } from 'env'
|
||||
// import { inspect } from '@xstate/inspect'
|
||||
|
@ -390,6 +390,24 @@ export class KclManager {
|
||||
this._cancelTokens.delete(currentExecutionId)
|
||||
markOnce('code/endExecuteAst')
|
||||
}
|
||||
|
||||
/**
|
||||
* This cleanup function is external and internal to the KclSingleton class.
|
||||
* Since the WASM runtime can panic and the error cannot be caught in executeAst
|
||||
* we need a global exception handler in exceptions.ts
|
||||
* This file will interface with this cleanup as if it caught the original error
|
||||
* to properly restore the TS application state.
|
||||
*/
|
||||
executeAstCleanUp() {
|
||||
this.isExecuting = false
|
||||
this.executeIsStale = null
|
||||
this.engineCommandManager.addCommandLog({
|
||||
type: 'execution-done',
|
||||
data: null,
|
||||
})
|
||||
markOnce('code/endExecuteAst')
|
||||
}
|
||||
|
||||
// NOTE: this always updates the code state and editor.
|
||||
// DO NOT CALL THIS from codemirror ever.
|
||||
async executeAstMock(
|
||||
|
@ -1,4 +1,5 @@
|
||||
import init, {
|
||||
import {
|
||||
init,
|
||||
parse_wasm,
|
||||
recast_wasm,
|
||||
execute,
|
||||
@ -16,7 +17,9 @@ import init, {
|
||||
default_project_settings,
|
||||
base64_decode,
|
||||
clear_scene_and_bust_cache,
|
||||
} from '../wasm-lib/pkg/wasm_lib'
|
||||
reloadModule,
|
||||
} from 'lib/wasm_lib_wrapper'
|
||||
|
||||
import { KCLError } from './errors'
|
||||
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
||||
import { EngineCommandManager } from './std/engineConnection'
|
||||
@ -144,6 +147,7 @@ export const wasmUrl = () => {
|
||||
// Initialise the wasm module.
|
||||
const initialise = async () => {
|
||||
try {
|
||||
await reloadModule()
|
||||
const fullUrl = wasmUrl()
|
||||
const input = await fetch(fullUrl)
|
||||
const buffer = await input.arrayBuffer()
|
||||
|
51
src/lib/exceptions.ts
Normal file
51
src/lib/exceptions.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { reloadModule, getModule } from 'lib/wasm_lib_wrapper'
|
||||
import toast from 'react-hot-toast'
|
||||
import { reportRejection } from './trap'
|
||||
|
||||
let initialized = false
|
||||
|
||||
/**
|
||||
* WASM/Rust runtime can panic and the original try/catch/finally blocks will not trigger
|
||||
* on the await promise. The interface will killed. This means we need to catch the error at
|
||||
* the global/DOM level. This will have to interface with whatever controlflow that needs to be picked up
|
||||
* within the error branch in the typescript to cover the application state.
|
||||
*/
|
||||
export const initializeWindowExceptionHandler = () => {
|
||||
if (window && !initialized) {
|
||||
window.addEventListener('error', (event) => {
|
||||
void (async () => {
|
||||
if (matchImportExportErrorCrash(event.message)) {
|
||||
// do global singleton cleanup
|
||||
kclManager.executeAstCleanUp()
|
||||
toast.error(
|
||||
'You have hit a KCL execution bug! Put your KCL code in a github issue to help us resolve this bug.'
|
||||
)
|
||||
try {
|
||||
await reloadModule()
|
||||
await getModule().default()
|
||||
} catch (e) {
|
||||
console.error('Failed to initialize wasm_lib')
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
})().catch(reportRejection)
|
||||
})
|
||||
// Make sure we only initialize this event listener once
|
||||
initialized = true
|
||||
} else {
|
||||
console.error(
|
||||
`Failed to initialize, window: ${window}, initialized:${initialized}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifically match a substring of the message error to detect an import export runtime issue
|
||||
* when the WASM runtime panics
|
||||
*/
|
||||
const matchImportExportErrorCrash = (message: string): boolean => {
|
||||
// called `Result::unwrap_throw()` on an `Err` value
|
||||
const substringError = '`Result::unwrap_throw()` on an `Err` value'
|
||||
return message.indexOf(substringError) !== -1 ? true : false
|
||||
}
|
108
src/lib/wasm_lib_wrapper.ts
Normal file
108
src/lib/wasm_lib_wrapper.ts
Normal file
@ -0,0 +1,108 @@
|
||||
/**
|
||||
* This wrapper file is to enable reloading of the wasm_lib.js file.
|
||||
* When the wasm instance bricks there is no API or interface to restart,
|
||||
* restore, or re init the WebAssembly instance. The entire application would need
|
||||
* to restart.
|
||||
* A way to bypass this is by reloading the entire .js file so the global wasm variable
|
||||
* gets reinitialized and we do not use that old reference
|
||||
*/
|
||||
|
||||
import {
|
||||
parse_wasm as ParseWasm,
|
||||
recast_wasm as RecastWasm,
|
||||
execute as Execute,
|
||||
kcl_lint as KclLint,
|
||||
modify_ast_for_sketch_wasm as ModifyAstForSketch,
|
||||
is_points_ccw as IsPointsCcw,
|
||||
get_tangential_arc_to_info as GetTangentialArcToInfo,
|
||||
program_memory_init as ProgramMemoryInit,
|
||||
make_default_planes as MakeDefaultPlanes,
|
||||
coredump as CoreDump,
|
||||
toml_stringify as TomlStringify,
|
||||
default_app_settings as DefaultAppSettings,
|
||||
parse_app_settings as ParseAppSettings,
|
||||
parse_project_settings as ParseProjectSettings,
|
||||
default_project_settings as DefaultProjectSettings,
|
||||
base64_decode as Base64Decode,
|
||||
clear_scene_and_bust_cache as ClearSceneAndBustCache,
|
||||
} from '../wasm-lib/pkg/wasm_lib'
|
||||
|
||||
type ModuleType = typeof import('../wasm-lib/pkg/wasm_lib')
|
||||
|
||||
// Stores the result of the import of the wasm_lib file
|
||||
let data: ModuleType
|
||||
|
||||
// Imports the .js file again which will clear the old import
|
||||
// This allows us to reinitialize the wasm instance
|
||||
export async function reloadModule() {
|
||||
data = await import(`../wasm-lib/pkg/wasm_lib`)
|
||||
}
|
||||
|
||||
export function getModule(): ModuleType {
|
||||
return data
|
||||
}
|
||||
|
||||
export async function init(module_or_path: any) {
|
||||
return await getModule().default(module_or_path)
|
||||
}
|
||||
export const parse_wasm: typeof ParseWasm = (...args) => {
|
||||
return getModule().parse_wasm(...args)
|
||||
}
|
||||
export const recast_wasm: typeof RecastWasm = (...args) => {
|
||||
return getModule().recast_wasm(...args)
|
||||
}
|
||||
export const execute: typeof Execute = (...args) => {
|
||||
return getModule().execute(...args)
|
||||
}
|
||||
export const kcl_lint: typeof KclLint = (...args) => {
|
||||
return getModule().kcl_lint(...args)
|
||||
}
|
||||
export const modify_ast_for_sketch_wasm: typeof ModifyAstForSketch = (
|
||||
...args
|
||||
) => {
|
||||
return getModule().modify_ast_for_sketch_wasm(...args)
|
||||
}
|
||||
export const is_points_ccw: typeof IsPointsCcw = (...args) => {
|
||||
return getModule().is_points_ccw(...args)
|
||||
}
|
||||
export const get_tangential_arc_to_info: typeof GetTangentialArcToInfo = (
|
||||
...args
|
||||
) => {
|
||||
return getModule().get_tangential_arc_to_info(...args)
|
||||
}
|
||||
export const program_memory_init: typeof ProgramMemoryInit = (...args) => {
|
||||
return getModule().program_memory_init(...args)
|
||||
}
|
||||
export const make_default_planes: typeof MakeDefaultPlanes = (...args) => {
|
||||
return getModule().make_default_planes(...args)
|
||||
}
|
||||
export const coredump: typeof CoreDump = (...args) => {
|
||||
return getModule().coredump(...args)
|
||||
}
|
||||
export const toml_stringify: typeof TomlStringify = (...args) => {
|
||||
return getModule().toml_stringify(...args)
|
||||
}
|
||||
export const default_app_settings: typeof DefaultAppSettings = (...args) => {
|
||||
return getModule().default_app_settings(...args)
|
||||
}
|
||||
export const parse_app_settings: typeof ParseAppSettings = (...args) => {
|
||||
return getModule().parse_app_settings(...args)
|
||||
}
|
||||
export const parse_project_settings: typeof ParseProjectSettings = (
|
||||
...args
|
||||
) => {
|
||||
return getModule().parse_project_settings(...args)
|
||||
}
|
||||
export const default_project_settings: typeof DefaultProjectSettings = (
|
||||
...args
|
||||
) => {
|
||||
return getModule().default_project_settings(...args)
|
||||
}
|
||||
export const base64_decode: typeof Base64Decode = (...args) => {
|
||||
return getModule().base64_decode(...args)
|
||||
}
|
||||
export const clear_scene_and_bust_cache: typeof ClearSceneAndBustCache = (
|
||||
...args
|
||||
) => {
|
||||
return getModule().clear_scene_and_bust_cache(...args)
|
||||
}
|
Reference in New Issue
Block a user