Skip to content

Commit

Permalink
feat: RUN-842, RUN-843: Compilation in a separate proceess
Browse files Browse the repository at this point in the history
  • Loading branch information
eust-dfinity committed Feb 21, 2024
1 parent 8ac2766 commit cd2ceda
Show file tree
Hide file tree
Showing 26 changed files with 755 additions and 40 deletions.
2 changes: 2 additions & 0 deletions bazel/defs.bzl
Expand Up @@ -160,11 +160,13 @@ def sha256sum2url(name, src, tags = [], **kwargs):
# Binaries needed for testing with canister_sandbox
_SANDBOX_DATA = [
"//rs/canister_sandbox",
"//rs/canister_sandbox:compiler_sandbox",
"//rs/canister_sandbox:sandbox_launcher",
]

# Env needed for testing with canister_sandbox
_SANDBOX_ENV = {
"COMPILER_BINARY": "$(rootpath //rs/canister_sandbox:compiler_sandbox)",
"LAUNCHER_BINARY": "$(rootpath //rs/canister_sandbox:sandbox_launcher)",
"SANDBOX_BINARY": "$(rootpath //rs/canister_sandbox)",
}
Expand Down
1 change: 1 addition & 0 deletions ic-os/guestos/defs.bzl
Expand Up @@ -30,6 +30,7 @@ def image_deps(mode, malicious = False):

# additional files to install
"//publish/binaries:canister_sandbox": "/opt/ic/bin/canister_sandbox:0755",
"//publish/binaries:compiler_sandbox": "/opt/ic/bin/compiler_sandbox:0755",
"//publish/binaries:guestos_tool": "/opt/ic/bin/guestos_tool:0755",
"//publish/binaries:ic-btc-adapter": "/opt/ic/bin/ic-btc-adapter:0755",
"//publish/binaries:ic-consensus-pool-util": "/opt/ic/bin/ic-consensus-pool-util:0755",
Expand Down
3 changes: 2 additions & 1 deletion ic-os/guestos/docs/SELinux-Policy.adoc
Expand Up @@ -174,7 +174,8 @@ to declutter the heavily overloaded role of the +ic_replica_t+
domain).

* +ic_canister_sandbox_exec_t+: This label is applied to the canister_sandbox
_binary_ file located at +/opt/ic/bin/canister_sandbox+. Its purpose is
_binary_ file located at +/opt/ic/bin/canister_sandbox+ and to the compiler_sandbox
_binary_ file located at +/opt/ic/bin/compiler_sandbox+. Its purpose is
to trigger transition into the +ic_canister_sandbox_t+ domain when executed.

