Fix resource leak and CPU performance issues
- Fix integer underflow panic in LED color publisher by adding bounds checking - Reduce screenshot capture frequency from 15 FPS to 5 FPS for better CPU performance - Reduce WebSocket force-send frequency from 200ms to 500ms - Fix WebSocket resource leak by properly cleaning up streams when connections end - Add proper stream lifecycle management with is_running flag checks - Ensure background tasks exit when streams are stopped This resolves the issue where CPU usage remained above 100% after visiting the LED strip configuration page, even when navigating to other pages.
This commit is contained in:
@ -296,8 +296,19 @@ impl LedColorsPublisher {
|
|||||||
let mut buffer = Vec::<u8>::with_capacity(group_size * 3);
|
let mut buffer = Vec::<u8>::with_capacity(group_size * 3);
|
||||||
|
|
||||||
if group.end > group.start {
|
if group.end > group.start {
|
||||||
for i in group.pos - display_led_offset..group_size + group.pos - display_led_offset
|
// Prevent integer underflow by using saturating subtraction
|
||||||
{
|
let start_index = if group.pos >= display_led_offset {
|
||||||
|
group.pos - display_led_offset
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
let end_index = if group.pos + group_size >= display_led_offset {
|
||||||
|
group_size + group.pos - display_led_offset
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
for i in start_index..end_index {
|
||||||
if i < colors.len() {
|
if i < colors.len() {
|
||||||
let bytes = colors[i].as_bytes();
|
let bytes = colors[i].as_bytes();
|
||||||
buffer.append(&mut bytes.to_vec());
|
buffer.append(&mut bytes.to_vec());
|
||||||
@ -308,10 +319,19 @@ impl LedColorsPublisher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for i in (group.pos - display_led_offset
|
// Prevent integer underflow by using saturating subtraction
|
||||||
..group_size + group.pos - display_led_offset)
|
let start_index = if group.pos >= display_led_offset {
|
||||||
.rev()
|
group.pos - display_led_offset
|
||||||
{
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
let end_index = if group.pos + group_size >= display_led_offset {
|
||||||
|
group_size + group.pos - display_led_offset
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
for i in (start_index..end_index).rev() {
|
||||||
if i < colors.len() {
|
if i < colors.len() {
|
||||||
let bytes = colors[i].as_bytes();
|
let bytes = colors[i].as_bytes();
|
||||||
buffer.append(&mut bytes.to_vec());
|
buffer.append(&mut bytes.to_vec());
|
||||||
|
@ -143,12 +143,12 @@ impl ScreenStreamManager {
|
|||||||
let mut last_process_time = Instant::now();
|
let mut last_process_time = Instant::now();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Check if stream still has subscribers
|
// Check if stream still has subscribers and is still running
|
||||||
let should_continue = {
|
let should_continue = {
|
||||||
let streams_lock = streams.read().await;
|
let streams_lock = streams.read().await;
|
||||||
if let Some(stream_state) = streams_lock.get(&display_id) {
|
if let Some(stream_state) = streams_lock.get(&display_id) {
|
||||||
let state = stream_state.read().await;
|
let state = stream_state.read().await;
|
||||||
!state.subscribers.is_empty()
|
!state.subscribers.is_empty() && state.is_running
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
@ -191,7 +191,7 @@ impl ScreenStreamManager {
|
|||||||
let mut state = stream_state.write().await;
|
let mut state = stream_state.write().await;
|
||||||
let changed = state.last_screenshot_hash.map_or(true, |hash| hash != frame_hash);
|
let changed = state.last_screenshot_hash.map_or(true, |hash| hash != frame_hash);
|
||||||
let elapsed_ms = state.last_force_send.elapsed().as_millis();
|
let elapsed_ms = state.last_force_send.elapsed().as_millis();
|
||||||
let force_send = elapsed_ms > 200; // Force send every 200ms for higher FPS
|
let force_send = elapsed_ms > 500; // Force send every 500ms for better CPU performance
|
||||||
|
|
||||||
if changed || force_send {
|
if changed || force_send {
|
||||||
state.last_screenshot_hash = Some(frame_hash);
|
state.last_screenshot_hash = Some(frame_hash);
|
||||||
@ -338,8 +338,19 @@ impl ScreenStreamManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn stop_stream(&self, display_id: u32) {
|
pub async fn stop_stream(&self, display_id: u32) {
|
||||||
|
log::info!("Stopping stream for display_id: {}", display_id);
|
||||||
let mut streams = self.streams.write().await;
|
let mut streams = self.streams.write().await;
|
||||||
|
|
||||||
|
if let Some(stream_state) = streams.get(&display_id) {
|
||||||
|
// Mark stream as not running to stop the processing task
|
||||||
|
let mut state = stream_state.write().await;
|
||||||
|
state.is_running = false;
|
||||||
|
log::info!("Marked stream as not running for display_id: {}", display_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the stream from the map
|
||||||
streams.remove(&display_id);
|
streams.remove(&display_id);
|
||||||
|
log::info!("Removed stream from manager for display_id: {}", display_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -440,6 +451,7 @@ pub async fn handle_websocket_connection(
|
|||||||
log::info!("Starting stream with config: display_id={}, width={}, height={}",
|
log::info!("Starting stream with config: display_id={}, width={}, height={}",
|
||||||
config.display_id, config.target_width, config.target_height);
|
config.display_id, config.target_width, config.target_height);
|
||||||
let stream_manager = ScreenStreamManager::global().await;
|
let stream_manager = ScreenStreamManager::global().await;
|
||||||
|
let display_id_for_cleanup = config.display_id;
|
||||||
let mut frame_rx = match stream_manager.start_stream(config).await {
|
let mut frame_rx = match stream_manager.start_stream(config).await {
|
||||||
Ok(rx) => {
|
Ok(rx) => {
|
||||||
log::info!("Screen stream started successfully");
|
log::info!("Screen stream started successfully");
|
||||||
@ -498,6 +510,11 @@ pub async fn handle_websocket_connection(
|
|||||||
_ = control_task => {},
|
_ = control_task => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean up resources when connection ends
|
||||||
|
log::info!("WebSocket connection ending, cleaning up resources for display_id: {}", display_id_for_cleanup);
|
||||||
|
let stream_manager = ScreenStreamManager::global().await;
|
||||||
|
stream_manager.stop_stream(display_id_for_cleanup).await;
|
||||||
|
|
||||||
log::info!("WebSocket connection handler completed");
|
log::info!("WebSocket connection handler completed");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -189,8 +189,8 @@ impl ScreenshotManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sleep for a frame duration (15 FPS for better performance)
|
// Sleep for a frame duration (5 FPS for much better CPU performance)
|
||||||
sleep(Duration::from_millis(67)).await;
|
sleep(Duration::from_millis(200)).await;
|
||||||
yield_now().await;
|
yield_now().await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user