Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ build64
build_arm64/
node/input_methods/McBopomofo
installer/*.exe
PIMELauncher/target/
.claude/
cleanup_x64.ps1
replace_x64_dll.ps1
43 changes: 29 additions & 14 deletions PIMELauncher/src/backend_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,31 @@ impl BackendManager {

/// Retrieves a channel to send messages directly to the backend.
pub async fn get_backend_input(&self, backend_name: &str) -> Option<mpsc::Sender<String>> {
let mut state = self.state.lock().await;
if !state.backends.contains_key(backend_name) {
// Dynamically look up the backend configuration
if let Some(config) = self.registry.get_backend(backend_name) {
let backend = self.spawn_backend_process(config).await;
state.backends.insert(backend_name.to_string(), backend);
} else {
// Fast path: backend already running — take and release the lock immediately.
{
let state = self.state.lock().await;
if let Some(b) = state.backends.get(backend_name) {
return Some(b.stdin_tx.clone());
}
}

// Slow path: spawn the backend without holding the lock, so other clients
// are not blocked during the (potentially multi-second) process startup.
let config = match self.registry.get_backend(backend_name) {
Some(c) => c.clone(),
None => {
error!("Unknown backend requested: {}", backend_name);
return None;
}
}
};
let backend = self.spawn_backend_process(&config).await;

// Re-acquire lock to insert; use entry() so a concurrent spawn doesn't overwrite.
let mut state = self.state.lock().await;
state
.backends
.entry(backend_name.to_string())
.or_insert(backend);
state.backends.get(backend_name).map(|b| b.stdin_tx.clone())
}

Expand Down Expand Up @@ -213,6 +227,7 @@ impl BackendManager {
.current_dir(&working_dir)
.creation_flags(CREATE_NO_WINDOW)
.env("PYTHONIOENCODING", "utf-8:ignore")
.env("PYTHONUNBUFFERED", "1")
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
Expand Down Expand Up @@ -289,14 +304,14 @@ impl BackendManager {
loop {
tokio::select! {
msg = stdin_rx.recv() => {
let Some(data) = msg else {
let Some(data) = msg else {
info!("Backend {} stdin channel closed. Exiting input loop.", backend_name);
break;
break;
};
let now = Self::current_ms();
last_request_time = Some(now);
info!("Backend {} received request from channel. Data len: {}. req_t={}", backend_name, data.len(), now);

// LinesCodec expects data without the newline, it will add it for us.
let write_res = tokio::time::timeout(Duration::from_secs(5), stdin_writer.send(data)).await;
if let Err(_) = write_res {
Expand All @@ -315,12 +330,12 @@ impl BackendManager {
if let Some(req_t) = last_request_time {
let last_out = last_output_time.load(Ordering::SeqCst);
// Log tick status occasionally or at least for debugging
info!("Watchdog tick for {}: last_out={}, req_t={}, now={}, delta={}",
debug!("Watchdog tick for {}: last_out={}, req_t={}, now={}, delta={}",
backend_name, last_out, req_t, now, now as i64 - req_t as i64);

// If no output has been received since the last request and it's been more than 15 seconds
if last_out < req_t && (now - req_t) > 15000 {
error!("Backend {} seems to be hung (no output for 15s after request). last_out={}, req_t={}, now={}. Forcing restart.",
error!("Backend {} seems to be hung (no output for 15s after request). last_out={}, req_t={}, now={}. Forcing restart.",
backend_name, last_out, req_t, now);
let _ = child_process.kill().await;
break;
Expand Down
1 change: 1 addition & 0 deletions PIMELauncher/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ async fn main() {
// Writing to a non-existent stdout in a GUI subsystem app can cause hangs.
tracing_subscriber::fmt()
.with_ansi(false)
.with_max_level(tracing::Level::WARN)
.with_writer(std::io::sink)
.init();
}
Expand Down
12 changes: 3 additions & 9 deletions PIMELauncher/src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,9 @@ pub fn parse_client_handshake(first_line: &str) -> Result<String, String> {
/// Expects the format `PIME_MSG|<client_id>|<payload>`.
/// Returns `Some((client_id, payload))` if valid.
pub fn parse_backend_output(line: &str) -> Option<(String, String)> {
if line.starts_with("PIME_MSG|") {
let parts: Vec<&str> = line.splitn(3, '|').collect();
if parts.len() == 3 {
let client_id = parts[1].to_string();
let payload = parts[2].to_string();
return Some((client_id, payload));
}
}
None
let rest = line.strip_prefix("PIME_MSG|")?;
let sep = rest.find('|')?;
Some((rest[..sep].to_string(), rest[sep + 1..].to_string()))
}

/// Formats a message to be sent to a backend process.
Expand Down
Loading