mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-27 17:28:00 +00:00
cargo +nightly fmt (#3540)
* cargo +nightly fmt * add cargo-fmt check to ci * update ci * fmt * fmt * skip macro * ignore bridges
This commit is contained in:
@@ -15,15 +15,13 @@
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use always_assert::always;
|
||||
use async_std::{
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use async_std::path::{Path, PathBuf};
|
||||
use parity_scale_codec::{Decode, Encode};
|
||||
use polkadot_parachain::primitives::ValidationCodeHash;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
use parity_scale_codec::{Encode, Decode};
|
||||
|
||||
/// A final product of preparation process. Contains either a ready to run compiled artifact or
|
||||
/// a description what went wrong.
|
||||
@@ -70,8 +68,8 @@ impl ArtifactId {
|
||||
/// Tries to recover the artifact id from the given file name.
|
||||
#[cfg(test)]
|
||||
pub fn from_file_name(file_name: &str) -> Option<Self> {
|
||||
use std::str::FromStr as _;
|
||||
use polkadot_core_primitives::Hash;
|
||||
use std::str::FromStr as _;
|
||||
|
||||
let file_name = file_name.strip_prefix(Self::PREFIX)?;
|
||||
let code_hash = Hash::from_str(file_name).ok()?.into();
|
||||
@@ -123,9 +121,7 @@ impl Artifacts {
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn empty() -> Self {
|
||||
Self {
|
||||
artifacts: HashMap::new(),
|
||||
}
|
||||
Self { artifacts: HashMap::new() }
|
||||
}
|
||||
|
||||
/// Returns the state of the given artifact by its ID.
|
||||
@@ -139,10 +135,7 @@ impl Artifacts {
|
||||
/// replacing existing ones.
|
||||
pub fn insert_preparing(&mut self, artifact_id: ArtifactId) {
|
||||
// See the precondition.
|
||||
always!(self
|
||||
.artifacts
|
||||
.insert(artifact_id, ArtifactState::Preparing)
|
||||
.is_none());
|
||||
always!(self.artifacts.insert(artifact_id, ArtifactState::Preparing).is_none());
|
||||
}
|
||||
|
||||
/// Insert an artifact with the given ID as "prepared".
|
||||
@@ -164,9 +157,7 @@ impl Artifacts {
|
||||
|
||||
let mut to_remove = vec![];
|
||||
for (k, v) in self.artifacts.iter() {
|
||||
if let ArtifactState::Prepared {
|
||||
last_time_needed, ..
|
||||
} = *v {
|
||||
if let ArtifactState::Prepared { last_time_needed, .. } = *v {
|
||||
if now
|
||||
.duration_since(last_time_needed)
|
||||
.map(|age| age > artifact_ttl)
|
||||
@@ -187,8 +178,8 @@ impl Artifacts {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ArtifactId, Artifacts};
|
||||
use async_std::path::Path;
|
||||
use super::{Artifacts, ArtifactId};
|
||||
use sp_core::H256;
|
||||
use std::str::FromStr;
|
||||
|
||||
@@ -213,17 +204,24 @@ mod tests {
|
||||
#[test]
|
||||
fn path() {
|
||||
let path = Path::new("/test");
|
||||
let hash = H256::from_str("1234567890123456789012345678901234567890123456789012345678901234").unwrap().into();
|
||||
let hash =
|
||||
H256::from_str("1234567890123456789012345678901234567890123456789012345678901234")
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
assert_eq!(
|
||||
ArtifactId::new(hash).path(path).to_str(),
|
||||
Some("/test/wasmtime_0x1234567890123456789012345678901234567890123456789012345678901234"),
|
||||
Some(
|
||||
"/test/wasmtime_0x1234567890123456789012345678901234567890123456789012345678901234"
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn artifacts_removes_cache_on_startup() {
|
||||
let fake_cache_path = async_std::task::block_on(async move { crate::worker_common::tmpfile("test-cache").await.unwrap() });
|
||||
let fake_cache_path = async_std::task::block_on(async move {
|
||||
crate::worker_common::tmpfile("test-cache").await.unwrap()
|
||||
});
|
||||
let fake_artifact_path = {
|
||||
let mut p = fake_cache_path.clone();
|
||||
p.push("wasmtime_0x1234567890123456789012345678901234567890123456789012345678901234");
|
||||
|
||||
@@ -23,5 +23,5 @@
|
||||
mod queue;
|
||||
mod worker;
|
||||
|
||||
pub use queue::{ToQueue, start};
|
||||
pub use queue::{start, ToQueue};
|
||||
pub use worker::worker_entrypoint;
|
||||
|
||||
@@ -16,31 +16,27 @@
|
||||
|
||||
//! A queue that handles requests for PVF execution.
|
||||
|
||||
use crate::{
|
||||
worker_common::{IdleWorker, WorkerHandle},
|
||||
host::ResultSender,
|
||||
LOG_TARGET, InvalidCandidate, ValidationError,
|
||||
};
|
||||
use super::worker::Outcome;
|
||||
use std::{collections::VecDeque, fmt, time::Duration};
|
||||
use crate::{
|
||||
host::ResultSender,
|
||||
worker_common::{IdleWorker, WorkerHandle},
|
||||
InvalidCandidate, ValidationError, LOG_TARGET,
|
||||
};
|
||||
use async_std::path::PathBuf;
|
||||
use futures::{
|
||||
Future, FutureExt,
|
||||
channel::mpsc,
|
||||
future::BoxFuture,
|
||||
stream::{FuturesUnordered, StreamExt as _},
|
||||
Future, FutureExt,
|
||||
};
|
||||
use async_std::path::PathBuf;
|
||||
use slotmap::HopSlotMap;
|
||||
use std::{collections::VecDeque, fmt, time::Duration};
|
||||
|
||||
slotmap::new_key_type! { struct Worker; }
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ToQueue {
|
||||
Enqueue {
|
||||
artifact_path: PathBuf,
|
||||
params: Vec<u8>,
|
||||
result_tx: ResultSender,
|
||||
},
|
||||
Enqueue { artifact_path: PathBuf, params: Vec<u8>, result_tx: ResultSender },
|
||||
}
|
||||
|
||||
struct ExecuteJob {
|
||||
@@ -86,11 +82,7 @@ impl Workers {
|
||||
///
|
||||
/// Returns `None` if either worker is not recognized or idle token is absent.
|
||||
fn claim_idle(&mut self, worker: Worker) -> Option<IdleWorker> {
|
||||
self
|
||||
.running
|
||||
.get_mut(worker)?
|
||||
.idle
|
||||
.take()
|
||||
self.running.get_mut(worker)?.idle.take()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,17 +159,9 @@ async fn purge_dead(workers: &mut Workers) {
|
||||
}
|
||||
|
||||
fn handle_to_queue(queue: &mut Queue, to_queue: ToQueue) {
|
||||
let ToQueue::Enqueue {
|
||||
artifact_path,
|
||||
params,
|
||||
result_tx,
|
||||
} = to_queue;
|
||||
let ToQueue::Enqueue { artifact_path, params, result_tx } = to_queue;
|
||||
|
||||
let job = ExecuteJob {
|
||||
artifact_path,
|
||||
params,
|
||||
result_tx,
|
||||
};
|
||||
let job = ExecuteJob { artifact_path, params, result_tx };
|
||||
|
||||
if let Some(available) = queue.workers.find_available() {
|
||||
assign(queue, available, job);
|
||||
@@ -194,18 +178,15 @@ async fn handle_mux(queue: &mut Queue, event: QueueEvent) {
|
||||
QueueEvent::Spawn((idle, handle)) => {
|
||||
queue.workers.spawn_inflight -= 1;
|
||||
|
||||
let worker = queue.workers.running.insert(WorkerData {
|
||||
idle: Some(idle),
|
||||
handle,
|
||||
});
|
||||
let worker = queue.workers.running.insert(WorkerData { idle: Some(idle), handle });
|
||||
|
||||
if let Some(job) = queue.queue.pop_front() {
|
||||
assign(queue, worker, job);
|
||||
}
|
||||
}
|
||||
},
|
||||
QueueEvent::StartWork(worker, outcome, result_tx) => {
|
||||
handle_job_finish(queue, worker, outcome, result_tx);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,38 +194,22 @@ async fn handle_mux(queue: &mut Queue, event: QueueEvent) {
|
||||
/// worker. Otherwise, puts back into the available workers list.
|
||||
fn handle_job_finish(queue: &mut Queue, worker: Worker, outcome: Outcome, result_tx: ResultSender) {
|
||||
let (idle_worker, result) = match outcome {
|
||||
Outcome::Ok {
|
||||
result_descriptor,
|
||||
duration_ms,
|
||||
idle_worker,
|
||||
} => {
|
||||
Outcome::Ok { result_descriptor, duration_ms, idle_worker } => {
|
||||
// TODO: propagate the soft timeout
|
||||
drop(duration_ms);
|
||||
|
||||
(Some(idle_worker), Ok(result_descriptor))
|
||||
}
|
||||
},
|
||||
Outcome::InvalidCandidate { err, idle_worker } => (
|
||||
Some(idle_worker),
|
||||
Err(ValidationError::InvalidCandidate(
|
||||
InvalidCandidate::WorkerReportedError(err),
|
||||
)),
|
||||
),
|
||||
Outcome::InternalError { err, idle_worker } => (
|
||||
Some(idle_worker),
|
||||
Err(ValidationError::InternalError(err)),
|
||||
),
|
||||
Outcome::HardTimeout => (
|
||||
None,
|
||||
Err(ValidationError::InvalidCandidate(
|
||||
InvalidCandidate::HardTimeout,
|
||||
)),
|
||||
),
|
||||
Outcome::IoErr => (
|
||||
None,
|
||||
Err(ValidationError::InvalidCandidate(
|
||||
InvalidCandidate::AmbigiousWorkerDeath,
|
||||
)),
|
||||
Err(ValidationError::InvalidCandidate(InvalidCandidate::WorkerReportedError(err))),
|
||||
),
|
||||
Outcome::InternalError { err, idle_worker } =>
|
||||
(Some(idle_worker), Err(ValidationError::InternalError(err))),
|
||||
Outcome::HardTimeout =>
|
||||
(None, Err(ValidationError::InvalidCandidate(InvalidCandidate::HardTimeout))),
|
||||
Outcome::IoErr =>
|
||||
(None, Err(ValidationError::InvalidCandidate(InvalidCandidate::AmbigiousWorkerDeath))),
|
||||
};
|
||||
|
||||
// First we send the result. It may fail due the other end of the channel being dropped, that's
|
||||
@@ -293,15 +258,11 @@ async fn spawn_worker_task(program_path: PathBuf, spawn_timeout: Duration) -> Qu
|
||||
match super::worker::spawn(&program_path, spawn_timeout).await {
|
||||
Ok((idle, handle)) => break QueueEvent::Spawn((idle, handle)),
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
"failed to spawn an execute worker: {:?}",
|
||||
err,
|
||||
);
|
||||
tracing::warn!(target: LOG_TARGET, "failed to spawn an execute worker: {:?}", err,);
|
||||
|
||||
// Assume that the failure intermittent and retry after a delay.
|
||||
Delay::new(Duration::from_secs(3)).await;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -310,14 +271,11 @@ async fn spawn_worker_task(program_path: PathBuf, spawn_timeout: Duration) -> Qu
|
||||
///
|
||||
/// The worker must be running and idle.
|
||||
fn assign(queue: &mut Queue, worker: Worker, job: ExecuteJob) {
|
||||
let idle = queue
|
||||
.workers
|
||||
.claim_idle(worker)
|
||||
.expect(
|
||||
"this caller must supply a worker which is idle and running;
|
||||
let idle = queue.workers.claim_idle(worker).expect(
|
||||
"this caller must supply a worker which is idle and running;
|
||||
thus claim_idle cannot return None;
|
||||
qed."
|
||||
);
|
||||
qed.",
|
||||
);
|
||||
queue.mux.push(
|
||||
async move {
|
||||
let outcome = super::worker::start_work(idle, job.artifact_path, job.params).await;
|
||||
@@ -333,12 +291,6 @@ pub fn start(
|
||||
spawn_timeout: Duration,
|
||||
) -> (mpsc::Sender<ToQueue>, impl Future<Output = ()>) {
|
||||
let (to_queue_tx, to_queue_rx) = mpsc::channel(20);
|
||||
let run = Queue::new(
|
||||
program_path,
|
||||
worker_capacity,
|
||||
spawn_timeout,
|
||||
to_queue_rx,
|
||||
)
|
||||
.run();
|
||||
let run = Queue::new(program_path, worker_capacity, spawn_timeout, to_queue_rx).run();
|
||||
(to_queue_tx, run)
|
||||
}
|
||||
|
||||
@@ -16,14 +16,13 @@
|
||||
|
||||
use crate::{
|
||||
artifacts::Artifact,
|
||||
LOG_TARGET,
|
||||
executor_intf::TaskExecutor,
|
||||
worker_common::{
|
||||
IdleWorker, SpawnErr, WorkerHandle, bytes_to_path, framed_recv, framed_send, path_to_bytes,
|
||||
spawn_with_program_path, worker_event_loop,
|
||||
bytes_to_path, framed_recv, framed_send, path_to_bytes, spawn_with_program_path,
|
||||
worker_event_loop, IdleWorker, SpawnErr, WorkerHandle,
|
||||
},
|
||||
LOG_TARGET,
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
use async_std::{
|
||||
io,
|
||||
os::unix::net::UnixStream,
|
||||
@@ -31,8 +30,9 @@ use async_std::{
|
||||
};
|
||||
use futures::FutureExt;
|
||||
use futures_timer::Delay;
|
||||
use parity_scale_codec::{Decode, Encode};
|
||||
use polkadot_parachain::primitives::ValidationResult;
|
||||
use parity_scale_codec::{Encode, Decode};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
const EXECUTION_TIMEOUT: Duration = Duration::from_secs(3);
|
||||
|
||||
@@ -43,36 +43,20 @@ pub async fn spawn(
|
||||
program_path: &Path,
|
||||
spawn_timeout: Duration,
|
||||
) -> Result<(IdleWorker, WorkerHandle), SpawnErr> {
|
||||
spawn_with_program_path(
|
||||
"execute",
|
||||
program_path,
|
||||
&["execute-worker"],
|
||||
spawn_timeout,
|
||||
)
|
||||
.await
|
||||
spawn_with_program_path("execute", program_path, &["execute-worker"], spawn_timeout).await
|
||||
}
|
||||
|
||||
/// Outcome of PVF execution.
|
||||
pub enum Outcome {
|
||||
/// PVF execution completed successfully and the result is returned. The worker is ready for
|
||||
/// another job.
|
||||
Ok {
|
||||
result_descriptor: ValidationResult,
|
||||
duration_ms: u64,
|
||||
idle_worker: IdleWorker,
|
||||
},
|
||||
Ok { result_descriptor: ValidationResult, duration_ms: u64, idle_worker: IdleWorker },
|
||||
/// The candidate validation failed. It may be for example because the preparation process
|
||||
/// produced an error or the wasm execution triggered a trap.
|
||||
InvalidCandidate {
|
||||
err: String,
|
||||
idle_worker: IdleWorker,
|
||||
},
|
||||
InvalidCandidate { err: String, idle_worker: IdleWorker },
|
||||
/// An internal error happened during the validation. Such an error is most likely related to
|
||||
/// some transient glitch.
|
||||
InternalError {
|
||||
err: String,
|
||||
idle_worker: IdleWorker,
|
||||
},
|
||||
InternalError { err: String, idle_worker: IdleWorker },
|
||||
/// The execution time exceeded the hard limit. The worker is terminated.
|
||||
HardTimeout,
|
||||
/// An I/O error happened during communication with the worker. This may mean that the worker
|
||||
@@ -97,7 +81,7 @@ pub async fn start_work(
|
||||
);
|
||||
|
||||
if send_request(&mut stream, &artifact_path, &validation_params).await.is_err() {
|
||||
return Outcome::IoErr;
|
||||
return Outcome::IoErr
|
||||
}
|
||||
|
||||
let response = futures::select! {
|
||||
@@ -111,22 +95,12 @@ pub async fn start_work(
|
||||
};
|
||||
|
||||
match response {
|
||||
Response::Ok {
|
||||
result_descriptor,
|
||||
duration_ms,
|
||||
} => Outcome::Ok {
|
||||
result_descriptor,
|
||||
duration_ms,
|
||||
idle_worker: IdleWorker { stream, pid },
|
||||
},
|
||||
Response::InvalidCandidate(err) => Outcome::InvalidCandidate {
|
||||
err,
|
||||
idle_worker: IdleWorker { stream, pid },
|
||||
},
|
||||
Response::InternalError(err) => Outcome::InternalError {
|
||||
err,
|
||||
idle_worker: IdleWorker { stream, pid },
|
||||
},
|
||||
Response::Ok { result_descriptor, duration_ms } =>
|
||||
Outcome::Ok { result_descriptor, duration_ms, idle_worker: IdleWorker { stream, pid } },
|
||||
Response::InvalidCandidate(err) =>
|
||||
Outcome::InvalidCandidate { err, idle_worker: IdleWorker { stream, pid } },
|
||||
Response::InternalError(err) =>
|
||||
Outcome::InternalError { err, idle_worker: IdleWorker { stream, pid } },
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,10 +141,7 @@ async fn recv_response(stream: &mut UnixStream) -> io::Result<Response> {
|
||||
|
||||
#[derive(Encode, Decode)]
|
||||
enum Response {
|
||||
Ok {
|
||||
result_descriptor: ValidationResult,
|
||||
duration_ms: u64,
|
||||
},
|
||||
Ok { result_descriptor: ValidationResult, duration_ms: u64 },
|
||||
InvalidCandidate(String),
|
||||
InternalError(String),
|
||||
}
|
||||
@@ -190,10 +161,7 @@ impl Response {
|
||||
pub fn worker_entrypoint(socket_path: &str) {
|
||||
worker_event_loop("execute", socket_path, |mut stream| async move {
|
||||
let executor = TaskExecutor::new().map_err(|e| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("cannot create task executor: {}", e),
|
||||
)
|
||||
io::Error::new(io::ErrorKind::Other, format!("cannot create task executor: {}", e))
|
||||
})?;
|
||||
loop {
|
||||
let (artifact_path, params) = recv_request(&mut stream).await?;
|
||||
@@ -215,13 +183,12 @@ async fn validate_using_artifact(
|
||||
spawner: &TaskExecutor,
|
||||
) -> Response {
|
||||
let artifact_bytes = match async_std::fs::read(artifact_path).await {
|
||||
Err(e) => {
|
||||
Err(e) =>
|
||||
return Response::InternalError(format!(
|
||||
"failed to read the artifact at {}: {:?}",
|
||||
artifact_path.display(),
|
||||
e,
|
||||
))
|
||||
}
|
||||
)),
|
||||
Ok(b) => b,
|
||||
};
|
||||
|
||||
@@ -231,47 +198,31 @@ async fn validate_using_artifact(
|
||||
};
|
||||
|
||||
let compiled_artifact = match &artifact {
|
||||
Artifact::PrevalidationErr(msg) => {
|
||||
return Response::format_invalid("prevalidation", msg);
|
||||
}
|
||||
Artifact::PreparationErr(msg) => {
|
||||
return Response::format_invalid("preparation", msg);
|
||||
}
|
||||
Artifact::DidntMakeIt => {
|
||||
return Response::format_invalid("preparation timeout", "");
|
||||
}
|
||||
Artifact::PrevalidationErr(msg) => return Response::format_invalid("prevalidation", msg),
|
||||
Artifact::PreparationErr(msg) => return Response::format_invalid("preparation", msg),
|
||||
Artifact::DidntMakeIt => return Response::format_invalid("preparation timeout", ""),
|
||||
|
||||
Artifact::Compiled { compiled_artifact } => compiled_artifact,
|
||||
};
|
||||
|
||||
let validation_started_at = Instant::now();
|
||||
let descriptor_bytes =
|
||||
match unsafe {
|
||||
// SAFETY: this should be safe since the compiled artifact passed here comes from the
|
||||
// file created by the prepare workers. These files are obtained by calling
|
||||
// [`executor_intf::prepare`].
|
||||
crate::executor_intf::execute(compiled_artifact, params, spawner.clone())
|
||||
} {
|
||||
Err(err) => {
|
||||
return Response::format_invalid("execute", &err.to_string());
|
||||
}
|
||||
Ok(d) => d,
|
||||
};
|
||||
let descriptor_bytes = match unsafe {
|
||||
// SAFETY: this should be safe since the compiled artifact passed here comes from the
|
||||
// file created by the prepare workers. These files are obtained by calling
|
||||
// [`executor_intf::prepare`].
|
||||
crate::executor_intf::execute(compiled_artifact, params, spawner.clone())
|
||||
} {
|
||||
Err(err) => return Response::format_invalid("execute", &err.to_string()),
|
||||
Ok(d) => d,
|
||||
};
|
||||
|
||||
let duration_ms = validation_started_at.elapsed().as_millis() as u64;
|
||||
|
||||
let result_descriptor = match ValidationResult::decode(&mut &descriptor_bytes[..]) {
|
||||
Err(err) => {
|
||||
return Response::InvalidCandidate(format!(
|
||||
"validation result decoding failed: {}",
|
||||
err
|
||||
))
|
||||
}
|
||||
Err(err) =>
|
||||
return Response::InvalidCandidate(format!("validation result decoding failed: {}", err)),
|
||||
Ok(r) => r,
|
||||
};
|
||||
|
||||
Response::Ok {
|
||||
result_descriptor,
|
||||
duration_ms,
|
||||
}
|
||||
Response::Ok { result_descriptor, duration_ms }
|
||||
}
|
||||
|
||||
@@ -16,16 +16,14 @@
|
||||
|
||||
//! Interface to the Substrate Executor
|
||||
|
||||
use std::any::{TypeId, Any};
|
||||
use sc_executor_common::{
|
||||
runtime_blob::RuntimeBlob,
|
||||
wasm_runtime::{InvokeMethod, WasmModule as _},
|
||||
};
|
||||
use sc_executor_wasmtime::{Config, Semantics, DeterministicStackLimit};
|
||||
use sp_core::{
|
||||
storage::{ChildInfo, TrackedStorageKey},
|
||||
};
|
||||
use sc_executor_wasmtime::{Config, DeterministicStackLimit, Semantics};
|
||||
use sp_core::storage::{ChildInfo, TrackedStorageKey};
|
||||
use sp_wasm_interface::HostFunctions as _;
|
||||
use std::any::{Any, TypeId};
|
||||
|
||||
const CONFIG: Config = Config {
|
||||
// TODO: Make sure we don't use more than 1GB: https://github.com/paritytech/polkadot/issues/699
|
||||
@@ -95,9 +93,7 @@ pub unsafe fn execute(
|
||||
CONFIG,
|
||||
HostFunctions::host_functions(),
|
||||
)?;
|
||||
runtime
|
||||
.new_instance()?
|
||||
.call(InvokeMethod::Export("validate_block"), params)
|
||||
runtime.new_instance()?.call(InvokeMethod::Export("validate_block"), params)
|
||||
})?
|
||||
}
|
||||
|
||||
|
||||
@@ -21,23 +21,20 @@
|
||||
//! [`ValidationHost`], that allows communication with that event-loop.
|
||||
|
||||
use crate::{
|
||||
Priority, Pvf, ValidationError,
|
||||
artifacts::{Artifacts, ArtifactState, ArtifactId},
|
||||
execute, prepare,
|
||||
artifacts::{ArtifactId, ArtifactState, Artifacts},
|
||||
execute, prepare, Priority, Pvf, ValidationError,
|
||||
};
|
||||
use always_assert::never;
|
||||
use async_std::path::{Path, PathBuf};
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
Future, FutureExt, SinkExt, StreamExt,
|
||||
};
|
||||
use polkadot_parachain::primitives::ValidationResult;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
use always_assert::never;
|
||||
use async_std::{
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use polkadot_parachain::primitives::ValidationResult;
|
||||
use futures::{
|
||||
Future, FutureExt, SinkExt, StreamExt,
|
||||
channel::{mpsc, oneshot},
|
||||
};
|
||||
|
||||
/// An alias to not spell the type for the oneshot sender for the PVF execution result.
|
||||
pub(crate) type ResultSender = oneshot::Sender<Result<ValidationResult, ValidationError>>;
|
||||
@@ -64,12 +61,7 @@ impl ValidationHost {
|
||||
result_tx: ResultSender,
|
||||
) -> Result<(), String> {
|
||||
self.to_host_tx
|
||||
.send(ToHost::ExecutePvf {
|
||||
pvf,
|
||||
params,
|
||||
priority,
|
||||
result_tx,
|
||||
})
|
||||
.send(ToHost::ExecutePvf { pvf, params, priority, result_tx })
|
||||
.await
|
||||
.map_err(|_| "the inner loop hung up".to_string())
|
||||
}
|
||||
@@ -89,15 +81,8 @@ impl ValidationHost {
|
||||
}
|
||||
|
||||
enum ToHost {
|
||||
ExecutePvf {
|
||||
pvf: Pvf,
|
||||
params: Vec<u8>,
|
||||
priority: Priority,
|
||||
result_tx: ResultSender,
|
||||
},
|
||||
HeadsUp {
|
||||
active_pvfs: Vec<Pvf>,
|
||||
},
|
||||
ExecutePvf { pvf: Pvf, params: Vec<u8>, priority: Priority, result_tx: ResultSender },
|
||||
HeadsUp { active_pvfs: Vec<Pvf> },
|
||||
}
|
||||
|
||||
/// Configuration for the validation host.
|
||||
@@ -180,12 +165,7 @@ pub fn start(config: Config) -> (ValidationHost, impl Future<Output = ()>) {
|
||||
let run = async move {
|
||||
let artifacts = Artifacts::new(&config.cache_path).await;
|
||||
|
||||
futures::pin_mut!(
|
||||
run_prepare_queue,
|
||||
run_prepare_pool,
|
||||
run_execute_queue,
|
||||
run_sweeper
|
||||
);
|
||||
futures::pin_mut!(run_prepare_queue, run_prepare_pool, run_execute_queue, run_sweeper);
|
||||
|
||||
run(
|
||||
Inner {
|
||||
@@ -375,12 +355,7 @@ async fn handle_to_host(
|
||||
to_host: ToHost,
|
||||
) -> Result<(), Fatal> {
|
||||
match to_host {
|
||||
ToHost::ExecutePvf {
|
||||
pvf,
|
||||
params,
|
||||
priority,
|
||||
result_tx,
|
||||
} => {
|
||||
ToHost::ExecutePvf { pvf, params, priority, result_tx } => {
|
||||
handle_execute_pvf(
|
||||
cache_path,
|
||||
artifacts,
|
||||
@@ -393,10 +368,10 @@ async fn handle_to_host(
|
||||
result_tx,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
},
|
||||
ToHost::HeadsUp { active_pvfs } => {
|
||||
handle_heads_up(artifacts, prepare_queue, active_pvfs).await?;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -417,9 +392,7 @@ async fn handle_execute_pvf(
|
||||
|
||||
if let Some(state) = artifacts.artifact_state_mut(&artifact_id) {
|
||||
match state {
|
||||
ArtifactState::Prepared {
|
||||
ref mut last_time_needed,
|
||||
} => {
|
||||
ArtifactState::Prepared { ref mut last_time_needed } => {
|
||||
*last_time_needed = SystemTime::now();
|
||||
|
||||
send_execute(
|
||||
@@ -431,19 +404,16 @@ async fn handle_execute_pvf(
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
},
|
||||
ArtifactState::Preparing => {
|
||||
send_prepare(
|
||||
prepare_queue,
|
||||
prepare::ToQueue::Amend {
|
||||
priority,
|
||||
artifact_id: artifact_id.clone(),
|
||||
},
|
||||
prepare::ToQueue::Amend { priority, artifact_id: artifact_id.clone() },
|
||||
)
|
||||
.await?;
|
||||
|
||||
awaiting_prepare.add(artifact_id, params, result_tx);
|
||||
}
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// Artifact is unknown: register it and enqueue a job with the corresponding priority and
|
||||
@@ -454,7 +424,7 @@ async fn handle_execute_pvf(
|
||||
awaiting_prepare.add(artifact_id, params, result_tx);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
async fn handle_heads_up(
|
||||
@@ -468,15 +438,13 @@ async fn handle_heads_up(
|
||||
let artifact_id = active_pvf.as_artifact_id();
|
||||
if let Some(state) = artifacts.artifact_state_mut(&artifact_id) {
|
||||
match state {
|
||||
ArtifactState::Prepared {
|
||||
last_time_needed, ..
|
||||
} => {
|
||||
ArtifactState::Prepared { last_time_needed, .. } => {
|
||||
*last_time_needed = now;
|
||||
}
|
||||
},
|
||||
ArtifactState::Preparing => {
|
||||
// Already preparing. We don't need to send a priority amend either because
|
||||
// it can't get any lower than the background.
|
||||
}
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// The artifact is unknown: register it and put a background job into the prepare queue.
|
||||
@@ -484,10 +452,7 @@ async fn handle_heads_up(
|
||||
|
||||
send_prepare(
|
||||
prepare_queue,
|
||||
prepare::ToQueue::Enqueue {
|
||||
priority: Priority::Background,
|
||||
pvf: active_pvf,
|
||||
},
|
||||
prepare::ToQueue::Enqueue { priority: Priority::Background, pvf: active_pvf },
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
@@ -512,8 +477,8 @@ async fn handle_prepare_done(
|
||||
// thus the artifact cannot be unknown, only preparing;
|
||||
// qed.
|
||||
never!("an unknown artifact was prepared: {:?}", artifact_id);
|
||||
return Ok(());
|
||||
}
|
||||
return Ok(())
|
||||
},
|
||||
Some(ArtifactState::Prepared { .. }) => {
|
||||
// before sending request to prepare, the artifact is inserted with `preparing` state;
|
||||
// the requests are deduplicated for the same artifact id;
|
||||
@@ -521,8 +486,8 @@ async fn handle_prepare_done(
|
||||
// thus the artifact cannot be prepared, only preparing;
|
||||
// qed.
|
||||
never!("the artifact is already prepared: {:?}", artifact_id);
|
||||
return Ok(());
|
||||
}
|
||||
return Ok(())
|
||||
},
|
||||
Some(state @ ArtifactState::Preparing) => state,
|
||||
};
|
||||
|
||||
@@ -534,24 +499,18 @@ async fn handle_prepare_done(
|
||||
if result_tx.is_canceled() {
|
||||
// Preparation could've taken quite a bit of time and the requester may be not interested
|
||||
// in execution anymore, in which case we just skip the request.
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
|
||||
send_execute(
|
||||
execute_queue,
|
||||
execute::ToQueue::Enqueue {
|
||||
artifact_path: artifact_path.clone(),
|
||||
params,
|
||||
result_tx,
|
||||
},
|
||||
execute::ToQueue::Enqueue { artifact_path: artifact_path.clone(), params, result_tx },
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Now consider the artifact prepared.
|
||||
*state = ArtifactState::Prepared {
|
||||
last_time_needed: SystemTime::now(),
|
||||
};
|
||||
*state = ArtifactState::Prepared { last_time_needed: SystemTime::now() };
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -592,7 +551,7 @@ async fn sweeper_task(mut sweeper_rx: mpsc::Receiver<PathBuf>) {
|
||||
None => break,
|
||||
Some(condemned) => {
|
||||
let _ = async_std::fs::remove_file(condemned).await;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -611,8 +570,8 @@ fn pulse_every(interval: std::time::Duration) -> impl futures::Stream<Item = ()>
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use futures::future::BoxFuture;
|
||||
use assert_matches::assert_matches;
|
||||
use futures::future::BoxFuture;
|
||||
|
||||
#[async_std::test]
|
||||
async fn pulse_test() {
|
||||
@@ -634,9 +593,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn artifact_path(descriminator: u32) -> PathBuf {
|
||||
artifact_id(descriminator)
|
||||
.path(&PathBuf::from(std::env::temp_dir()))
|
||||
.to_owned()
|
||||
artifact_id(descriminator).path(&PathBuf::from(std::env::temp_dir())).to_owned()
|
||||
}
|
||||
|
||||
struct Builder {
|
||||
@@ -673,13 +630,7 @@ mod tests {
|
||||
}
|
||||
|
||||
impl Test {
|
||||
fn new(
|
||||
Builder {
|
||||
cleanup_pulse_interval,
|
||||
artifact_ttl,
|
||||
artifacts,
|
||||
}: Builder,
|
||||
) -> Self {
|
||||
fn new(Builder { cleanup_pulse_interval, artifact_ttl, artifacts }: Builder) -> Self {
|
||||
let cache_path = PathBuf::from(std::env::temp_dir());
|
||||
|
||||
let (to_host_tx, to_host_rx) = mpsc::channel(10);
|
||||
@@ -727,20 +678,14 @@ mod tests {
|
||||
|
||||
async fn poll_and_recv_to_prepare_queue(&mut self) -> prepare::ToQueue {
|
||||
let to_prepare_queue_rx = &mut self.to_prepare_queue_rx;
|
||||
run_until(
|
||||
&mut self.run,
|
||||
async { to_prepare_queue_rx.next().await.unwrap() }.boxed(),
|
||||
)
|
||||
.await
|
||||
run_until(&mut self.run, async { to_prepare_queue_rx.next().await.unwrap() }.boxed())
|
||||
.await
|
||||
}
|
||||
|
||||
async fn poll_and_recv_to_execute_queue(&mut self) -> execute::ToQueue {
|
||||
let to_execute_queue_rx = &mut self.to_execute_queue_rx;
|
||||
run_until(
|
||||
&mut self.run,
|
||||
async { to_execute_queue_rx.next().await.unwrap() }.boxed(),
|
||||
)
|
||||
.await
|
||||
run_until(&mut self.run, async { to_execute_queue_rx.next().await.unwrap() }.boxed())
|
||||
.await
|
||||
}
|
||||
|
||||
async fn poll_ensure_to_execute_queue_is_empty(&mut self) {
|
||||
@@ -798,7 +743,7 @@ mod tests {
|
||||
}
|
||||
|
||||
if let Poll::Ready(r) = futures::poll!(&mut *fut) {
|
||||
break r;
|
||||
break r
|
||||
}
|
||||
|
||||
if futures::poll!(&mut *task).is_ready() {
|
||||
@@ -831,9 +776,7 @@ mod tests {
|
||||
let mut test = builder.build();
|
||||
let mut host = test.host_handle();
|
||||
|
||||
host.heads_up(vec![Pvf::from_discriminator(1)])
|
||||
.await
|
||||
.unwrap();
|
||||
host.heads_up(vec![Pvf::from_discriminator(1)]).await.unwrap();
|
||||
|
||||
let to_sweeper_rx = &mut test.to_sweeper_rx;
|
||||
run_until(
|
||||
@@ -847,9 +790,7 @@ mod tests {
|
||||
|
||||
// Extend TTL for the first artifact and make sure we don't receive another file removal
|
||||
// request.
|
||||
host.heads_up(vec![Pvf::from_discriminator(1)])
|
||||
.await
|
||||
.unwrap();
|
||||
host.heads_up(vec![Pvf::from_discriminator(1)]).await.unwrap();
|
||||
test.poll_ensure_to_sweeper_is_empty().await;
|
||||
}
|
||||
|
||||
@@ -858,9 +799,7 @@ mod tests {
|
||||
let mut test = Builder::default().build();
|
||||
let mut host = test.host_handle();
|
||||
|
||||
host.heads_up(vec![Pvf::from_discriminator(1)])
|
||||
.await
|
||||
.unwrap();
|
||||
host.heads_up(vec![Pvf::from_discriminator(1)]).await.unwrap();
|
||||
|
||||
// Run until we receive a prepare request.
|
||||
let prepare_q_rx = &mut test.to_prepare_queue_rx;
|
||||
@@ -877,22 +816,14 @@ mod tests {
|
||||
.await;
|
||||
|
||||
let (result_tx, _result_rx) = oneshot::channel();
|
||||
host.execute_pvf(
|
||||
Pvf::from_discriminator(1),
|
||||
vec![],
|
||||
Priority::Critical,
|
||||
result_tx,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
host.execute_pvf(Pvf::from_discriminator(1), vec![], Priority::Critical, result_tx)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
run_until(
|
||||
&mut test.run,
|
||||
async {
|
||||
assert_matches!(
|
||||
prepare_q_rx.next().await.unwrap(),
|
||||
prepare::ToQueue::Amend { .. }
|
||||
);
|
||||
assert_matches!(prepare_q_rx.next().await.unwrap(), prepare::ToQueue::Amend { .. });
|
||||
}
|
||||
.boxed(),
|
||||
)
|
||||
@@ -907,14 +838,9 @@ mod tests {
|
||||
let mut host = test.host_handle();
|
||||
|
||||
let (result_tx, result_rx_pvf_1_1) = oneshot::channel();
|
||||
host.execute_pvf(
|
||||
Pvf::from_discriminator(1),
|
||||
b"pvf1".to_vec(),
|
||||
Priority::Normal,
|
||||
result_tx,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
host.execute_pvf(Pvf::from_discriminator(1), b"pvf1".to_vec(), Priority::Normal, result_tx)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let (result_tx, result_rx_pvf_1_2) = oneshot::channel();
|
||||
host.execute_pvf(
|
||||
@@ -927,14 +853,9 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
let (result_tx, result_rx_pvf_2) = oneshot::channel();
|
||||
host.execute_pvf(
|
||||
Pvf::from_discriminator(2),
|
||||
b"pvf2".to_vec(),
|
||||
Priority::Normal,
|
||||
result_tx,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
host.execute_pvf(Pvf::from_discriminator(2), b"pvf2".to_vec(), Priority::Normal, result_tx)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_matches!(
|
||||
test.poll_and_recv_to_prepare_queue().await,
|
||||
@@ -972,39 +893,27 @@ mod tests {
|
||||
);
|
||||
|
||||
result_tx_pvf_1_1
|
||||
.send(Err(ValidationError::InvalidCandidate(
|
||||
InvalidCandidate::AmbigiousWorkerDeath,
|
||||
)))
|
||||
.send(Err(ValidationError::InvalidCandidate(InvalidCandidate::AmbigiousWorkerDeath)))
|
||||
.unwrap();
|
||||
assert_matches!(
|
||||
result_rx_pvf_1_1.now_or_never().unwrap().unwrap(),
|
||||
Err(ValidationError::InvalidCandidate(
|
||||
InvalidCandidate::AmbigiousWorkerDeath,
|
||||
))
|
||||
Err(ValidationError::InvalidCandidate(InvalidCandidate::AmbigiousWorkerDeath,))
|
||||
);
|
||||
|
||||
result_tx_pvf_1_2
|
||||
.send(Err(ValidationError::InvalidCandidate(
|
||||
InvalidCandidate::AmbigiousWorkerDeath,
|
||||
)))
|
||||
.send(Err(ValidationError::InvalidCandidate(InvalidCandidate::AmbigiousWorkerDeath)))
|
||||
.unwrap();
|
||||
assert_matches!(
|
||||
result_rx_pvf_1_2.now_or_never().unwrap().unwrap(),
|
||||
Err(ValidationError::InvalidCandidate(
|
||||
InvalidCandidate::AmbigiousWorkerDeath,
|
||||
))
|
||||
Err(ValidationError::InvalidCandidate(InvalidCandidate::AmbigiousWorkerDeath,))
|
||||
);
|
||||
|
||||
result_tx_pvf_2
|
||||
.send(Err(ValidationError::InvalidCandidate(
|
||||
InvalidCandidate::AmbigiousWorkerDeath,
|
||||
)))
|
||||
.send(Err(ValidationError::InvalidCandidate(InvalidCandidate::AmbigiousWorkerDeath)))
|
||||
.unwrap();
|
||||
assert_matches!(
|
||||
result_rx_pvf_2.now_or_never().unwrap().unwrap(),
|
||||
Err(ValidationError::InvalidCandidate(
|
||||
InvalidCandidate::AmbigiousWorkerDeath,
|
||||
))
|
||||
Err(ValidationError::InvalidCandidate(InvalidCandidate::AmbigiousWorkerDeath,))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1014,14 +923,9 @@ mod tests {
|
||||
let mut host = test.host_handle();
|
||||
|
||||
let (result_tx, result_rx) = oneshot::channel();
|
||||
host.execute_pvf(
|
||||
Pvf::from_discriminator(1),
|
||||
b"pvf1".to_vec(),
|
||||
Priority::Normal,
|
||||
result_tx,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
host.execute_pvf(Pvf::from_discriminator(1), b"pvf1".to_vec(), Priority::Normal, result_tx)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_matches!(
|
||||
test.poll_and_recv_to_prepare_queue().await,
|
||||
|
||||
@@ -91,7 +91,7 @@ pub mod testing;
|
||||
#[doc(hidden)]
|
||||
pub use sp_tracing;
|
||||
|
||||
pub use error::{ValidationError, InvalidCandidate};
|
||||
pub use error::{InvalidCandidate, ValidationError};
|
||||
pub use priority::Priority;
|
||||
pub use pvf::Pvf;
|
||||
|
||||
|
||||
@@ -26,6 +26,6 @@ mod pool;
|
||||
mod queue;
|
||||
mod worker;
|
||||
|
||||
pub use queue::{ToQueue, FromQueue, start as start_queue};
|
||||
pub use pool::start as start_pool;
|
||||
pub use queue::{start as start_queue, FromQueue, ToQueue};
|
||||
pub use worker::worker_entrypoint;
|
||||
|
||||
@@ -14,21 +14,19 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::worker::{self, Outcome};
|
||||
use crate::{
|
||||
worker_common::{IdleWorker, WorkerHandle},
|
||||
LOG_TARGET,
|
||||
};
|
||||
use super::{
|
||||
worker::{self, Outcome},
|
||||
};
|
||||
use std::{fmt, sync::Arc, task::Poll, time::Duration};
|
||||
use always_assert::never;
|
||||
use assert_matches::assert_matches;
|
||||
use async_std::path::{Path, PathBuf};
|
||||
use futures::{
|
||||
Future, FutureExt, StreamExt, channel::mpsc, future::BoxFuture, stream::FuturesUnordered,
|
||||
channel::mpsc, future::BoxFuture, stream::FuturesUnordered, Future, FutureExt, StreamExt,
|
||||
};
|
||||
use slotmap::HopSlotMap;
|
||||
use assert_matches::assert_matches;
|
||||
use always_assert::never;
|
||||
use std::{fmt, sync::Arc, task::Poll, time::Duration};
|
||||
|
||||
slotmap::new_key_type! { pub struct Worker; }
|
||||
|
||||
@@ -170,7 +168,7 @@ async fn purge_dead(
|
||||
// The idle token is missing, meaning this worker is now occupied: skip it. This is
|
||||
// because the worker process is observed by the work task and should it reach the
|
||||
// deadline or be terminated it will be handled by the corresponding mux event.
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
|
||||
if let Poll::Ready(()) = futures::poll!(&mut data.handle) {
|
||||
@@ -197,13 +195,8 @@ fn handle_to_pool(
|
||||
match to_pool {
|
||||
ToPool::Spawn => {
|
||||
mux.push(spawn_worker_task(program_path.to_owned(), spawn_timeout).boxed());
|
||||
}
|
||||
ToPool::StartWork {
|
||||
worker,
|
||||
code,
|
||||
artifact_path,
|
||||
background_priority,
|
||||
} => {
|
||||
},
|
||||
ToPool::StartWork { worker, code, artifact_path, background_priority } => {
|
||||
if let Some(data) = spawned.get_mut(worker) {
|
||||
if let Some(idle) = data.idle.take() {
|
||||
mux.push(
|
||||
@@ -213,7 +206,7 @@ fn handle_to_pool(
|
||||
code,
|
||||
cache_path.to_owned(),
|
||||
artifact_path,
|
||||
background_priority
|
||||
background_priority,
|
||||
)
|
||||
.boxed(),
|
||||
);
|
||||
@@ -229,16 +222,15 @@ fn handle_to_pool(
|
||||
// That's a relatively normal situation since the queue may send `start_work` and
|
||||
// before receiving it the pool would report that the worker died.
|
||||
}
|
||||
}
|
||||
},
|
||||
ToPool::Kill(worker) => {
|
||||
// It may be absent if it were previously already removed by `purge_dead`.
|
||||
let _ = spawned.remove(worker);
|
||||
}
|
||||
ToPool::BumpPriority(worker) => {
|
||||
},
|
||||
ToPool::BumpPriority(worker) =>
|
||||
if let Some(data) = spawned.get(worker) {
|
||||
worker::bump_priority(&data.handle);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,15 +241,11 @@ async fn spawn_worker_task(program_path: PathBuf, spawn_timeout: Duration) -> Po
|
||||
match worker::spawn(&program_path, spawn_timeout).await {
|
||||
Ok((idle, handle)) => break PoolEvent::Spawn(idle, handle),
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
"failed to spawn a prepare worker: {:?}",
|
||||
err,
|
||||
);
|
||||
tracing::warn!(target: LOG_TARGET, "failed to spawn a prepare worker: {:?}", err,);
|
||||
|
||||
// Assume that the failure intermittent and retry after a delay.
|
||||
Delay::new(Duration::from_secs(3)).await;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -282,15 +270,12 @@ fn handle_mux(
|
||||
) -> Result<(), Fatal> {
|
||||
match event {
|
||||
PoolEvent::Spawn(idle, handle) => {
|
||||
let worker = spawned.insert(WorkerData {
|
||||
idle: Some(idle),
|
||||
handle,
|
||||
});
|
||||
let worker = spawned.insert(WorkerData { idle: Some(idle), handle });
|
||||
|
||||
reply(from_pool, FromPool::Spawned(worker))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
PoolEvent::StartWork(worker, outcome) => {
|
||||
match outcome {
|
||||
Outcome::Concluded(idle) => {
|
||||
@@ -298,8 +283,8 @@ fn handle_mux(
|
||||
None => {
|
||||
// Perhaps the worker was killed meanwhile and the result is no longer
|
||||
// relevant.
|
||||
return Ok(());
|
||||
}
|
||||
return Ok(())
|
||||
},
|
||||
Some(data) => data,
|
||||
};
|
||||
|
||||
@@ -311,23 +296,23 @@ fn handle_mux(
|
||||
reply(from_pool, FromPool::Concluded(worker, false))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
Outcome::Unreachable => {
|
||||
if spawned.remove(worker).is_some() {
|
||||
reply(from_pool, FromPool::Rip(worker))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
Outcome::DidntMakeIt => {
|
||||
if spawned.remove(worker).is_some() {
|
||||
reply(from_pool, FromPool::Concluded(worker, true))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,11 +325,7 @@ pub fn start(
|
||||
program_path: PathBuf,
|
||||
cache_path: PathBuf,
|
||||
spawn_timeout: Duration,
|
||||
) -> (
|
||||
mpsc::Sender<ToPool>,
|
||||
mpsc::UnboundedReceiver<FromPool>,
|
||||
impl Future<Output = ()>,
|
||||
) {
|
||||
) -> (mpsc::Sender<ToPool>, mpsc::UnboundedReceiver<FromPool>, impl Future<Output = ()>) {
|
||||
let (to_pool_tx, to_pool_rx) = mpsc::channel(10);
|
||||
let (from_pool_tx, from_pool_rx) = mpsc::unbounded();
|
||||
|
||||
|
||||
@@ -16,14 +16,12 @@
|
||||
|
||||
//! A queue that handles requests for PVF preparation.
|
||||
|
||||
use super::{
|
||||
pool::{self, Worker},
|
||||
};
|
||||
use crate::{LOG_TARGET, Priority, Pvf, artifacts::ArtifactId};
|
||||
use futures::{Future, SinkExt, channel::mpsc, stream::StreamExt as _};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use async_std::path::PathBuf;
|
||||
use super::pool::{self, Worker};
|
||||
use crate::{artifacts::ArtifactId, Priority, Pvf, LOG_TARGET};
|
||||
use always_assert::{always, never};
|
||||
use async_std::path::PathBuf;
|
||||
use futures::{channel::mpsc, stream::StreamExt as _, Future, SinkExt};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
/// A request to pool.
|
||||
#[derive(Debug)]
|
||||
@@ -35,10 +33,7 @@ pub enum ToQueue {
|
||||
/// [`ToQueue::Amend`].
|
||||
Enqueue { priority: Priority, pvf: Pvf },
|
||||
/// Amends the priority for the given [`ArtifactId`] if it is running. If it's not, then it's noop.
|
||||
Amend {
|
||||
priority: Priority,
|
||||
artifact_id: ArtifactId,
|
||||
},
|
||||
Amend { priority: Priority, artifact_id: ArtifactId },
|
||||
}
|
||||
|
||||
/// A response from queue.
|
||||
@@ -62,11 +57,7 @@ struct Limits {
|
||||
impl Limits {
|
||||
/// Returns `true` if the queue is allowed to request one more worker.
|
||||
fn can_afford_one_more(&self, spawned_num: usize, critical: bool) -> bool {
|
||||
let cap = if critical {
|
||||
self.hard_capacity
|
||||
} else {
|
||||
self.soft_capacity
|
||||
};
|
||||
let cap = if critical { self.hard_capacity } else { self.soft_capacity };
|
||||
spawned_num < cap
|
||||
}
|
||||
|
||||
@@ -179,10 +170,7 @@ impl Queue {
|
||||
from_pool_rx,
|
||||
cache_path,
|
||||
spawn_inflight: 0,
|
||||
limits: Limits {
|
||||
hard_capacity,
|
||||
soft_capacity,
|
||||
},
|
||||
limits: Limits { hard_capacity, soft_capacity },
|
||||
jobs: slotmap::SlotMap::with_key(),
|
||||
unscheduled: Unscheduled::default(),
|
||||
artifact_id_to_job: HashMap::new(),
|
||||
@@ -194,7 +182,7 @@ impl Queue {
|
||||
macro_rules! break_if_fatal {
|
||||
($expr:expr) => {
|
||||
if let Err(Fatal) = $expr {
|
||||
break;
|
||||
break
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -215,13 +203,10 @@ async fn handle_to_queue(queue: &mut Queue, to_queue: ToQueue) -> Result<(), Fat
|
||||
match to_queue {
|
||||
ToQueue::Enqueue { priority, pvf } => {
|
||||
handle_enqueue(queue, priority, pvf).await?;
|
||||
}
|
||||
ToQueue::Amend {
|
||||
priority,
|
||||
artifact_id,
|
||||
} => {
|
||||
},
|
||||
ToQueue::Amend { priority, artifact_id } => {
|
||||
handle_amend(queue, priority, artifact_id).await?;
|
||||
}
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -241,14 +226,10 @@ async fn handle_enqueue(queue: &mut Queue, priority: Priority, pvf: Pvf) -> Resu
|
||||
"duplicate `enqueue` command received for {:?}",
|
||||
artifact_id,
|
||||
);
|
||||
return Ok(());
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let job = queue.jobs.insert(JobData {
|
||||
priority,
|
||||
pvf,
|
||||
worker: None,
|
||||
});
|
||||
let job = queue.jobs.insert(JobData { priority, pvf, worker: None });
|
||||
queue.artifact_id_to_job.insert(artifact_id, job);
|
||||
|
||||
if let Some(available) = find_idle_worker(queue) {
|
||||
@@ -264,12 +245,7 @@ async fn handle_enqueue(queue: &mut Queue, priority: Priority, pvf: Pvf) -> Resu
|
||||
}
|
||||
|
||||
fn find_idle_worker(queue: &mut Queue) -> Option<Worker> {
|
||||
queue
|
||||
.workers
|
||||
.iter()
|
||||
.filter(|(_, data)| data.is_idle())
|
||||
.map(|(k, _)| k)
|
||||
.next()
|
||||
queue.workers.iter().filter(|(_, data)| data.is_idle()).map(|(k, _)| k).next()
|
||||
}
|
||||
|
||||
async fn handle_amend(
|
||||
@@ -336,8 +312,8 @@ async fn handle_worker_concluded(
|
||||
// Assume the conditions holds, then this never is not hit;
|
||||
// qed.
|
||||
never!("never_none, {}", stringify!($expr));
|
||||
return Ok(());
|
||||
}
|
||||
return Ok(())
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -388,10 +364,7 @@ async fn handle_worker_concluded(
|
||||
spawn_extra_worker(queue, false).await?;
|
||||
}
|
||||
} else {
|
||||
if queue
|
||||
.limits
|
||||
.should_cull(queue.workers.len() + queue.spawn_inflight)
|
||||
{
|
||||
if queue.limits.should_cull(queue.workers.len() + queue.spawn_inflight) {
|
||||
// We no longer need services of this worker. Kill it.
|
||||
queue.workers.remove(worker);
|
||||
send_pool(&mut queue.to_pool_tx, pool::ToPool::Kill(worker)).await?;
|
||||
@@ -412,20 +385,16 @@ async fn handle_worker_rip(queue: &mut Queue, worker: Worker) -> Result<(), Fata
|
||||
if let Some(WorkerData { job: Some(job), .. }) = worker_data {
|
||||
// This is an edge case where the worker ripped after we sent assignment but before it
|
||||
// was received by the pool.
|
||||
let priority = queue
|
||||
.jobs
|
||||
.get(job)
|
||||
.map(|data| data.priority)
|
||||
.unwrap_or_else(|| {
|
||||
// job is inserted upon enqueue and removed on concluded signal;
|
||||
// this is enclosed in the if statement that narrows the situation to before
|
||||
// conclusion;
|
||||
// that means that the job still exists and is known;
|
||||
// this path cannot be hit;
|
||||
// qed.
|
||||
never!("the job of the ripped worker must be known but it is not");
|
||||
Priority::Normal
|
||||
});
|
||||
let priority = queue.jobs.get(job).map(|data| data.priority).unwrap_or_else(|| {
|
||||
// job is inserted upon enqueue and removed on concluded signal;
|
||||
// this is enclosed in the if statement that narrows the situation to before
|
||||
// conclusion;
|
||||
// that means that the job still exists and is known;
|
||||
// this path cannot be hit;
|
||||
// qed.
|
||||
never!("the job of the ripped worker must be known but it is not");
|
||||
Priority::Normal
|
||||
});
|
||||
queue.unscheduled.readd(priority, job);
|
||||
}
|
||||
|
||||
@@ -500,11 +469,7 @@ pub fn start(
|
||||
cache_path: PathBuf,
|
||||
to_pool_tx: mpsc::Sender<pool::ToPool>,
|
||||
from_pool_rx: mpsc::UnboundedReceiver<pool::FromPool>,
|
||||
) -> (
|
||||
mpsc::Sender<ToQueue>,
|
||||
mpsc::UnboundedReceiver<FromQueue>,
|
||||
impl Future<Output = ()>,
|
||||
) {
|
||||
) -> (mpsc::Sender<ToQueue>, mpsc::UnboundedReceiver<FromQueue>, impl Future<Output = ()>) {
|
||||
let (to_queue_tx, to_queue_rx) = mpsc::channel(150);
|
||||
let (from_queue_tx, from_queue_rx) = mpsc::unbounded();
|
||||
|
||||
@@ -524,11 +489,11 @@ pub fn start(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use slotmap::SlotMap;
|
||||
use assert_matches::assert_matches;
|
||||
use futures::{FutureExt, future::BoxFuture};
|
||||
use std::task::Poll;
|
||||
use super::*;
|
||||
use assert_matches::assert_matches;
|
||||
use futures::{future::BoxFuture, FutureExt};
|
||||
use slotmap::SlotMap;
|
||||
use std::task::Poll;
|
||||
|
||||
/// Creates a new PVF which artifact id can be uniquely identified by the given number.
|
||||
fn pvf(descriminator: u32) -> Pvf {
|
||||
@@ -549,7 +514,7 @@ mod tests {
|
||||
}
|
||||
|
||||
if let Poll::Ready(r) = futures::poll!(&mut *fut) {
|
||||
break r;
|
||||
break r
|
||||
}
|
||||
|
||||
if futures::poll!(&mut *task).is_ready() {
|
||||
@@ -597,37 +562,21 @@ mod tests {
|
||||
}
|
||||
|
||||
fn send_queue(&mut self, to_queue: ToQueue) {
|
||||
self.to_queue_tx
|
||||
.send(to_queue)
|
||||
.now_or_never()
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
self.to_queue_tx.send(to_queue).now_or_never().unwrap().unwrap();
|
||||
}
|
||||
|
||||
async fn poll_and_recv_from_queue(&mut self) -> FromQueue {
|
||||
let from_queue_rx = &mut self.from_queue_rx;
|
||||
run_until(
|
||||
&mut self.run,
|
||||
async { from_queue_rx.next().await.unwrap() }.boxed(),
|
||||
)
|
||||
.await
|
||||
run_until(&mut self.run, async { from_queue_rx.next().await.unwrap() }.boxed()).await
|
||||
}
|
||||
|
||||
fn send_from_pool(&mut self, from_pool: pool::FromPool) {
|
||||
self.from_pool_tx
|
||||
.send(from_pool)
|
||||
.now_or_never()
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
self.from_pool_tx.send(from_pool).now_or_never().unwrap().unwrap();
|
||||
}
|
||||
|
||||
async fn poll_and_recv_to_pool(&mut self) -> pool::ToPool {
|
||||
let to_pool_rx = &mut self.to_pool_rx;
|
||||
run_until(
|
||||
&mut self.run,
|
||||
async { to_pool_rx.next().await.unwrap() }.boxed(),
|
||||
)
|
||||
.await
|
||||
run_until(&mut self.run, async { to_pool_rx.next().await.unwrap() }.boxed()).await
|
||||
}
|
||||
|
||||
async fn poll_ensure_to_pool_is_empty(&mut self) {
|
||||
@@ -655,10 +604,7 @@ mod tests {
|
||||
async fn properly_concludes() {
|
||||
let mut test = Test::new(2, 2);
|
||||
|
||||
test.send_queue(ToQueue::Enqueue {
|
||||
priority: Priority::Background,
|
||||
pvf: pvf(1),
|
||||
});
|
||||
test.send_queue(ToQueue::Enqueue { priority: Priority::Background, pvf: pvf(1) });
|
||||
assert_eq!(test.poll_and_recv_to_pool().await, pool::ToPool::Spawn);
|
||||
|
||||
let w = test.workers.insert(());
|
||||
@@ -675,18 +621,9 @@ mod tests {
|
||||
async fn dont_spawn_over_soft_limit_unless_critical() {
|
||||
let mut test = Test::new(2, 3);
|
||||
|
||||
test.send_queue(ToQueue::Enqueue {
|
||||
priority: Priority::Normal,
|
||||
pvf: pvf(1),
|
||||
});
|
||||
test.send_queue(ToQueue::Enqueue {
|
||||
priority: Priority::Normal,
|
||||
pvf: pvf(2),
|
||||
});
|
||||
test.send_queue(ToQueue::Enqueue {
|
||||
priority: Priority::Normal,
|
||||
pvf: pvf(3),
|
||||
});
|
||||
test.send_queue(ToQueue::Enqueue { priority: Priority::Normal, pvf: pvf(1) });
|
||||
test.send_queue(ToQueue::Enqueue { priority: Priority::Normal, pvf: pvf(2) });
|
||||
test.send_queue(ToQueue::Enqueue { priority: Priority::Normal, pvf: pvf(3) });
|
||||
|
||||
// Receive only two spawns.
|
||||
assert_eq!(test.poll_and_recv_to_pool().await, pool::ToPool::Spawn);
|
||||
@@ -699,27 +636,15 @@ mod tests {
|
||||
test.send_from_pool(pool::FromPool::Spawned(w2));
|
||||
|
||||
// Get two start works.
|
||||
assert_matches!(
|
||||
test.poll_and_recv_to_pool().await,
|
||||
pool::ToPool::StartWork { .. }
|
||||
);
|
||||
assert_matches!(
|
||||
test.poll_and_recv_to_pool().await,
|
||||
pool::ToPool::StartWork { .. }
|
||||
);
|
||||
assert_matches!(test.poll_and_recv_to_pool().await, pool::ToPool::StartWork { .. });
|
||||
assert_matches!(test.poll_and_recv_to_pool().await, pool::ToPool::StartWork { .. });
|
||||
|
||||
test.send_from_pool(pool::FromPool::Concluded(w1, false));
|
||||
|
||||
assert_matches!(
|
||||
test.poll_and_recv_to_pool().await,
|
||||
pool::ToPool::StartWork { .. }
|
||||
);
|
||||
assert_matches!(test.poll_and_recv_to_pool().await, pool::ToPool::StartWork { .. });
|
||||
|
||||
// Enqueue a critical job.
|
||||
test.send_queue(ToQueue::Enqueue {
|
||||
priority: Priority::Critical,
|
||||
pvf: pvf(4),
|
||||
});
|
||||
test.send_queue(ToQueue::Enqueue { priority: Priority::Critical, pvf: pvf(4) });
|
||||
|
||||
// 2 out of 2 are working, but there is a critical job incoming. That means that spawning
|
||||
// another worker is warranted.
|
||||
@@ -730,23 +655,14 @@ mod tests {
|
||||
async fn cull_unwanted() {
|
||||
let mut test = Test::new(1, 2);
|
||||
|
||||
test.send_queue(ToQueue::Enqueue {
|
||||
priority: Priority::Normal,
|
||||
pvf: pvf(1),
|
||||
});
|
||||
test.send_queue(ToQueue::Enqueue { priority: Priority::Normal, pvf: pvf(1) });
|
||||
assert_eq!(test.poll_and_recv_to_pool().await, pool::ToPool::Spawn);
|
||||
let w1 = test.workers.insert(());
|
||||
test.send_from_pool(pool::FromPool::Spawned(w1));
|
||||
assert_matches!(
|
||||
test.poll_and_recv_to_pool().await,
|
||||
pool::ToPool::StartWork { .. }
|
||||
);
|
||||
assert_matches!(test.poll_and_recv_to_pool().await, pool::ToPool::StartWork { .. });
|
||||
|
||||
// Enqueue a critical job, which warrants spawning over the soft limit.
|
||||
test.send_queue(ToQueue::Enqueue {
|
||||
priority: Priority::Critical,
|
||||
pvf: pvf(2),
|
||||
});
|
||||
test.send_queue(ToQueue::Enqueue { priority: Priority::Critical, pvf: pvf(2) });
|
||||
assert_eq!(test.poll_and_recv_to_pool().await, pool::ToPool::Spawn);
|
||||
|
||||
// However, before the new worker had a chance to spawn, the first worker finishes with its
|
||||
@@ -764,47 +680,29 @@ mod tests {
|
||||
async fn bump_prio_on_urgency_change() {
|
||||
let mut test = Test::new(2, 2);
|
||||
|
||||
test.send_queue(ToQueue::Enqueue {
|
||||
priority: Priority::Background,
|
||||
pvf: pvf(1),
|
||||
});
|
||||
test.send_queue(ToQueue::Enqueue { priority: Priority::Background, pvf: pvf(1) });
|
||||
|
||||
assert_eq!(test.poll_and_recv_to_pool().await, pool::ToPool::Spawn);
|
||||
|
||||
let w = test.workers.insert(());
|
||||
test.send_from_pool(pool::FromPool::Spawned(w));
|
||||
|
||||
assert_matches!(
|
||||
test.poll_and_recv_to_pool().await,
|
||||
pool::ToPool::StartWork { .. }
|
||||
);
|
||||
assert_matches!(test.poll_and_recv_to_pool().await, pool::ToPool::StartWork { .. });
|
||||
test.send_queue(ToQueue::Amend {
|
||||
priority: Priority::Normal,
|
||||
artifact_id: pvf(1).as_artifact_id(),
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
test.poll_and_recv_to_pool().await,
|
||||
pool::ToPool::BumpPriority(w)
|
||||
);
|
||||
assert_eq!(test.poll_and_recv_to_pool().await, pool::ToPool::BumpPriority(w));
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn worker_mass_die_out_doesnt_stall_queue() {
|
||||
let mut test = Test::new(2, 2);
|
||||
|
||||
test.send_queue(ToQueue::Enqueue {
|
||||
priority: Priority::Normal,
|
||||
pvf: pvf(1),
|
||||
});
|
||||
test.send_queue(ToQueue::Enqueue {
|
||||
priority: Priority::Normal,
|
||||
pvf: pvf(2),
|
||||
});
|
||||
test.send_queue(ToQueue::Enqueue {
|
||||
priority: Priority::Normal,
|
||||
pvf: pvf(3),
|
||||
});
|
||||
test.send_queue(ToQueue::Enqueue { priority: Priority::Normal, pvf: pvf(1) });
|
||||
test.send_queue(ToQueue::Enqueue { priority: Priority::Normal, pvf: pvf(2) });
|
||||
test.send_queue(ToQueue::Enqueue { priority: Priority::Normal, pvf: pvf(3) });
|
||||
|
||||
assert_eq!(test.poll_and_recv_to_pool().await, pool::ToPool::Spawn);
|
||||
assert_eq!(test.poll_and_recv_to_pool().await, pool::ToPool::Spawn);
|
||||
@@ -815,14 +713,8 @@ mod tests {
|
||||
test.send_from_pool(pool::FromPool::Spawned(w1));
|
||||
test.send_from_pool(pool::FromPool::Spawned(w2));
|
||||
|
||||
assert_matches!(
|
||||
test.poll_and_recv_to_pool().await,
|
||||
pool::ToPool::StartWork { .. }
|
||||
);
|
||||
assert_matches!(
|
||||
test.poll_and_recv_to_pool().await,
|
||||
pool::ToPool::StartWork { .. }
|
||||
);
|
||||
assert_matches!(test.poll_and_recv_to_pool().await, pool::ToPool::StartWork { .. });
|
||||
assert_matches!(test.poll_and_recv_to_pool().await, pool::ToPool::StartWork { .. });
|
||||
|
||||
// Conclude worker 1 and rip it.
|
||||
test.send_from_pool(pool::FromPool::Concluded(w1, true));
|
||||
@@ -840,20 +732,14 @@ mod tests {
|
||||
async fn doesnt_resurrect_ripped_worker_if_no_work() {
|
||||
let mut test = Test::new(2, 2);
|
||||
|
||||
test.send_queue(ToQueue::Enqueue {
|
||||
priority: Priority::Normal,
|
||||
pvf: pvf(1),
|
||||
});
|
||||
test.send_queue(ToQueue::Enqueue { priority: Priority::Normal, pvf: pvf(1) });
|
||||
|
||||
assert_eq!(test.poll_and_recv_to_pool().await, pool::ToPool::Spawn);
|
||||
|
||||
let w1 = test.workers.insert(());
|
||||
test.send_from_pool(pool::FromPool::Spawned(w1));
|
||||
|
||||
assert_matches!(
|
||||
test.poll_and_recv_to_pool().await,
|
||||
pool::ToPool::StartWork { .. }
|
||||
);
|
||||
assert_matches!(test.poll_and_recv_to_pool().await, pool::ToPool::StartWork { .. });
|
||||
|
||||
test.send_from_pool(pool::FromPool::Concluded(w1, true));
|
||||
test.poll_ensure_to_pool_is_empty().await;
|
||||
@@ -863,10 +749,7 @@ mod tests {
|
||||
async fn rip_for_start_work() {
|
||||
let mut test = Test::new(2, 2);
|
||||
|
||||
test.send_queue(ToQueue::Enqueue {
|
||||
priority: Priority::Normal,
|
||||
pvf: pvf(1),
|
||||
});
|
||||
test.send_queue(ToQueue::Enqueue { priority: Priority::Normal, pvf: pvf(1) });
|
||||
|
||||
assert_eq!(test.poll_and_recv_to_pool().await, pool::ToPool::Spawn);
|
||||
|
||||
@@ -875,10 +758,7 @@ mod tests {
|
||||
|
||||
// Now, to the interesting part. After the queue normally issues the start_work command to
|
||||
// the pool, before receiving the command the queue may report that the worker ripped.
|
||||
assert_matches!(
|
||||
test.poll_and_recv_to_pool().await,
|
||||
pool::ToPool::StartWork { .. }
|
||||
);
|
||||
assert_matches!(test.poll_and_recv_to_pool().await, pool::ToPool::StartWork { .. });
|
||||
test.send_from_pool(pool::FromPool::Rip(w1));
|
||||
|
||||
// In this case, the pool should spawn a new worker and request it to work on the item.
|
||||
@@ -886,9 +766,6 @@ mod tests {
|
||||
|
||||
let w2 = test.workers.insert(());
|
||||
test.send_from_pool(pool::FromPool::Spawned(w2));
|
||||
assert_matches!(
|
||||
test.poll_and_recv_to_pool().await,
|
||||
pool::ToPool::StartWork { .. }
|
||||
);
|
||||
assert_matches!(test.poll_and_recv_to_pool().await, pool::ToPool::StartWork { .. });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,17 +15,17 @@
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{
|
||||
LOG_TARGET,
|
||||
artifacts::Artifact,
|
||||
worker_common::{
|
||||
IdleWorker, SpawnErr, WorkerHandle, bytes_to_path, framed_recv, framed_send, path_to_bytes,
|
||||
spawn_with_program_path, tmpfile_in, worker_event_loop,
|
||||
bytes_to_path, framed_recv, framed_send, path_to_bytes, spawn_with_program_path,
|
||||
tmpfile_in, worker_event_loop, IdleWorker, SpawnErr, WorkerHandle,
|
||||
},
|
||||
LOG_TARGET,
|
||||
};
|
||||
use async_std::{
|
||||
io,
|
||||
os::unix::net::UnixStream,
|
||||
path::{PathBuf, Path},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use futures::FutureExt as _;
|
||||
use futures_timer::Delay;
|
||||
@@ -43,13 +43,7 @@ pub async fn spawn(
|
||||
program_path: &Path,
|
||||
spawn_timeout: Duration,
|
||||
) -> Result<(IdleWorker, WorkerHandle), SpawnErr> {
|
||||
spawn_with_program_path(
|
||||
"prepare",
|
||||
program_path,
|
||||
&["prepare-worker"],
|
||||
spawn_timeout,
|
||||
)
|
||||
.await
|
||||
spawn_with_program_path("prepare", program_path, &["prepare-worker"], spawn_timeout).await
|
||||
}
|
||||
|
||||
pub enum Outcome {
|
||||
@@ -99,7 +93,7 @@ pub async fn start_work(
|
||||
"failed to send a prepare request: {:?}",
|
||||
err,
|
||||
);
|
||||
return Outcome::Unreachable;
|
||||
return Outcome::Unreachable
|
||||
}
|
||||
|
||||
// Wait for the result from the worker, keeping in mind that there may be a timeout, the
|
||||
@@ -172,13 +166,13 @@ pub async fn start_work(
|
||||
Selected::Done => {
|
||||
renice(pid, NICENESS_FOREGROUND);
|
||||
Outcome::Concluded(IdleWorker { stream, pid })
|
||||
}
|
||||
},
|
||||
Selected::IoErr | Selected::Deadline => {
|
||||
let bytes = Artifact::DidntMakeIt.serialize();
|
||||
// best effort: there is nothing we can do here if the write fails.
|
||||
let _ = async_std::fs::write(&artifact_path, &bytes).await;
|
||||
Outcome::DidntMakeIt
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
.await
|
||||
@@ -202,8 +196,8 @@ where
|
||||
"failed to create a temp file for the artifact: {:?}",
|
||||
err,
|
||||
);
|
||||
return Outcome::DidntMakeIt;
|
||||
}
|
||||
return Outcome::DidntMakeIt
|
||||
},
|
||||
};
|
||||
|
||||
let outcome = f(tmp_file.clone()).await;
|
||||
@@ -223,7 +217,7 @@ where
|
||||
"failed to remove the tmp file: {:?}",
|
||||
err,
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
outcome
|
||||
@@ -304,9 +298,7 @@ pub fn worker_entrypoint(socket_path: &str) {
|
||||
|
||||
fn prepare_artifact(code: &[u8]) -> Artifact {
|
||||
let blob = match crate::executor_intf::prevalidate(code) {
|
||||
Err(err) => {
|
||||
return Artifact::PrevalidationErr(format!("{:?}", err));
|
||||
}
|
||||
Err(err) => return Artifact::PrevalidationErr(format!("{:?}", err)),
|
||||
Ok(b) => b,
|
||||
};
|
||||
|
||||
|
||||
@@ -29,9 +29,10 @@ pub fn validate_candidate(
|
||||
code: &[u8],
|
||||
params: &[u8],
|
||||
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||
use crate::executor_intf::{prevalidate, prepare, execute, TaskExecutor};
|
||||
use crate::executor_intf::{execute, prepare, prevalidate, TaskExecutor};
|
||||
|
||||
let code = sp_maybe_compressed_blob::decompress(code, 10 * 1024 * 1024).expect("Decompressing code failed");
|
||||
let code = sp_maybe_compressed_blob::decompress(code, 10 * 1024 * 1024)
|
||||
.expect("Decompressing code failed");
|
||||
|
||||
let blob = prevalidate(&*code)?;
|
||||
let artifact = prepare(blob)?;
|
||||
@@ -61,15 +62,15 @@ macro_rules! decl_puppet_worker_main {
|
||||
match subcommand.as_ref() {
|
||||
"sleep" => {
|
||||
std::thread::sleep(std::time::Duration::from_secs(5));
|
||||
}
|
||||
},
|
||||
"prepare-worker" => {
|
||||
let socket_path = &args[2];
|
||||
$crate::prepare_worker_entrypoint(socket_path);
|
||||
}
|
||||
},
|
||||
"execute-worker" => {
|
||||
let socket_path = &args[2];
|
||||
$crate::execute_worker_entrypoint(socket_path);
|
||||
}
|
||||
},
|
||||
other => panic!("unknown subcommand: {}", other),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,13 @@ use crate::LOG_TARGET;
|
||||
use async_std::{
|
||||
io,
|
||||
os::unix::net::{UnixListener, UnixStream},
|
||||
path::{PathBuf, Path},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use futures::{
|
||||
AsyncRead, AsyncWrite, AsyncReadExt as _, AsyncWriteExt as _, FutureExt as _, never::Never,
|
||||
never::Never, AsyncRead, AsyncReadExt as _, AsyncWrite, AsyncWriteExt as _, FutureExt as _,
|
||||
};
|
||||
use futures_timer::Delay;
|
||||
use pin_project::pin_project;
|
||||
use rand::Rng;
|
||||
use std::{
|
||||
fmt, mem,
|
||||
@@ -33,7 +34,6 @@ use std::{
|
||||
task::{Context, Poll},
|
||||
time::Duration,
|
||||
};
|
||||
use pin_project::pin_project;
|
||||
|
||||
/// This is publicly exposed only for integration tests.
|
||||
#[doc(hidden)]
|
||||
@@ -47,9 +47,7 @@ pub async fn spawn_with_program_path(
|
||||
with_transient_socket_path(debug_id, |socket_path| {
|
||||
let socket_path = socket_path.to_owned();
|
||||
async move {
|
||||
let listener = UnixListener::bind(&socket_path)
|
||||
.await
|
||||
.map_err(|_| SpawnErr::Bind)?;
|
||||
let listener = UnixListener::bind(&socket_path).await.map_err(|_| SpawnErr::Bind)?;
|
||||
|
||||
let handle = WorkerHandle::spawn(program_path, extra_args, socket_path)
|
||||
.map_err(|_| SpawnErr::ProcessSpawn)?;
|
||||
@@ -97,11 +95,7 @@ pub async fn tmpfile_in(prefix: &str, dir: &Path) -> io::Result<PathBuf> {
|
||||
|
||||
let mut buf = Vec::with_capacity(prefix.len() + DESCRIMINATOR_LEN);
|
||||
buf.extend(prefix.as_bytes());
|
||||
buf.extend(
|
||||
rand::thread_rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(DESCRIMINATOR_LEN),
|
||||
);
|
||||
buf.extend(rand::thread_rng().sample_iter(&Alphanumeric).take(DESCRIMINATOR_LEN));
|
||||
|
||||
let s = std::str::from_utf8(&buf)
|
||||
.expect("the string is collected from a valid utf-8 sequence; qed");
|
||||
@@ -120,9 +114,7 @@ pub async fn tmpfile_in(prefix: &str, dir: &Path) -> io::Result<PathBuf> {
|
||||
}
|
||||
}
|
||||
|
||||
Err(
|
||||
io::Error::new(io::ErrorKind::Other, "failed to create a temporary file")
|
||||
)
|
||||
Err(io::Error::new(io::ErrorKind::Other, "failed to create a temporary file"))
|
||||
}
|
||||
|
||||
/// The same as [`tmpfile_in`], but uses [`std::env::temp_dir`] as the directory.
|
||||
@@ -245,17 +237,17 @@ impl futures::Future for WorkerHandle {
|
||||
Ok(0) => {
|
||||
// 0 means EOF means the child was terminated. Resolve.
|
||||
Poll::Ready(())
|
||||
}
|
||||
},
|
||||
Ok(_bytes_read) => {
|
||||
// weird, we've read something. Pretend that never happened and reschedule ourselves.
|
||||
cx.waker().wake_by_ref();
|
||||
Poll::Pending
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
// The implementation is guaranteed to not to return WouldBlock and Interrupted. This
|
||||
// leaves us with a legit errors which we suppose were due to termination.
|
||||
Poll::Ready(())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user