use std::collections::BinaryHeap;
use std::collections::HashMap;
use std::fmt::Display;
use std::fmt::Formatter;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::Duration;
use cheetah_string::CheetahString;
use parking_lot::RwLock;
use rocketmq_error::RocketMQError;
use tokio::sync::Notify;
use tracing::error;
use tracing::info;
use tracing::warn;
use crate::base::transient_store_pool::TransientStorePool;
use crate::log_file::mapped_file::default_mapped_file_impl::DefaultMappedFile;
use crate::log_file::mapped_file::MappedFile;
const WAIT_TIMEOUT: Duration = Duration::from_secs(5);
pub struct AllocateMappedFileService {
request_table: Arc<RwLock<HashMap<String, Arc<AllocateRequest>>>>,
request_queue: Arc<RwLock<BinaryHeap<Arc<AllocateRequest>>>>,
has_exception: Arc<AtomicBool>,
stopped: Arc<AtomicBool>,
notify: Arc<Notify>,
worker_handle: Arc<parking_lot::Mutex<Option<tokio::task::JoinHandle<()>>>>,
transient_store_pool: Option<Arc<TransientStorePool>>,
transient_store_pool_enable: bool,
fast_fail_if_no_buffer: bool,
}
impl Default for AllocateMappedFileService {
fn default() -> Self {
Self::new()
}
}
impl AllocateMappedFileService {
pub fn new_with_config(
transient_store_pool: Option<Arc<TransientStorePool>>,
transient_store_pool_enable: bool,
fast_fail_if_no_buffer: bool,
) -> Self {
let request_table = Arc::new(RwLock::new(HashMap::new()));
let request_queue = Arc::new(RwLock::new(BinaryHeap::new()));
let has_exception = Arc::new(AtomicBool::new(false));
let stopped = Arc::new(AtomicBool::new(false));
let notify = Arc::new(Notify::new());
Self {
request_table,
request_queue,
has_exception,
stopped,
notify,
worker_handle: Arc::new(parking_lot::Mutex::new(None)),
transient_store_pool,
transient_store_pool_enable,
fast_fail_if_no_buffer,
}
}
pub fn new() -> Self {
Self::new_with_config(None, false, false)
}
pub fn start(&self) {
let request_table = self.request_table.clone();
let request_queue = self.request_queue.clone();
let has_exception = self.has_exception.clone();
let stopped = self.stopped.clone();
let notify = self.notify.clone();
let transient_store_pool = self.transient_store_pool.clone();
let handle = tokio::spawn(async move {
Self::run_worker(
request_table,
request_queue,
has_exception,
stopped,
notify,
transient_store_pool,
)
.await;
});
*self.worker_handle.lock() = Some(handle);
info!("AllocateMappedFileService started");
}
async fn run_worker(
request_table: Arc<RwLock<HashMap<String, Arc<AllocateRequest>>>>,
request_queue: Arc<RwLock<BinaryHeap<Arc<AllocateRequest>>>>,
has_exception: Arc<AtomicBool>,
stopped: Arc<AtomicBool>,
notify: Arc<Notify>,
transient_store_pool: Option<Arc<TransientStorePool>>,
) {
info!("AllocateMappedFileService: service started");
while !stopped.load(Ordering::Relaxed) {
tokio::select! {
_ = notify.notified() => {
while !stopped.load(Ordering::Relaxed) {
if !Self::mmap_operation(
&request_table,
&request_queue,
&has_exception,
&transient_store_pool,
).await {
break;
}
}
}
_ = tokio::time::sleep(Duration::from_millis(100)) => {
}
}
}
info!("AllocateMappedFileService: service end");
}
async fn mmap_operation(
request_table: &Arc<RwLock<HashMap<String, Arc<AllocateRequest>>>>,
request_queue: &Arc<RwLock<BinaryHeap<Arc<AllocateRequest>>>>,
has_exception: &Arc<AtomicBool>,
transient_store_pool: &Option<Arc<TransientStorePool>>,
) -> bool {
let req = {
let mut queue = request_queue.write();
queue.pop()
};
let req = match req {
Some(r) => r,
None => return false, };
let expected_request = {
let table = request_table.read();
table.get(&req.file_path).cloned()
};
let expected_request = match expected_request {
Some(r) => r,
None => {
warn!(
"this mmap request expired, maybe cause timeout {} {}",
req.file_path, req.file_size
);
return true;
}
};
if !Arc::ptr_eq(&expected_request, &req) {
warn!(
"never expected here, maybe cause timeout {} {}",
req.file_path, req.file_size
);
return true;
}
if req.mapped_file.read().is_some() {
return true;
}
let result = Self::create_mapped_file(&req, transient_store_pool).await;
match result {
Ok(mapped_file) => {
*req.mapped_file.write() = Some(mapped_file);
has_exception.store(false, Ordering::Relaxed);
req.complete();
true
}
Err(e) => {
error!(
"AllocateMappedFileService: failed to create mapped file {}: {}",
req.file_path, e
);
has_exception.store(true, Ordering::Relaxed);
request_queue.write().push(req);
tokio::time::sleep(Duration::from_millis(1)).await;
false
}
}
}
async fn create_mapped_file(
req: &AllocateRequest,
transient_store_pool: &Option<Arc<TransientStorePool>>,
) -> Result<Arc<DefaultMappedFile>, RocketMQError> {
let start = std::time::Instant::now();
let file_path = req.file_path.clone();
let file_size = req.file_size as u64;
let transient_pool = transient_store_pool.clone();
let mapped_file: DefaultMappedFile =
tokio::task::spawn_blocking(move || -> Result<DefaultMappedFile, RocketMQError> {
if let Some(pool) = transient_pool {
Ok(DefaultMappedFile::new_with_transient_store_pool(
CheetahString::from_string(file_path.clone()),
file_size,
(*pool).clone(),
))
} else {
Ok(DefaultMappedFile::new(
CheetahString::from_string(file_path.clone()),
file_size,
))
}
})
.await
.map_err(|e| RocketMQError::StorageWriteFailed {
path: req.file_path.clone(),
reason: e.to_string(),
})??;
let elapsed = start.elapsed();
if elapsed.as_millis() > 10 {
let queue_size = 0; warn!(
"create mappedFile spent time(ms) {} queue size {} {} {}",
elapsed.as_millis(),
queue_size,
req.file_path,
req.file_size
);
}
Ok(Arc::new(mapped_file))
}
pub async fn put_request_and_return_mapped_file(
&self,
next_file_path: String,
next_next_file_path: String,
file_size: i32,
) -> Result<Option<Arc<DefaultMappedFile>>, RocketMQError> {
let mut can_submit_requests = 2;
if self.transient_store_pool_enable {
if let Some(ref pool) = self.transient_store_pool {
if self.fast_fail_if_no_buffer {
let queue_size = self.request_queue.read().len();
can_submit_requests = pool.available_buffer_nums().saturating_sub(queue_size);
}
}
}
let next_req = Arc::new(AllocateRequest::new(next_file_path.clone(), file_size));
let next_put_ok = {
let mut table = self.request_table.write();
if table.contains_key(&next_file_path) {
false
} else {
table.insert(next_file_path.clone(), next_req.clone());
true
}
};
if next_put_ok {
if can_submit_requests == 0 {
warn!(
"[NOTIFYME]TransientStorePool is not enough, so create mapped file error, RequestQueueSize: {}, \
StorePoolSize: {}",
self.request_queue.read().len(),
self.transient_store_pool
.as_ref()
.map_or(0, |p| p.available_buffer_nums())
);
self.request_table.write().remove(&next_file_path);
return Ok(None);
}
self.request_queue.write().push(next_req.clone());
self.notify.notify_one();
can_submit_requests -= 1;
}
let next_next_req = Arc::new(AllocateRequest::new(next_next_file_path.clone(), file_size));
let next_next_put_ok = {
let mut table = self.request_table.write();
if table.contains_key(&next_next_file_path) {
false
} else {
table.insert(next_next_file_path.clone(), next_next_req.clone());
true
}
};
if next_next_put_ok {
if can_submit_requests == 0 {
warn!(
"[NOTIFYME]TransientStorePool is not enough, so skip preallocate mapped file, RequestQueueSize: \
{}, StorePoolSize: {}",
self.request_queue.read().len(),
self.transient_store_pool
.as_ref()
.map_or(0, |p| p.available_buffer_nums())
);
self.request_table.write().remove(&next_next_file_path);
} else {
self.request_queue.write().push(next_next_req);
self.notify.notify_one();
}
}
if self.has_exception.load(Ordering::Relaxed) {
warn!("AllocateMappedFileService has exception, so return null");
return Ok(None);
}
let result = {
let table = self.request_table.read();
table.get(&next_file_path).cloned()
};
if let Some(req) = result {
let wait_result = tokio::time::timeout(WAIT_TIMEOUT, req.wait()).await;
match wait_result {
Ok(()) => {
self.request_table.write().remove(&next_file_path);
let mapped_file = req.mapped_file.read().clone();
Ok(mapped_file)
}
Err(_) => {
warn!("create mmap timeout {} {}", req.file_path, req.file_size);
Ok(None)
}
}
} else {
error!("find preallocate mmap failed, this never happen");
Ok(None)
}
}
pub async fn submit_request(
&self,
file_path: String,
file_size: u64,
) -> Result<Arc<DefaultMappedFile>, RocketMQError> {
let result = self
.put_request_and_return_mapped_file(
file_path.clone(),
String::new(), file_size as i32,
)
.await?;
result.ok_or_else(|| RocketMQError::StorageWriteFailed {
path: file_path.clone(),
reason: "Allocation failed or timed out".to_string(),
})
}
pub async fn allocate_mapped_file(
&self,
file_path: String,
file_size: u64,
) -> Result<Arc<DefaultMappedFile>, RocketMQError> {
self.submit_request(file_path, file_size).await
}
pub async fn shutdown(&self) {
info!("AllocateMappedFileService: shutting down");
self.stopped.store(true, Ordering::Relaxed);
self.notify.notify_one();
let handle = self.worker_handle.lock().take();
if let Some(handle) = handle {
let _ = tokio::time::timeout(Duration::from_secs(3), handle).await;
}
let table = self.request_table.read();
for req in table.values() {
if let Some(ref mapped_file) = *req.mapped_file.read() {
info!("delete pre allocated mapped file, {}", req.file_path);
mapped_file.destroy(1000);
}
}
info!("AllocateMappedFileService: shutdown complete");
}
pub fn get_service_name(&self) -> &'static str {
"AllocateMappedFileService"
}
pub fn has_exception(&self) -> bool {
self.has_exception.load(Ordering::Relaxed)
}
}
struct AllocateRequest {
file_path: String,
file_size: i32,
completion: Arc<Notify>,
completed: Arc<AtomicBool>,
mapped_file: Arc<RwLock<Option<Arc<DefaultMappedFile>>>>,
}
impl AllocateRequest {
fn new(file_path: String, file_size: i32) -> Self {
Self {
file_path,
file_size,
completion: Arc::new(Notify::new()),
completed: Arc::new(AtomicBool::new(false)),
mapped_file: Arc::new(RwLock::new(None)),
}
}
async fn wait(&self) {
if !self.completed.load(Ordering::Acquire) {
self.completion.notified().await;
}
}
fn complete(&self) {
self.completed.store(true, Ordering::Release);
self.completion.notify_waiters();
}
fn file_offset(&self) -> i64 {
if let Some(separator_idx) = self.file_path.rfind(std::path::MAIN_SEPARATOR) {
if let Ok(offset) = self.file_path[(separator_idx + 1)..].parse::<i64>() {
return offset;
}
}
0
}
}
impl Display for AllocateRequest {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"AllocateRequest[file_path={},file_size={}]",
self.file_path, self.file_size
)
}
}
impl PartialEq for AllocateRequest {
fn eq(&self, other: &Self) -> bool {
self.file_path == other.file_path && self.file_size == other.file_size
}
}
impl Eq for AllocateRequest {}
impl PartialOrd for AllocateRequest {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for AllocateRequest {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
other.file_offset().cmp(&self.file_offset())
}
}