From 63612825b01bc4ea1571ffc376ee8c9bf5ff1e69 Mon Sep 17 00:00:00 2001 From: Avi Cohen Date: Thu, 28 Nov 2024 12:36:00 +0200 Subject: [PATCH] Remove terminate request variant --- .../src/state/contract_class_manager.rs | 220 ++++++------------ crates/blockifier/src/state/global_cache.rs | 2 +- 2 files changed, 73 insertions(+), 149 deletions(-) diff --git a/crates/blockifier/src/state/contract_class_manager.rs b/crates/blockifier/src/state/contract_class_manager.rs index a458bf707ab..d94728fba89 100644 --- a/crates/blockifier/src/state/contract_class_manager.rs +++ b/crates/blockifier/src/state/contract_class_manager.rs @@ -1,7 +1,6 @@ -use std::fmt::{Display, Formatter}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{sync_channel, Receiver, SyncSender, TrySendError}; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use std::thread::JoinHandle; use log::{error, info}; @@ -17,118 +16,70 @@ use crate::state::global_cache::{CachedCairoNative, ContractCaches}; const CHANNEL_SIZE: usize = 1000; -/// Represents a request for the compilation handler. +/// Represents a request to compile a sierra contract class to a native compiled class. /// -/// # `Request` variants: -/// * `Terminate` - signals the compilation handler to terminate. -/// * `Compile` - requests compilation of a sierra contract class to a native compiled class. -/// -/// # `Request::Compile` fields: +/// # Fields: /// * `class_hash` - used to identify the contract class in the cache. /// * `sierra_contract_class` - the sierra contract class to be compiled. /// * `casm_compiled_class` - stored in [`NativeContractClassV1`] to allow fallback to cairo_vm /// execution in case of unxecpected failure during native execution. -pub enum Request { - Terminate, - Compile(ClassHash, Arc, ContractClassV1), -} - -impl Display for Request { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Request::Terminate => write!(f, "Terminate"), - Request::Compile(class_hash, _, _) => { - write!(f, "Request(class_hash: {:?})", class_hash) - } - } - } -} +type CompilationRequest = (ClassHash, Arc, ContractClassV1); /// Manages the global cache of contract classes and handles sierra-to-native compilation requests. struct ContractClassManager { // The global cache of contract classes: casm, sierra, and native. - contract_caches: ContractCaches, + contract_caches: Arc, // The sending half of the compilation request channel. - sender: SyncSender, + sender: SyncSender, // A flag that signals the termination of the compilation handler. - stop_marker: AtomicBool, + stop_marker: Arc, // The join handle to the thread running the compilation handler. - join_handle: Mutex>>, + join_handle: JoinHandle<()>, } impl ContractClassManager { /// Creates a new contract class manager and spawns a thread that listens for compilation /// requests and processes them (a.k.a. the compilation handler). - /// Returns an `Arc` to the created contract class manager. - pub fn start(contract_caches: ContractCaches) -> Arc { + /// Returns the contract class manager. + pub fn start(contract_caches: ContractCaches) -> ContractClassManager { // TODO(Avi, 15/12/2024): Add the size of the channel to the config. + let contract_caches = Arc::new(contract_caches); let (sender, receiver) = sync_channel(CHANNEL_SIZE); let compiler_config = SierraToCasmCompilationConfig::default(); let compiler = CommandLineCompiler::new(compiler_config); + let stop_marker = Arc::new(AtomicBool::new(false)); - // Create the contract class manager. - let contract_class_manager = Arc::new(ContractClassManager { - contract_caches, - sender, - stop_marker: AtomicBool::new(false), - // Store `None` in a mutex-guarded Option to allow setting the join handle after - // spawning the thread. - join_handle: Mutex::new(None), - }); - - // Spawn a thread running the compilation handler. let join_handle = std::thread::spawn({ - let contract_class_manager = Arc::clone(&contract_class_manager); - move || contract_class_manager.run_compilation_handler(receiver, compiler) + let contract_caches = Arc::clone(&contract_caches); + let stop_marker = Arc::clone(&stop_marker); + move || run_compilation_handler(contract_caches, receiver, compiler, stop_marker) }); - // Store the join handle to allow waiting for the thread to finish. - let mut mutex_guard = contract_class_manager - .join_handle - .lock() - .expect("No other thread should access the join handle."); - *mutex_guard = Some(join_handle); - drop(mutex_guard); - - // Return the contract class manager. - contract_class_manager + ContractClassManager { contract_caches, sender, stop_marker, join_handle } } /// Stops the compilation handler. pub fn stop(&self) { self.stop_marker.store(true, Ordering::Relaxed); - // For cases where the compilation request channel is empty, send a termination request to - // unblock the compilation handler. This is necessary because the handler is - // non-busy-waiting for requests. - self.send_request(Request::Terminate); } - /// Sends a request to the compilation handler. - pub fn send_request(&self, request: Request) { + /// Sends a compilation request to the compilation handler. Does not block the sender. Logs an + /// error is the channel is full. + pub fn send_compilation_request(&self, request: CompilationRequest) { self.cache_request_contracts(&request); - - match request { - Request::Terminate => { - // When sending a termination request, block the sender until the request is sent. - self.sender.send(request).expect("Compilation request channel is closed."); + // TODO(Avi, 15/12/2024): Check for duplicated requests. + self.sender.try_send(request).map_err(|err| match err { + TrySendError::Full((class_hash, _, _)) => { + error!( + "Compilation request channel is full (size: {}). Compilation request for \ + class hash {} was not sent.", + CHANNEL_SIZE, class_hash + ) } - Request::Compile(_, _, _) => { - // When sending a compilation request, send the request without blocking the sender. - // TODO(Avi, 15/12/2024): Check for duplicated requests. - self.sender.try_send(request).map_err(|err| match err { - TrySendError::Full(request) => { - error!( - "Compilation request channel is full (size: {}). Compilation request \ - {} was not sent.", - CHANNEL_SIZE, request - ) - } - TrySendError::Disconnected(_) => { - panic!("Compilation request channel is closed.") - } - }); + TrySendError::Disconnected(_) => { + panic!("Compilation request channel is closed.") } - } + }); } /// Returns the native compiled class for the given class hash, if it exists in cache. @@ -146,80 +97,53 @@ impl ContractClassManager { self.contract_caches.get_casm(class_hash) } - /// Handles requests on the compilation request channel. - /// If no request is available, non-busy-waits until a request is available. - fn run_compilation_handler(&self, receiver: Receiver, compiler: CommandLineCompiler) { - info!("Compilation handler started."); - for request in receiver.iter() { - if self.stopped() { - info!("Compilation handler terminated."); - return; - } - match request { - Request::Terminate => { - info!("Compilation handler terminated."); - return; - } - Request::Compile(class_hash, sierra, casm) => { - if self.contract_caches.get_native(&class_hash).is_some() { - // The contract class is already compiled to native - skip the compilation. - continue; - } - // TODO(Avi): Convert `sierra_contract_class` to - // `cairo_lang_starknet_classes::contract_class::ContractClass` - let compilation_result = compiler.compile_to_native(sierra.into()); - match compilation_result { - Ok(executor) => { - let native_compiled_class = NativeContractClassV1::new(executor, casm); - self.contract_caches.set_native( - class_hash, - CachedCairoNative::Compiled(native_compiled_class), - ); - } - Err(err) => { - error!("Error compiling contract class: {}", err); - self.contract_caches - .set_native(class_hash, CachedCairoNative::CompilationFailed); - } - } - } - } - } + /// Waits for the compilation handler to terminate. + pub fn join(self) { + self.join_handle.join().unwrap(); } /// Caches the sierra and casm contract classes of a compilation request. - fn cache_request_contracts(&self, request: &Request) { - match request { - Request::Terminate => {} - Request::Compile(class_hash, sierra, casm) => { - self.contract_caches.set_sierra(class_hash.clone(), sierra.clone()); - let cached_casm = RunnableContractClass::from(casm.clone()); - self.contract_caches.set_casm(class_hash.clone(), cached_casm); - } - } - } - - /// Returns true if the compilation handler has been stopped. - fn stopped(&self) -> bool { - self.stop_marker.load(Ordering::Relaxed) + fn cache_request_contracts(&self, request: &CompilationRequest) { + let (class_hash, sierra, casm) = request.clone(); + self.contract_caches.set_sierra(class_hash, sierra); + let cached_casm = RunnableContractClass::from(casm); + self.contract_caches.set_casm(class_hash, cached_casm); } } -impl Drop for ContractClassManager { - /// Ensures the thread running the compilation handler is terminated when the contract class - /// manager is dropped. - fn drop(&mut self) { - self.stop(); - let join_handle = self - .join_handle - .lock() - .expect("The lock should only be accessed when the contract class manager is dropped.") - .take() - .expect( - "The join handle should be set when the thread running the compilation handler is - spawned.", - ); - - join_handle.join().unwrap(); +/// Handles compilation requests from the channel. +/// If no request is available, non-busy-waits until a request is available. +/// When the sender is dropped, the compilation handler processes all pending requests and +/// terminates. +fn run_compilation_handler( + contract_caches: Arc, + receiver: Receiver, + compiler: CommandLineCompiler, + stop_marker: Arc, +) { + info!("Compilation handler started."); + for (class_hash, sierra, casm) in receiver.iter() { + if stop_marker.load(Ordering::Relaxed) { + info!("Compilation handler terminated."); + return; + } + if contract_caches.get_native(&class_hash).is_some() { + // The contract class is already compiled to native - skip the compilation. + continue; + } + // TODO(Avi): Convert `sierra_contract_class` to + // `cairo_lang_starknet_classes::contract_class::ContractClass` + let compilation_result = compiler.compile_to_native(sierra.into()); + match compilation_result { + Ok(executor) => { + let native_compiled_class = NativeContractClassV1::new(executor, casm); + contract_caches + .set_native(class_hash, CachedCairoNative::Compiled(native_compiled_class)); + } + Err(err) => { + error!("Error compiling contract class: {}", err); + contract_caches.set_native(class_hash, CachedCairoNative::CompilationFailed); + } + } } } diff --git a/crates/blockifier/src/state/global_cache.rs b/crates/blockifier/src/state/global_cache.rs index 54a5df9fa3f..fdc03799bcb 100644 --- a/crates/blockifier/src/state/global_cache.rs +++ b/crates/blockifier/src/state/global_cache.rs @@ -13,7 +13,7 @@ use crate::execution::native::contract_class::NativeContractClassV1; type ContractLRUCache = SizedCache; pub type LockedClassCache<'a, T> = MutexGuard<'a, ContractLRUCache>; #[derive(Debug, Clone)] -// Thread-safe LRU cache for contract classes (Seirra or compiled Casm/Native), optimized for +// Thread-safe LRU cache for contract classes (Seirra or compiled Casm/Native), optimized for // inter-language sharing when `blockifier` compiles as a shared library. // TODO(Yoni, 1/1/2025): consider defining CachedStateReader. pub struct GlobalContractCache(pub Arc>>);