* +ic_canister_sandbox_t+: This label is applied to the canister
Expand Down
1 change: 1 addition & 0 deletions ic-os/guestos/rootfs/prep/ic-node/ic-node.fc
Expand Up @@ -2,6 +2,7 @@
/opt/ic/bin/replica -- gen_context(system_u:object_r:ic_replica_exec_t,s0)
/opt/ic/bin/ic-https-outcalls-adapter -- gen_context(system_u:object_r:ic_http_adapter_exec_t,s0)
/opt/ic/bin/canister_sandbox -- gen_context(system_u:object_r:ic_canister_sandbox_exec_t,s0)
/opt/ic/bin/compiler_sandbox -- gen_context(system_u:object_r:ic_canister_sandbox_exec_t,s0)
/var/lib/ic/backup(/.*)? gen_context(system_u:object_r:ic_data_t,s0)
/var/lib/ic/data(/.*)? gen_context(system_u:object_r:ic_data_t,s0)
/var/lib/ic/data/ic_state/page_deltas(/.*)? gen_context(system_u:object_r:ic_canister_mem_t,s0)
Expand Down
1 change: 1 addition & 0 deletions publish/binaries/BUILD.bazel
Expand Up @@ -23,6 +23,7 @@ NO_STRIP = [
BINARIES = {
"canary-proxy": "//rs/boundary_node/canary_proxy:canary-proxy",
"canister_sandbox": "//rs/canister_sandbox",
"compiler_sandbox": "//rs/canister_sandbox:compiler_sandbox",
"ic-btc-adapter": "//rs/bitcoin/adapter:ic-btc-adapter",
"replica": "//rs/replica",
"boundary-node-prober": "//rs/boundary_node/prober:boundary-node-prober",
Expand Down
8 changes: 8 additions & 0 deletions rs/canister_sandbox/BUILD.bazel
Expand Up @@ -115,3 +115,11 @@ rust_binary(
proc_macro_deps = MACRO_DEPENDENCIES,
deps = DEPENDENCIES + [":backend_lib"],
)

rust_binary(
name = "compiler_sandbox",
srcs = ["bin/compiler.rs"],
crate_name = "canister_sandbox",
version = "0.9.0",
deps = [":backend_lib"],
)
4 changes: 4 additions & 0 deletions rs/canister_sandbox/Cargo.toml
Expand Up @@ -64,6 +64,10 @@ sigsegv_handler_checksum = ["memory_tracker/sigsegv_handler_checksum"]
name = "canister_sandbox"
path = "bin/canister_sandbox.rs"

[[bin]]
name = "compiler_sandbox"
path = "bin/compiler.rs"

[[bin]]
name = "sandbox_launcher"
path = "bin/sandbox_launcher.rs"
Expand Down
3 changes: 3 additions & 0 deletions rs/canister_sandbox/bin/compiler.rs
@@ -0,0 +1,3 @@
fn main() {
ic_canister_sandbox_backend_lib::compiler_sandbox::compiler_sandbox_main();
}
215 changes: 215 additions & 0 deletions rs/canister_sandbox/src/compiler_sandbox.rs
@@ -0,0 +1,215 @@
use serde::{Deserialize, Serialize};
use std::os::unix::{net::UnixStream, prelude::FromRawFd};
use std::sync::Arc;

use crate::launcher_service::LauncherService;
use crate::protocol::transport::{Message, WireMessage};
use crate::{rpc, transport, transport::UnixStreamMuxWriter};
use ic_embedders::{wasm_utils, CompilationResult, SerializedModule, WasmtimeEmbedder};
use ic_interfaces::execution_environment::{HypervisorError, HypervisorResult};
use ic_logger::{error, trace, ReplicaLogger};
use ic_wasm_types::WasmEngineError;

// A helper used for actual compilation in the compiler sandbox
fn compile_and_serialize(
embedder: &WasmtimeEmbedder,
wasm_src: Vec<u8>,
) -> HypervisorResult<(CompilationResult, SerializedModule)> {
let wasm = wasm_utils::decoding::decode_wasm(Arc::new(wasm_src))?;
let (_cache, res) = wasm_utils::compile(embedder, &wasm);
res
}

fn unexpected(desc: &str) -> HypervisorError {
HypervisorError::WasmEngineError(WasmEngineError::Unexpected(desc.to_string()))
}

#[derive(Debug, Serialize, Deserialize, Clone)]
struct PlainWasm {
#[serde(with = "serde_bytes")]
pub wasm_src: Vec<u8>,
}

impl crate::fdenum::EnumerateInnerFileDescriptors for PlainWasm {
fn enumerate_fds<'a>(&'a mut self, _fds: &mut Vec<&'a mut std::os::unix::io::RawFd>) {}
}

#[derive(Debug, Serialize, Deserialize, Clone)]
struct CompiledWasm {
result: HypervisorResult<(CompilationResult, SerializedModule)>,
}

impl crate::fdenum::EnumerateInnerFileDescriptors for CompiledWasm {
fn enumerate_fds<'a>(&'a mut self, _fds: &mut Vec<&'a mut std::os::unix::io::RawFd>) {}
}

impl crate::transport::MuxInto<WireMessage<PlainWasm, CompiledWasm>> for CompiledWasm {
fn wrap(self, cookie: u64) -> WireMessage<PlainWasm, CompiledWasm> {
WireMessage {
cookie,
msg: crate::protocol::transport::Message::Reply(self),
}
}
}

pub struct WasmCompilerProxy {
socket_a: Arc<UnixStream>,
read_worker_handle: Option<std::thread::JoinHandle<()>>,
rpc: crate::rpc::Channel<PlainWasm, CompiledWasm>,
log: ReplicaLogger,
}

impl WasmCompilerProxy {
pub fn start(
log: ReplicaLogger,
launcher: &dyn LauncherService,
exec_path: &str,
argv: &[String],
) -> HypervisorResult<Self> {
let (socket_a, socket_b) = UnixStream::pair()
.map_err(|e| unexpected(&format!("Failed to create a socket: {}", e)))?;
use std::os::unix::io::AsRawFd;

launcher
.launch_compiler(crate::protocol::launchersvc::LaunchCompilerRequest {
exec_path: exec_path.to_string(),
argv: argv.to_vec(),
socket: socket_b.as_raw_fd(),
})
.on_completion(|_| ());

let socket_a = Arc::new(socket_a);
let send_worker =
UnixStreamMuxWriter::<WireMessage<PlainWasm, CompiledWasm>>::new(socket_a.clone());
let tx = send_worker.make_sink::<PlainWasm>();

let reply_collector = Arc::new(crate::rpc::ReplyManager::<CompiledWasm>::new());
let channel = crate::rpc::Channel::new(tx, reply_collector.clone());

let read_worker_handle = Some({
let log = log.clone();
let socket_a = socket_a.clone();
std::thread::spawn(move || {
let reply_collector_clone = reply_collector.clone();
transport::socket_read_messages::<_, _>(
move |message: WireMessage<PlainWasm, CompiledWasm>| match message.msg {
Message::Request(_) => {
error!(
log,
"Compiler proxy received a request. This is unexpected. Cookie: {}",
message.cookie
);
}
Message::Reply(w) => {
use rpc::MessageSink;
reply_collector.handle(message.cookie, w);
}
},
socket_a,
crate::SocketReaderConfig::default(),
);
send_worker.stop();
reply_collector_clone.flush_with_errors(); // We are shutting down. No more replies will come
})
});

Ok(Self {
socket_a,
read_worker_handle,
rpc: channel,
log,
})
}

pub fn initiate_stop(&self) {
// The compiler process should shut down when the socket is closed
let _ignore = self.socket_a.shutdown(std::net::Shutdown::Both);
}

pub fn compile(
&self,
wasm_src: Vec<u8>,
) -> HypervisorResult<(CompilationResult, SerializedModule)> {
let req = PlainWasm { wasm_src };
match self.rpc.call(req, Ok).sync() {
Ok(compiled_wasm) => compiled_wasm.result,
Err(_rpc_err) => {
let msg = "Compiler RPC error. Possibly compiler died".to_string();
error!(&self.log, "{}", msg);
Err(HypervisorError::WasmEngineError(WasmEngineError::Other(
msg,
)))
}
}
}
}

impl Drop for WasmCompilerProxy {
fn drop(&mut self) {
self.initiate_stop();
if let Some(h) = self.read_worker_handle.take() {
h.join().ok();
}
}
}

pub fn compiler_sandbox_main() {
let logger_config = ic_config::logger::Config {
target: ic_config::logger::LogTarget::Stderr,
level: slog::Level::Warning,
..Default::default()
};
let (log, _log_guard) = ic_logger::new_replica_logger_from_config(&logger_config);
let mut embedder_config_arg: Option<crate::EmbeddersConfig> = None;

let mut args = std::env::args();
while let Some(arg) = args.next() {
if arg.as_str() == "--embedder-config" {
let config_arg = args.next().expect("Missing embedder config.");
embedder_config_arg = Some(
serde_json::from_str(config_arg.as_str())
.expect("Could not parse the argument, invalid embedder config value."),
)
}
}
let config = embedder_config_arg
.expect("Error from the sandbox process due to unknown embedder config.");

rayon::ThreadPoolBuilder::new()
.num_threads(config.num_rayon_compilation_threads)
.build_global()
.unwrap();

let embedder = Arc::new(ic_embedders::WasmtimeEmbedder::new(config, log.clone()));

let socket = unsafe { UnixStream::from_raw_fd(3) };
let socket = Arc::new(socket);
let send_worker =
UnixStreamMuxWriter::<WireMessage<PlainWasm, CompiledWasm>>::new(socket.clone());
let tx = send_worker.make_sink::<CompiledWasm>();

let log_clone = log.clone();
transport::socket_read_messages::<_, _>(
move |message: WireMessage<PlainWasm, CompiledWasm>| match message.msg {
Message::Request(w) => {
trace!(log, "Compile request received. Cookie: {}", message.cookie);
let result = compile_and_serialize(&embedder, w.wasm_src);
let cw = CompiledWasm { result };
let call = rpc::Call::new_resolved(Ok(cw));
let call = rpc::Call::new_wrap(call, |x| x);
tx.handle(message.cookie, call.sync().unwrap());
}
Message::Reply(_) => {
error!(
log,
"Compiler received a reply. This is unexpected. Cookie: {}", message.cookie
);
}
},
socket,
transport::SocketReaderConfig::for_sandbox(),
);

send_worker.stop();
trace!(log_clone, "Compiler shut down gracefully");
}

0 comments on commit cd2ceda

Please sign in to comment.