fix(cast): stop hoarding full-track buffers in the DLNA media server
register_audio inserted every cast track's complete bytes into the entries map and NOTHING ever removed them — not stop, not disconnect; only app shutdown dropped the server. A DLNA session therefore grew by one full track per auto-advance (issue #550: 50 MB at launch to 4.5 GB, never released). Three changes: - register_audio/register_file evict every other entry — only the track being cast is servable anyway. - stop_if_cast and disconnect clear the entries outright, so stopping actually releases the buffers. - MediaSource::Data is Arc-backed: the per-request entry clone no longer copies the whole track (it did, on every GET/HEAD/range request), and an in-flight response keeps its own Arc so eviction can never truncate an ongoing range request. Fixes #550 cc
This commit is contained in:
2 files changed
+36
-3
No files matched your search
@@ -48,7 +48,10 @@ struct MediaEntry {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
enum MediaSource {
|
enum MediaSource {
|
||||||
Data(Vec<u8>),
|
// Arc so cloning an entry out of the map for an in-flight request never
|
||||||
|
// copies the whole track, and so eviction can't invalidate a response
|
||||||
|
// that is still being served (#550).
|
||||||
|
Data(std::sync::Arc<Vec<u8>>),
|
||||||
File(PathBuf),
|
File(PathBuf),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,6 +153,14 @@ impl MediaServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get base URL (e.g., "http://192.168.1.100:8080")
|
/// Get base URL (e.g., "http://192.168.1.100:8080")
|
||||||
|
/// Drop every registered entry (releases the full-track buffers). Called
|
||||||
|
/// on cast stop/disconnect so track bytes don't outlive the session (#550).
|
||||||
|
pub fn clear_entries(&self) {
|
||||||
|
if let Ok(mut entries) = self.entries.lock() {
|
||||||
|
entries.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn base_url(&self) -> String {
|
pub fn base_url(&self) -> String {
|
||||||
self.base_url.clone()
|
self.base_url.clone()
|
||||||
}
|
}
|
||||||
@@ -164,10 +175,16 @@ impl MediaServer {
|
|||||||
let entry = MediaEntry {
|
let entry = MediaEntry {
|
||||||
content_type: content_type.to_string(),
|
content_type: content_type.to_string(),
|
||||||
size: data.len() as u64,
|
size: data.len() as u64,
|
||||||
source: MediaSource::Data(data),
|
source: MediaSource::Data(std::sync::Arc::new(data)),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Ok(mut entries) = self.entries.lock() {
|
if let Ok(mut entries) = self.entries.lock() {
|
||||||
|
// Only the track being cast is servable — evict everything else.
|
||||||
|
// Entries held full track bytes and NOTHING ever removed them, so
|
||||||
|
// a DLNA session grew by one full track per auto-advance until app
|
||||||
|
// exit (#550: 50 MB -> 4.5 GB). An in-flight response keeps its
|
||||||
|
// own Arc, so eviction never truncates an ongoing range request.
|
||||||
|
entries.retain(|k, _| *k == id);
|
||||||
entries.insert(id, entry);
|
entries.insert(id, entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,6 +226,9 @@ impl MediaServer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Ok(mut entries) = self.entries.lock() {
|
if let Ok(mut entries) = self.entries.lock() {
|
||||||
|
// Same eviction rule as register_audio (#550): switching to a
|
||||||
|
// local file must also release the previous track's bytes.
|
||||||
|
entries.retain(|k, _| *k == id);
|
||||||
entries.insert(id, entry);
|
entries.insert(id, entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -355,6 +355,11 @@ impl SlintCastService {
|
|||||||
inner.protocol = None;
|
inner.protocol = None;
|
||||||
inner.connected_device_ip = None;
|
inner.connected_device_ip = None;
|
||||||
inner.connected_device_name = None;
|
inner.connected_device_name = None;
|
||||||
|
// Release the served track buffers with the session (#550); the
|
||||||
|
// server itself stays up for the next connect.
|
||||||
|
if let Some(server) = inner.media_server.as_ref() {
|
||||||
|
server.clear_entries();
|
||||||
|
}
|
||||||
inner.current_track_id = None;
|
inner.current_track_id = None;
|
||||||
inner.is_playing = false;
|
inner.is_playing = false;
|
||||||
inner.track_end_detected = false;
|
inner.track_end_detected = false;
|
||||||
@@ -592,7 +597,15 @@ impl SlintCastService {
|
|||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
self.stop_renderer().await?;
|
self.stop_renderer().await?;
|
||||||
self.inner.lock().await.is_playing = false;
|
{
|
||||||
|
let mut inner = self.inner.lock().await;
|
||||||
|
inner.is_playing = false;
|
||||||
|
// Stop = the user is done with this track; release its bytes
|
||||||
|
// instead of holding them until the next cast/app exit (#550).
|
||||||
|
if let Some(server) = inner.media_server.as_ref() {
|
||||||
|
server.clear_entries();
|
||||||
|
}
|
||||||
|
}
|
||||||
self.push_connection_state().await;
|
self.push_connection_state().await;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user