|
| 1 | +# Result & Error Handling API of the Instrumented Process |
| 2 | + |
| 3 | +## Terminology |
| 4 | + |
| 5 | +- The _instrumented process_ is an external process used for the isolated invocation. |
| 6 | +- The `ConcreteExecutor` is a class which provides smooth and concise interaction with the _instrumented process_. It works in the _main process_. |
| 7 | +- A client is an object which directly uses `ConcreteExecutor`, so it works in the _main process_ as well. |
| 8 | +- An _Instrumentation_ is an object which should be passed to `ConcreteExecutor`. It defines the logic of invocation and bytecode instrumentation in the _instrumented process_. |
| 9 | + |
| 10 | +## Common |
| 11 | + |
| 12 | +Basically, if any exception happens inside the _instrumented process_, it is rethrown to the client process via RD. |
| 13 | + - Some of the errors lead to the instant death of the _instrumented process_. Such errors are wrapped in `InstrumentedProcessDeathException`. Before processing the next request, the _instrumented process_ will be restarted automatically, but it can take some time. |
| 14 | + - Errors which do not cause the termination of the _instrumented process_ are wrapped in `InstrumentedProcessError`. Process won't be restarted, so client's requests will be handled by the same process. We believe, that the state of the _instrumented process_ is consistent, but in some tricky situations it may be not. |
| 15 | + |
| 16 | +The extra logic of error and result handling depends on the provided instrumentation. |
| 17 | + |
| 18 | +## UtConcreteExecutionInstrumentation |
| 19 | + |
| 20 | +The next sections are related only to the `UtExecutionInstrumentation` passed to the _instrumented process_. |
| 21 | + |
| 22 | +The calling of `ConcreteExecutor::executeAsync` instantiated by the `UtExecutionInstrumentation` can lead to the three possible situations: |
| 23 | +- `InstrumentedProcessDeathException` occurs. Usually, this situation means there is an internal issue in the _instrumented process_, but, nevertheless, this exception should be handled by the client. |
| 24 | +- `InstrumentedProcessError` occurs. It also means an internal issue and should be handled by the client. Sometimes it happens because the client provided the wrong configuration or parameters, but the _instrumented process_ **can't determine exactly** what's wrong with the client's data. The cause contains the description of the phase which threw the exception. |
| 25 | +- No exception occurs, so the `UtConcreteExecutionResult` is returned. It means that everything went well during the invocation or something broke down because of the wrong input, and the _instrumented process_ **knows exactly** what's wrong with the client's input. The _instrumented process_ guarantee that the state **is consistent**. The exact reason of failure is a `UtConcreteExecutionResult::result` field. It includes: |
| 26 | + - `UtSandboxFailure` --- violation of permissions. |
| 27 | + - `UtTimeoutException` --- the test execution time exceeds the provided time limit (`UtConcreteExecutionData::timeout`). |
| 28 | + - `UtExecutionSuccess` --- the test executed successfully. |
| 29 | + - `UtExplicitlyThrownException` --- the target method threw exception explicitly (via `throw` instruction). |
| 30 | + - `UtImplicitlyThrownException` --- the target method threw exception implicitly (`NPE`, `OOB`, etc. or it was thrown inside the system library) |
| 31 | + - etc. |
| 32 | + |
| 33 | +### How the error handling works |
| 34 | + |
| 35 | +The flow of the `UtExecutionInstrumentation::invoke` consists of 6 phases: |
| 36 | +- `ValueConstructionPhase` --- constructs values from the models. |
| 37 | +- `PreparationPhase` --- prepares statics, etc. |
| 38 | +- `InvocationPhase` --- invokes the target method. |
| 39 | +- `StatisticsCollectionPhase` --- collects the coverage and execution-related data. |
| 40 | +- `ModelConstructionPhase` --- constructs the result models from. |
| 41 | +- `PostprocessingPhase` --- restores statics, clear mocks, etc. |
| 42 | + |
| 43 | +Each phase can throw two kinds of exceptions: |
| 44 | +- `ExecutionPhaseStop` --- indicates that the phase want to stop the invocation pipeline completely, because it's already has a result. The returned result is the `ExecutionPhaseStop::result` field. |
| 45 | +- `ExecutionPhaseError` --- indicates that an unexpected error happened inside the phase execution, so it's rethrown to the main process. |
| 46 | + |
| 47 | +The `PhasesController::computeConcreteExecutionResult` then matches on the exception type and rethrows the exception if it's an `ExecutionPhaseError`, and returns the result if it's an `ExecutionPhaseStop`. |
| 48 | + |
| 49 | +### Timeout |
| 50 | + |
| 51 | +There is a time limit on the concrete execution, so the `UtExecutionInstrumentation::invoke` method must respect. We wrap phases which can take a long time with the `executePhaseInTimeout` block, which internally keeps and updates the elapsed time. If any wrapped phase exceeds the timeout, we return the `TimeoutException` as the result of that phase. |
| 52 | + |
| 53 | +The clients cannot depend that cancellation request immediately breaks the invocation inside the _instrumented process_. The invocation is guaranteed to finish in the time of passed timeout, but not earlier. |
| 54 | + |
| 55 | +Even if the `TimeoutException` occurs, the _instrumented process_ is ready to receive new requests. |
0 commit comments