mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-10 21:37:22 +00:00
change prepare worker to use fork instead of threads (#1685)
Co-authored-by: Marcin S <marcin@realemail.net>
This commit is contained in:
@@ -35,9 +35,9 @@ pub enum PrepareError {
|
||||
/// Instantiation of the WASM module instance failed.
|
||||
#[codec(index = 2)]
|
||||
RuntimeConstruction(String),
|
||||
/// An unexpected panic has occurred in the preparation worker.
|
||||
/// An unexpected error has occurred in the preparation job.
|
||||
#[codec(index = 3)]
|
||||
Panic(String),
|
||||
JobError(String),
|
||||
/// Failed to prepare the PVF due to the time limit.
|
||||
#[codec(index = 4)]
|
||||
TimedOut,
|
||||
@@ -48,12 +48,12 @@ pub enum PrepareError {
|
||||
/// The temporary file for the artifact could not be created at the given cache path. This
|
||||
/// state is reported by the validation host (not by the worker).
|
||||
#[codec(index = 6)]
|
||||
CreateTmpFileErr(String),
|
||||
CreateTmpFile(String),
|
||||
/// The response from the worker is received, but the file cannot be renamed (moved) to the
|
||||
/// final destination location. This state is reported by the validation host (not by the
|
||||
/// worker).
|
||||
#[codec(index = 7)]
|
||||
RenameTmpFileErr {
|
||||
RenameTmpFile {
|
||||
err: String,
|
||||
// Unfortunately `PathBuf` doesn't implement `Encode`/`Decode`, so we do a fallible
|
||||
// conversion to `Option<String>`.
|
||||
@@ -68,11 +68,14 @@ pub enum PrepareError {
|
||||
/// reported by the validation host (not by the worker).
|
||||
#[codec(index = 9)]
|
||||
ClearWorkerDir(String),
|
||||
/// The preparation job process died, due to OOM, a seccomp violation, or some other factor.
|
||||
JobDied(String),
|
||||
#[codec(index = 10)]
|
||||
/// Some error occurred when interfacing with the kernel.
|
||||
#[codec(index = 11)]
|
||||
Kernel(String),
|
||||
}
|
||||
|
||||
/// Pre-encoded length-prefixed `PrepareResult::Err(PrepareError::OutOfMemory)`
|
||||
pub const OOM_PAYLOAD: &[u8] = b"\x02\x00\x00\x00\x00\x00\x00\x00\x01\x08";
|
||||
|
||||
impl PrepareError {
|
||||
/// Returns whether this is a deterministic error, i.e. one that should trigger reliably. Those
|
||||
/// errors depend on the PVF itself and the sc-executor/wasmtime logic.
|
||||
@@ -83,12 +86,15 @@ impl PrepareError {
|
||||
pub fn is_deterministic(&self) -> bool {
|
||||
use PrepareError::*;
|
||||
match self {
|
||||
Prevalidation(_) | Preparation(_) | Panic(_) | OutOfMemory => true,
|
||||
TimedOut |
|
||||
Prevalidation(_) | Preparation(_) | JobError(_) | OutOfMemory => true,
|
||||
IoErr(_) |
|
||||
CreateTmpFileErr(_) |
|
||||
RenameTmpFileErr { .. } |
|
||||
ClearWorkerDir(_) => false,
|
||||
JobDied(_) |
|
||||
CreateTmpFile(_) |
|
||||
RenameTmpFile { .. } |
|
||||
ClearWorkerDir(_) |
|
||||
Kernel(_) => false,
|
||||
// Can occur due to issues with the PVF, but also due to factors like local load.
|
||||
TimedOut => false,
|
||||
// Can occur due to issues with the PVF, but also due to local errors.
|
||||
RuntimeConstruction(_) => false,
|
||||
}
|
||||
@@ -102,14 +108,16 @@ impl fmt::Display for PrepareError {
|
||||
Prevalidation(err) => write!(f, "prevalidation: {}", err),
|
||||
Preparation(err) => write!(f, "preparation: {}", err),
|
||||
RuntimeConstruction(err) => write!(f, "runtime construction: {}", err),
|
||||
Panic(err) => write!(f, "panic: {}", err),
|
||||
JobError(err) => write!(f, "panic: {}", err),
|
||||
TimedOut => write!(f, "prepare: timeout"),
|
||||
IoErr(err) => write!(f, "prepare: io error while receiving response: {}", err),
|
||||
CreateTmpFileErr(err) => write!(f, "prepare: error creating tmp file: {}", err),
|
||||
RenameTmpFileErr { err, src, dest } =>
|
||||
JobDied(err) => write!(f, "prepare: prepare job died: {}", err),
|
||||
CreateTmpFile(err) => write!(f, "prepare: error creating tmp file: {}", err),
|
||||
RenameTmpFile { err, src, dest } =>
|
||||
write!(f, "prepare: error renaming tmp file ({:?} -> {:?}): {}", src, dest, err),
|
||||
OutOfMemory => write!(f, "prepare: out of memory"),
|
||||
ClearWorkerDir(err) => write!(f, "prepare: error clearing worker cache: {}", err),
|
||||
Kernel(err) => write!(f, "prepare: error interfacing with the kernel: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,9 +141,9 @@ pub enum InternalValidationError {
|
||||
// conversion to `Option<String>`.
|
||||
path: Option<String>,
|
||||
},
|
||||
/// An error occurred in the CPU time monitor thread. Should be totally unrelated to
|
||||
/// validation.
|
||||
CpuTimeMonitorThread(String),
|
||||
/// Some error occurred when interfacing with the kernel.
|
||||
Kernel(String),
|
||||
|
||||
/// Some non-deterministic preparation error occurred.
|
||||
NonDeterministicPrepareError(PrepareError),
|
||||
}
|
||||
@@ -158,17 +166,8 @@ impl fmt::Display for InternalValidationError {
|
||||
"validation: host could not clear the worker cache ({:?}) after a job: {}",
|
||||
path, err
|
||||
),
|
||||
CpuTimeMonitorThread(err) =>
|
||||
write!(f, "validation: an error occurred in the CPU time monitor thread: {}", err),
|
||||
Kernel(err) => write!(f, "validation: error interfacing with the kernel: {}", err),
|
||||
NonDeterministicPrepareError(err) => write!(f, "validation: prepare: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_encoded_payloads() {
|
||||
let oom_enc = PrepareResult::Err(PrepareError::OutOfMemory).encode();
|
||||
let mut oom_payload = oom_enc.len().to_le_bytes().to_vec();
|
||||
oom_payload.extend(oom_enc);
|
||||
assert_eq!(oom_payload, OOM_PAYLOAD);
|
||||
}
|
||||
|
||||
@@ -28,9 +28,9 @@ pub struct Handshake {
|
||||
pub executor_params: ExecutorParams,
|
||||
}
|
||||
|
||||
/// The response from an execution job on the worker.
|
||||
/// The response from the execution worker.
|
||||
#[derive(Debug, Encode, Decode)]
|
||||
pub enum Response {
|
||||
pub enum WorkerResponse {
|
||||
/// The job completed successfully.
|
||||
Ok {
|
||||
/// The result of parachain validation.
|
||||
@@ -41,14 +41,38 @@ pub enum Response {
|
||||
/// The candidate is invalid.
|
||||
InvalidCandidate(String),
|
||||
/// The job timed out.
|
||||
TimedOut,
|
||||
/// An unexpected panic has occurred in the execution worker.
|
||||
Panic(String),
|
||||
JobTimedOut,
|
||||
/// The job process has died. We must kill the worker just in case.
|
||||
///
|
||||
/// We cannot treat this as an internal error because malicious code may have killed the job.
|
||||
/// We still retry it, because in the non-malicious case it is likely spurious.
|
||||
JobDied(String),
|
||||
/// An unexpected error occurred in the job process, e.g. failing to spawn a thread, panic,
|
||||
/// etc.
|
||||
///
|
||||
/// Because malicious code can cause a job error, we must not treat it as an internal error. We
|
||||
/// still retry it, because in the non-malicious case it is likely spurious.
|
||||
JobError(String),
|
||||
|
||||
/// Some internal error occurred.
|
||||
InternalError(InternalValidationError),
|
||||
}
|
||||
|
||||
impl Response {
|
||||
/// The result of a job on the execution worker.
|
||||
pub type JobResult = Result<JobResponse, JobError>;
|
||||
|
||||
/// The successful response from a job on the execution worker.
|
||||
#[derive(Debug, Encode, Decode)]
|
||||
pub enum JobResponse {
|
||||
Ok {
|
||||
/// The result of parachain validation.
|
||||
result_descriptor: ValidationResult,
|
||||
},
|
||||
/// The candidate is invalid.
|
||||
InvalidCandidate(String),
|
||||
}
|
||||
|
||||
impl JobResponse {
|
||||
/// Creates an invalid response from a context `ctx` and a message `msg` (which can be empty).
|
||||
pub fn format_invalid(ctx: &'static str, msg: &str) -> Self {
|
||||
if msg.is_empty() {
|
||||
@@ -58,3 +82,18 @@ impl Response {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An unexpected error occurred in the execution job process. Because this comes from the job,
|
||||
/// which executes untrusted code, this error must likewise be treated as untrusted. That is, we
|
||||
/// cannot raise an internal error based on this.
|
||||
#[derive(thiserror::Error, Debug, Encode, Decode)]
|
||||
pub enum JobError {
|
||||
#[error("The job timed out")]
|
||||
TimedOut,
|
||||
#[error("An unexpected panic has occurred in the execution job: {0}")]
|
||||
Panic(String),
|
||||
#[error("Could not spawn the requested thread: {0}")]
|
||||
CouldNotSpawnThread(String),
|
||||
#[error("An error occurred in the CPU time monitor thread: {0}")]
|
||||
CpuTimeMonitorThread(String),
|
||||
}
|
||||
|
||||
@@ -205,9 +205,15 @@ impl fmt::Display for WorkerKind {
|
||||
}
|
||||
}
|
||||
|
||||
// The worker version must be passed in so that we accurately get the version of the worker, and not
|
||||
// the version that this crate was compiled with.
|
||||
pub fn worker_event_loop<F>(
|
||||
// NOTE: The worker version must be passed in so that we accurately get the version of the worker,
|
||||
// and not the version that this crate was compiled with.
|
||||
//
|
||||
// NOTE: This must not spawn any threads due to safety requirements in `event_loop` and to avoid
|
||||
// errors in [`security::unshare_user_namespace_and_change_root`].
|
||||
//
|
||||
/// Initializes the worker process, then runs the given event loop, which spawns a new job process
|
||||
/// to securely handle each incoming request.
|
||||
pub fn run_worker<F>(
|
||||
worker_kind: WorkerKind,
|
||||
socket_path: PathBuf,
|
||||
#[cfg_attr(not(target_os = "linux"), allow(unused_mut))] mut worker_dir_path: PathBuf,
|
||||
|
||||
Reference in New Issue
Block a user