Skip to content

Commit

Permalink
Remove terminate request variant
Browse files Browse the repository at this point in the history
  • Loading branch information
avi-starkware committed Nov 28, 2024
1 parent 6cf7871 commit 6361282
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 149 deletions.
220 changes: 72 additions & 148 deletions crates/blockifier/src/state/contract_class_manager.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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<SierraContractClass>, 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<SierraContractClass>, 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<ContractCaches>,
// The sending half of the compilation request channel.
sender: SyncSender<Request>,
sender: SyncSender<CompilationRequest>,
// A flag that signals the termination of the compilation handler.
stop_marker: AtomicBool,
stop_marker: Arc<AtomicBool>,
// The join handle to the thread running the compilation handler.
join_handle: Mutex<Option<JoinHandle<()>>>,
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<ContractClassManager> {
/// 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.
Expand All @@ -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<Request>, 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<ContractCaches>,
receiver: Receiver<CompilationRequest>,
compiler: CommandLineCompiler,
stop_marker: Arc<AtomicBool>,
) {
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);
}
}
}
}
2 changes: 1 addition & 1 deletion crates/blockifier/src/state/global_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::execution::native::contract_class::NativeContractClassV1;
type ContractLRUCache<T> = SizedCache<ClassHash, T>;
pub type LockedClassCache<'a, T> = MutexGuard<'a, ContractLRUCache<T>>;
#[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<T: Clone>(pub Arc<Mutex<ContractLRUCache<T>>>);
Expand Down

0 comments on commit 6361282

Please sign in to comment.