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:
Shawn Tabrizi
2021-08-02 12:47:33 +02:00
committed by GitHub
parent 30e3012270
commit ff5d56fb76
350 changed files with 20617 additions and 21266 deletions
+17 -19
View File
@@ -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");
+1 -1
View File
@@ -23,5 +23,5 @@
mod queue;
mod worker;
pub use queue::{ToQueue, start};
pub use queue::{start, ToQueue};
pub use worker::worker_entrypoint;
+31 -79
View File
@@ -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)
}
+35 -84
View File
@@ -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 }
}
+4 -8
View File
@@ -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)
})?
}
+63 -159
View File
@@ -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,
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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;
+24 -43
View File
@@ -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();
+62 -185
View File
@@ -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 { .. });
}
}
+12 -20
View File
@@ -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,
};
+6 -5
View File
@@ -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),
}
}
+9 -17
View File
@@ -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(())
}
},
}
}
}