Files
SpotiFLAC-Mobile/rust_backend/crates/extensions/tests/optimization_segments.rs
T
zarzet aa99726439 feat(backend): migrate production backend to Rust
Replace the Go backend with the Rust workspace and route Android/iOS through
UniFFI bindings. Include the migrated extension runtime, network, providers,
media metadata, downloads and library operations, with version 5.0.0+147.

Remove Go sources, adapters, native build selection and CI dependencies.
Build Rust unconditionally and lock the iOS Rust pod using a relative path.
Retain legacy source and migration evidence in a local ignored archive.

Validation: Android Kotlin compile and 53 native tests; Swift Rust-branch
equivalence and syntax; CocoaPods install; workflow, shell, Ruby, plist and
diff checks. Reuse the preceding 100 Rust tests, fmt/Clippy and five-ABI
build checkpoint; no new APK or iOS application build for this cleanup.

Known follow-up: URL/URLSearchParams globals are missing from the Rust JS
runtime. A controlled extension replay confirms a URL-resolution regression;
this commit does not fix that runtime gap.
2026-09-14 22:58:27 +07:00

261 lines
9.4 KiB
Rust

#![cfg(unix)]
use serde_json::{Value, json};
use spotiflac_core::cancellation::{CancellationDomain, CancellationError, CancellationRegistry};
use spotiflac_extensions::environment::ExtensionEnvironment;
use spotiflac_extensions::{ExtensionError, RuntimeLimits};
use std::fs;
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::sync::{Mutex, mpsc};
use std::thread;
use std::time::{Duration, Instant};
const SEGMENTS: usize = 2048;
const WINDOW: usize = 4;
static SERIAL: Mutex<()> = Mutex::new(());
struct Server {
base: String,
arrivals: mpsc::Receiver<usize>,
closed: mpsc::Receiver<()>,
control: mpsc::Sender<bool>,
worker: Option<thread::JoinHandle<()>>,
}
impl Server {
fn new() -> Self {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let base = format!("http://{}", listener.local_addr().unwrap());
listener.set_nonblocking(true).unwrap();
let (sent, arrivals) = mpsc::channel();
let (close, closed) = mpsc::channel();
let (control, commands) = mpsc::channel();
let worker = thread::spawn(move || {
let deadline = Instant::now() + Duration::from_secs(20);
let mut held: Option<TcpStream> = None;
let mut requests = 0;
while Instant::now() < deadline {
match commands.try_recv() {
Ok(true) => {
let mut stream = held.take().expect("segment zero is held");
stream.set_nonblocking(false).unwrap();
stream.write_all(&[0; 3]).unwrap();
}
Ok(false) | Err(mpsc::TryRecvError::Disconnected) => break,
Err(mpsc::TryRecvError::Empty) => {}
}
if let Some(stream) = &mut held {
match stream.read(&mut [0]) {
Ok(0) => {
held.take();
let _ = close.send(());
}
Err(error)
if matches!(
error.kind(),
std::io::ErrorKind::ConnectionReset
| std::io::ErrorKind::ConnectionAborted
) =>
{
held.take();
let _ = close.send(());
}
Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {}
result => panic!("unexpected held segment read: {result:?}"),
}
}
let (mut stream, _) = match listener.accept() {
Ok(connection) => connection,
Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {
if requests == SEGMENTS && held.is_none() {
break;
}
thread::sleep(Duration::from_millis(1));
continue;
}
Err(error) => panic!("accept failed: {error}"),
};
stream.set_nonblocking(false).unwrap();
stream
.set_read_timeout(Some(Duration::from_secs(2)))
.unwrap();
stream
.set_write_timeout(Some(Duration::from_secs(2)))
.unwrap();
let mut request = Vec::new();
while !request.windows(4).any(|part| part == b"\r\n\r\n") {
let mut bytes = [0; 1024];
let count = stream.read(&mut bytes).unwrap();
assert!(count > 0 && request.len() + count <= 8192);
request.extend_from_slice(&bytes[..count]);
}
let index: usize = std::str::from_utf8(&request)
.unwrap()
.split_whitespace()
.nth(1)
.unwrap()
.trim_start_matches('/')
.parse()
.unwrap();
assert!(index < SEGMENTS);
stream
.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 4\r\nConnection: close\r\n\r\n")
.unwrap();
if index == 0 {
stream.write_all(&[0]).unwrap();
stream.set_nonblocking(true).unwrap();
held = Some(stream);
} else {
stream.write_all(&(index as u32).to_be_bytes()).unwrap();
}
requests += 1;
if sent.send(index).is_err() {
break;
}
}
});
Self {
base,
arrivals,
closed,
control,
worker: Some(worker),
}
}
}
impl Drop for Server {
fn drop(&mut self) {
let _ = self.control.send(false);
if let Some(worker) = self.worker.take() {
let _ = worker.join();
}
}
}
fn stalled_window(cancel: bool) {
let _serial = SERIAL.lock().unwrap_or_else(|error| error.into_inner());
let server = Server::new();
let directory = tempfile::tempdir().unwrap();
let environment = ExtensionEnvironment::new(
directory.path(),
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"1",
)
.unwrap();
environment.set_allow_private_network(true).unwrap();
let manifest = json!({"name":"example.segments","version":"1","description":"Generic segment fixture",
"type":["download_provider"],"permissions":{"file":true,"network":["127.0.0.1"],"allowHttp":true}}).to_string();
let runtime = environment
.load(
&manifest,
r#"registerExtension({run(base,count,parallel){
return file.downloadSegments(Array.from({length:count},(_,index)=>base+'/'+index),
'result.bin',{maxParallel:parallel,maxAttempts:1});
}});"#,
RuntimeLimits::default(),
)
.unwrap();
let root = directory.path().join("example.segments");
let target = root.join("result.bin");
fs::write(&target, b"original").unwrap();
let descriptors = fs::read_dir("/dev/fd").unwrap().count();
let registry = CancellationRegistry::new(CancellationDomain::Download);
let lease = std::sync::Arc::new(registry.acquire("window").unwrap());
let arguments = json!([server.base, SEGMENTS, WINDOW]).to_string();
let (done, result) = mpsc::channel();
let worker = thread::spawn(move || {
let value = runtime.call_download("run", &arguments, Some(lease), 10_000);
let _ = done.send(value);
});
let mut initial = Vec::new();
for _ in 0..WINDOW {
initial.push(
server
.arrivals
.recv_timeout(Duration::from_secs(5))
.unwrap(),
);
}
initial.sort_unstable();
assert_eq!(initial, (0..WINDOW).collect::<Vec<_>>());
assert_eq!(
server.arrivals.recv_timeout(Duration::from_millis(150)),
Err(mpsc::RecvTimeoutError::Timeout)
);
let staged = fs::read_dir(&root)
.unwrap()
.filter(|entry| {
entry
.as_ref()
.unwrap()
.file_name()
.to_string_lossy()
.contains(".segment.")
})
.count();
assert_eq!(
staged, WINDOW,
"active and completed segment files share the window"
);
assert!(
fs::read_dir("/dev/fd").unwrap().count() <= descriptors + WINDOW * 4 + 16,
"open descriptors must depend on the window, not the segment count"
);
if cancel {
registry.cancel("window").unwrap();
} else {
server.control.send(true).unwrap();
}
let outcome = result.recv_timeout(Duration::from_secs(15)).unwrap();
worker.join().unwrap();
if cancel {
assert_eq!(
outcome,
Err(ExtensionError::Cancelled(
CancellationError::DownloadCancelled
))
);
server.closed.recv_timeout(Duration::from_secs(2)).unwrap();
assert_eq!(fs::read(&target).unwrap(), b"original");
assert!(matches!(
server.arrivals.try_recv(),
Err(mpsc::TryRecvError::Empty)
));
} else {
let value: Value = serde_json::from_str(&outcome.unwrap()).unwrap();
assert_eq!(value["success"], true);
let expected: Vec<_> = (0..SEGMENTS as u32).flat_map(u32::to_be_bytes).collect();
assert_eq!(fs::read(&target).unwrap(), expected);
let mut remaining: Vec<_> = (WINDOW..SEGMENTS)
.map(|_| {
server
.arrivals
.recv_timeout(Duration::from_secs(2))
.unwrap()
})
.collect();
remaining.sort_unstable();
assert_eq!(remaining, (WINDOW..SEGMENTS).collect::<Vec<_>>());
}
assert!(fs::read_dir(&root).unwrap().all(|entry| {
!entry
.unwrap()
.file_name()
.to_string_lossy()
.contains(".partial")
}));
environment.shutdown();
}
#[test]
fn stalled_first_segment_bounds_files_and_requests_then_assembles_in_order() {
stalled_window(false);
}
#[test]
fn cancellation_of_stalled_segment_window_joins_workers_and_preserves_output() {
stalled_window(true);
}