mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-21 09:45:41 +00:00
98.6% OF DEVELOPERS CANNOT REVIEW THIS PR! [read more...] (#7337)
* [WIP] PVF: Split out worker binaries * Address compilation problems and re-design a bit * Reorganize once more, fix tests * Reformat with new nightly to make `cargo fmt` test happy * Address `clippy` warnings * Add temporary trace to debug zombienet tests * Fix zombienet node upgrade test * Fix malus and its CI * Fix building worker binaries with malus * More fixes for malus * Remove unneeded cli subcommands * Support placing auxiliary binaries to `/usr/libexec` * Fix spelling * Spelling Co-authored-by: Marcin S. <marcin@realemail.net> * Implement review comments (mostly nits) * Fix worker node version flag * Rework getting the worker paths * Address a couple of review comments * Minor restructuring * Fix CI error * Add tests for worker binaries detection * Improve tests; try to fix CI * Move workers module into separate file * Try to fix failing test and workers not printing latest version - Tests were not finding the worker binaries - Workers were not being rebuilt when the version changed - Made some errors easier to read * Make a bunch of fixes * Rebuild nodes on version change * Fix more issues * Fix tests * Pass node version from node into dependencies to avoid recompiles - [X] get version in CLI - [X] pass it in to service - [X] pass version along to PVF - [X] remove rerun from service - [X] add rerun to CLI - [X] don’t rerun pvf/worker’s (these should be built by nodes which have rerun enabled) * Some more improvements for smoother tests - [X] Fix tests - [X] Make puppet workers pass None for version and remove rerun - [X] Make test collators self-contained * Add back rerun to PVF workers * Move worker binaries into files in cli crate As a final optimization I've separated out each worker binary from its own crate into the CLI crate. Before, the worker bin shared a crate with the worker lib, so when the binaries got recompiled so did the libs and everything transitively depending on the libs. This commit fixes this regression that was causing recompiles after every commit. * Fix bug (was passing worker version for node version) * Move workers out of cli into root src/bin/ dir - [X] Pass in node version from top-level (polkadot) - [X] Add build.rs with rerun-git-head to root dir * Add some sanity checks for workers to dockerfiles * Update malus + [X] Make it self-contained + [X] Undo multiple binary changes * Try to fix clippy errors * Address `cargo run` issue - [X] Add default-run for polkadot - [X] Add note about installation to error * Update readme (installation instructions) * Allow disabling external workers for local/testing setups + [X] cli flag to enable single-binary mode + [X] Add message to error * Revert unnecessary Cargo.lock changes * Remove unnecessary build scripts from collators * Add back missing malus commands (should fix failing ZN job) * Some minor fixes * Update Cargo.lock * Fix some build errors * Undo self-contained binaries; cli flag to disable version check + [X] Remove --dont-run-external-workers + [X] Add --disable-worker-version-check + [X] Remove PVF subcommands + [X] Redo malus changes * Try to fix failing job and add some docs for local tests --------- Co-authored-by: Dmitry Sinyavin <dmitry.sinyavin@parity.io> Co-authored-by: s0me0ne-unkn0wn <48632512+s0me0ne-unkn0wn@users.noreply.github.com> Co-authored-by: parity-processbot <>
This commit is contained in:
@@ -116,169 +116,189 @@ async fn send_response(stream: &mut UnixStream, result: PrepareResult) -> io::Re
|
||||
///
|
||||
/// 7. Send the result of preparation back to the host. If any error occurred in the above steps, we
|
||||
/// send that in the `PrepareResult`.
|
||||
pub fn worker_entrypoint(socket_path: &str, node_version: Option<&str>) {
|
||||
worker_event_loop("prepare", socket_path, node_version, |mut stream| async move {
|
||||
let worker_pid = std::process::id();
|
||||
pub fn worker_entrypoint(
|
||||
socket_path: &str,
|
||||
node_version: Option<&str>,
|
||||
worker_version: Option<&str>,
|
||||
) {
|
||||
worker_event_loop(
|
||||
"prepare",
|
||||
socket_path,
|
||||
node_version,
|
||||
worker_version,
|
||||
|mut stream| async move {
|
||||
let worker_pid = std::process::id();
|
||||
|
||||
loop {
|
||||
let (pvf, temp_artifact_dest) = recv_request(&mut stream).await?;
|
||||
gum::debug!(
|
||||
target: LOG_TARGET,
|
||||
%worker_pid,
|
||||
"worker: preparing artifact",
|
||||
);
|
||||
loop {
|
||||
let (pvf, temp_artifact_dest) = recv_request(&mut stream).await?;
|
||||
gum::debug!(
|
||||
target: LOG_TARGET,
|
||||
%worker_pid,
|
||||
"worker: preparing artifact",
|
||||
);
|
||||
|
||||
let preparation_timeout = pvf.prep_timeout();
|
||||
let prepare_job_kind = pvf.prep_kind();
|
||||
let executor_params = (*pvf.executor_params()).clone();
|
||||
let preparation_timeout = pvf.prep_timeout();
|
||||
let prepare_job_kind = pvf.prep_kind();
|
||||
let executor_params = (*pvf.executor_params()).clone();
|
||||
|
||||
// Conditional variable to notify us when a thread is done.
|
||||
let condvar = thread::get_condvar();
|
||||
// Conditional variable to notify us when a thread is done.
|
||||
let condvar = thread::get_condvar();
|
||||
|
||||
// Run the memory tracker in a regular, non-worker thread.
|
||||
#[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))]
|
||||
let condvar_memory = Arc::clone(&condvar);
|
||||
#[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))]
|
||||
let memory_tracker_thread = std::thread::spawn(|| memory_tracker_loop(condvar_memory));
|
||||
// Run the memory tracker in a regular, non-worker thread.
|
||||
#[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))]
|
||||
let condvar_memory = Arc::clone(&condvar);
|
||||
#[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))]
|
||||
let memory_tracker_thread = std::thread::spawn(|| memory_tracker_loop(condvar_memory));
|
||||
|
||||
let cpu_time_start = ProcessTime::now();
|
||||
let cpu_time_start = ProcessTime::now();
|
||||
|
||||
// Spawn a new thread that runs the CPU time monitor.
|
||||
let (cpu_time_monitor_tx, cpu_time_monitor_rx) = channel::<()>();
|
||||
let cpu_time_monitor_thread = thread::spawn_worker_thread(
|
||||
"cpu time monitor thread",
|
||||
move || {
|
||||
cpu_time_monitor_loop(cpu_time_start, preparation_timeout, cpu_time_monitor_rx)
|
||||
},
|
||||
Arc::clone(&condvar),
|
||||
WaitOutcome::TimedOut,
|
||||
)?;
|
||||
// Spawn another thread for preparation.
|
||||
let prepare_thread = thread::spawn_worker_thread(
|
||||
"prepare thread",
|
||||
move || {
|
||||
// Try to enable landlock.
|
||||
#[cfg(target_os = "linux")]
|
||||
// Spawn a new thread that runs the CPU time monitor.
|
||||
let (cpu_time_monitor_tx, cpu_time_monitor_rx) = channel::<()>();
|
||||
let cpu_time_monitor_thread = thread::spawn_worker_thread(
|
||||
"cpu time monitor thread",
|
||||
move || {
|
||||
cpu_time_monitor_loop(
|
||||
cpu_time_start,
|
||||
preparation_timeout,
|
||||
cpu_time_monitor_rx,
|
||||
)
|
||||
},
|
||||
Arc::clone(&condvar),
|
||||
WaitOutcome::TimedOut,
|
||||
)?;
|
||||
// Spawn another thread for preparation.
|
||||
let prepare_thread = thread::spawn_worker_thread(
|
||||
"prepare thread",
|
||||
move || {
|
||||
// Try to enable landlock.
|
||||
#[cfg(target_os = "linux")]
|
||||
let landlock_status = polkadot_node_core_pvf_common::worker::security::landlock::try_restrict_thread()
|
||||
.map(LandlockStatus::from_ruleset_status)
|
||||
.map_err(|e| e.to_string());
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let landlock_status: Result<LandlockStatus, String> = Ok(LandlockStatus::NotEnforced);
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let landlock_status: Result<LandlockStatus, String> = Ok(LandlockStatus::NotEnforced);
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut result = prepare_artifact(pvf, cpu_time_start);
|
||||
#[allow(unused_mut)]
|
||||
let mut result = prepare_artifact(pvf, cpu_time_start);
|
||||
|
||||
// Get the `ru_maxrss` stat. If supported, call getrusage for the thread.
|
||||
#[cfg(target_os = "linux")]
|
||||
let mut result = result.map(|(artifact, elapsed)| (artifact, elapsed, get_max_rss_thread()));
|
||||
// Get the `ru_maxrss` stat. If supported, call getrusage for the thread.
|
||||
#[cfg(target_os = "linux")]
|
||||
let mut result = result
|
||||
.map(|(artifact, elapsed)| (artifact, elapsed, get_max_rss_thread()));
|
||||
|
||||
// If we are pre-checking, check for runtime construction errors.
|
||||
//
|
||||
// As pre-checking is more strict than just preparation in terms of memory and
|
||||
// time, it is okay to do extra checks here. This takes negligible time anyway.
|
||||
if let PrepareJobKind::Prechecking = prepare_job_kind {
|
||||
result = result.and_then(|output| {
|
||||
runtime_construction_check(output.0.as_ref(), executor_params)?;
|
||||
Ok(output)
|
||||
});
|
||||
}
|
||||
// If we are pre-checking, check for runtime construction errors.
|
||||
//
|
||||
// As pre-checking is more strict than just preparation in terms of memory and
|
||||
// time, it is okay to do extra checks here. This takes negligible time anyway.
|
||||
if let PrepareJobKind::Prechecking = prepare_job_kind {
|
||||
result = result.and_then(|output| {
|
||||
runtime_construction_check(output.0.as_ref(), executor_params)?;
|
||||
Ok(output)
|
||||
});
|
||||
}
|
||||
|
||||
(result, landlock_status)
|
||||
},
|
||||
Arc::clone(&condvar),
|
||||
WaitOutcome::Finished,
|
||||
)?;
|
||||
(result, landlock_status)
|
||||
},
|
||||
Arc::clone(&condvar),
|
||||
WaitOutcome::Finished,
|
||||
)?;
|
||||
|
||||
let outcome = thread::wait_for_threads(condvar);
|
||||
let outcome = thread::wait_for_threads(condvar);
|
||||
|
||||
let result = match outcome {
|
||||
WaitOutcome::Finished => {
|
||||
let _ = cpu_time_monitor_tx.send(());
|
||||
let result = match outcome {
|
||||
WaitOutcome::Finished => {
|
||||
let _ = cpu_time_monitor_tx.send(());
|
||||
|
||||
match prepare_thread.join().unwrap_or_else(|err| {
|
||||
(
|
||||
Err(PrepareError::Panic(stringify_panic_payload(err))),
|
||||
Ok(LandlockStatus::Unavailable),
|
||||
)
|
||||
}) {
|
||||
(Err(err), _) => {
|
||||
// Serialized error will be written into the socket.
|
||||
Err(err)
|
||||
},
|
||||
(Ok(ok), landlock_status) => {
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let (artifact, cpu_time_elapsed) = ok;
|
||||
#[cfg(target_os = "linux")]
|
||||
let (artifact, cpu_time_elapsed, max_rss) = ok;
|
||||
|
||||
// Stop the memory stats worker and get its observed memory stats.
|
||||
#[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))]
|
||||
let memory_tracker_stats = get_memory_tracker_loop_stats(memory_tracker_thread, worker_pid).await;
|
||||
let memory_stats = MemoryStats {
|
||||
#[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))]
|
||||
memory_tracker_stats,
|
||||
match prepare_thread.join().unwrap_or_else(|err| {
|
||||
(
|
||||
Err(PrepareError::Panic(stringify_panic_payload(err))),
|
||||
Ok(LandlockStatus::Unavailable),
|
||||
)
|
||||
}) {
|
||||
(Err(err), _) => {
|
||||
// Serialized error will be written into the socket.
|
||||
Err(err)
|
||||
},
|
||||
(Ok(ok), landlock_status) => {
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let (artifact, cpu_time_elapsed) = ok;
|
||||
#[cfg(target_os = "linux")]
|
||||
max_rss: extract_max_rss_stat(max_rss, worker_pid),
|
||||
};
|
||||
let (artifact, cpu_time_elapsed, max_rss) = ok;
|
||||
|
||||
// Log if landlock threw an error.
|
||||
if let Err(err) = landlock_status {
|
||||
// Stop the memory stats worker and get its observed memory stats.
|
||||
#[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))]
|
||||
let memory_tracker_stats = get_memory_tracker_loop_stats(memory_tracker_thread, worker_pid)
|
||||
.await;
|
||||
let memory_stats = MemoryStats {
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
feature = "jemalloc-allocator"
|
||||
))]
|
||||
memory_tracker_stats,
|
||||
#[cfg(target_os = "linux")]
|
||||
max_rss: extract_max_rss_stat(max_rss, worker_pid),
|
||||
};
|
||||
|
||||
// Log if landlock threw an error.
|
||||
if let Err(err) = landlock_status {
|
||||
gum::warn!(
|
||||
target: LOG_TARGET,
|
||||
%worker_pid,
|
||||
"error enabling landlock: {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
|
||||
// Write the serialized artifact into a temp file.
|
||||
//
|
||||
// PVF host only keeps artifacts statuses in its memory, successfully
|
||||
// compiled code gets stored on the disk (and consequently deserialized
|
||||
// by execute-workers). The prepare worker is only required to send `Ok`
|
||||
// to the pool to indicate the success.
|
||||
|
||||
gum::debug!(
|
||||
target: LOG_TARGET,
|
||||
%worker_pid,
|
||||
"worker: writing artifact to {}",
|
||||
temp_artifact_dest.display(),
|
||||
);
|
||||
tokio::fs::write(&temp_artifact_dest, &artifact).await?;
|
||||
|
||||
Ok(PrepareStats { cpu_time_elapsed, memory_stats })
|
||||
},
|
||||
}
|
||||
},
|
||||
// If the CPU thread is not selected, we signal it to end, the join handle is
|
||||
// dropped and the thread will finish in the background.
|
||||
WaitOutcome::TimedOut => {
|
||||
match cpu_time_monitor_thread.join() {
|
||||
Ok(Some(cpu_time_elapsed)) => {
|
||||
// Log if we exceed the timeout and the other thread hasn't finished.
|
||||
gum::warn!(
|
||||
target: LOG_TARGET,
|
||||
%worker_pid,
|
||||
"error enabling landlock: {}",
|
||||
err
|
||||
"prepare job took {}ms cpu time, exceeded prepare timeout {}ms",
|
||||
cpu_time_elapsed.as_millis(),
|
||||
preparation_timeout.as_millis(),
|
||||
);
|
||||
}
|
||||
Err(PrepareError::TimedOut)
|
||||
},
|
||||
Ok(None) => Err(PrepareError::IoErr(
|
||||
"error communicating over closed channel".into(),
|
||||
)),
|
||||
// Errors in this thread are independent of the PVF.
|
||||
Err(err) => Err(PrepareError::IoErr(stringify_panic_payload(err))),
|
||||
}
|
||||
},
|
||||
WaitOutcome::Pending => unreachable!(
|
||||
"we run wait_while until the outcome is no longer pending; qed"
|
||||
),
|
||||
};
|
||||
|
||||
// Write the serialized artifact into a temp file.
|
||||
//
|
||||
// PVF host only keeps artifacts statuses in its memory, successfully
|
||||
// compiled code gets stored on the disk (and consequently deserialized
|
||||
// by execute-workers). The prepare worker is only required to send `Ok`
|
||||
// to the pool to indicate the success.
|
||||
|
||||
gum::debug!(
|
||||
target: LOG_TARGET,
|
||||
%worker_pid,
|
||||
"worker: writing artifact to {}",
|
||||
temp_artifact_dest.display(),
|
||||
);
|
||||
tokio::fs::write(&temp_artifact_dest, &artifact).await?;
|
||||
|
||||
Ok(PrepareStats { cpu_time_elapsed, memory_stats })
|
||||
},
|
||||
}
|
||||
},
|
||||
// If the CPU thread is not selected, we signal it to end, the join handle is
|
||||
// dropped and the thread will finish in the background.
|
||||
WaitOutcome::TimedOut => {
|
||||
match cpu_time_monitor_thread.join() {
|
||||
Ok(Some(cpu_time_elapsed)) => {
|
||||
// Log if we exceed the timeout and the other thread hasn't finished.
|
||||
gum::warn!(
|
||||
target: LOG_TARGET,
|
||||
%worker_pid,
|
||||
"prepare job took {}ms cpu time, exceeded prepare timeout {}ms",
|
||||
cpu_time_elapsed.as_millis(),
|
||||
preparation_timeout.as_millis(),
|
||||
);
|
||||
Err(PrepareError::TimedOut)
|
||||
},
|
||||
Ok(None) => Err(PrepareError::IoErr(
|
||||
"error communicating over closed channel".into(),
|
||||
)),
|
||||
// Errors in this thread are independent of the PVF.
|
||||
Err(err) => Err(PrepareError::IoErr(stringify_panic_payload(err))),
|
||||
}
|
||||
},
|
||||
WaitOutcome::Pending =>
|
||||
unreachable!("we run wait_while until the outcome is no longer pending; qed"),
|
||||
};
|
||||
|
||||
send_response(&mut stream, result).await?;
|
||||
}
|
||||
});
|
||||
send_response(&mut stream, result).await?;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn prepare_artifact(
|
||||
|
||||
Reference in New Issue
Block a user