Skip to content

Commit

Permalink
release-app feat: created watchdog to make sure pipes (bun) properly …
Browse files Browse the repository at this point in the history
…ends when the parent (CLI or sidecar) is stopped which will solve especially issues with windows failing to update app because bun was running in the background and user had to kill bun through task manager
  • Loading branch information
louis030195 committed Feb 8, 2025
1 parent 1c3be2e commit 067a739
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 3 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ resolver = "2"


[workspace.package]
version = "0.2.35"
version = "0.2.36"
authors = ["louis030195 <[email protected]>"]
description = ""
repository = "https://github.com/mediar-ai/screenpipe"
Expand Down
2 changes: 1 addition & 1 deletion screenpipe-app-tauri/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "screenpipe-app"
version = "0.32.0"
version = "0.32.1"
description = ""
authors = ["you"]
license = ""
Expand Down
197 changes: 196 additions & 1 deletion screenpipe-core/src/pipes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ mod pipes {
use std::future::Future;
use std::path::PathBuf;
use std::pin::Pin;
use std::time::{SystemTime, UNIX_EPOCH};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use tokio::process::Command;
use tokio::sync::watch;

Expand Down Expand Up @@ -80,6 +80,183 @@ mod pipes {
.to_string()
}

async fn create_watchdog_script(parent_pid: u32, child_pid: u32) -> Result<PathBuf> {
let script_content = if cfg!(windows) {
format!(
r#"
$parentPid = {parent_pid}
$childPid = {child_pid}
while ($true) {{
try {{
$parent = Get-Process -Id $parentPid -ErrorAction Stop
Start-Sleep -Seconds 1
}} catch {{
try {{
Stop-Process -Id $childPid -Force
}} catch {{
# Process might already be gone
}}
exit
}}
}}
"#
)
} else {
format!(
r#"#!/bin/bash
set -x # Enable debug mode
parent_pid={parent_pid}
child_pid={child_pid}
echo "[$(date)] Watchdog started - monitoring parent PID: $parent_pid, child PID: $child_pid"
find_all_children() {{
local parent=$1
local children=$(pgrep -P $parent)
echo $children
for child in $children; do
find_all_children $child
done
}}
cleanup() {{
echo "[$(date)] Starting cleanup..."
# Get all child processes recursively
all_children=$(find_all_children $child_pid)
echo "[$(date)] Found child processes: $all_children"
# Try process group first
child_pgid=$(ps -o pgid= -p $child_pid 2>/dev/null | tr -d ' ')
if [ ! -z "$child_pgid" ]; then
echo "[$(date)] Killing process group $child_pgid"
kill -TERM -$child_pgid 2>/dev/null || true
sleep 1
kill -KILL -$child_pgid 2>/dev/null || true
fi
# Kill all children we found
if [ ! -z "$all_children" ]; then
echo "[$(date)] Killing all child processes: $all_children"
kill -TERM $all_children 2>/dev/null || true
sleep 1
kill -KILL $all_children 2>/dev/null || true
fi
# Kill the main process
echo "[$(date)] Killing main process $child_pid"
kill -TERM $child_pid 2>/dev/null || true
sleep 1
kill -KILL $child_pid 2>/dev/null || true
# Verify all processes are gone
sleep 1
ps aux | grep -E "($child_pid|$all_children)" | grep -v grep
exit 0
}}
trap cleanup SIGTERM SIGINT
while true; do
if ! ps -p $parent_pid > /dev/null 2>&1; then
echo "[$(date)] Parent process ($parent_pid) not found, terminating child processes"
cleanup
exit
fi
sleep 1
done
"#
)
};

let temp_dir = std::env::temp_dir();
let script_name = if cfg!(windows) {
format!("watchdog_{parent_pid}_{child_pid}.ps1")
} else {
format!("watchdog_{parent_pid}_{child_pid}.sh")
};
let script_path = temp_dir.join(script_name);

tokio::fs::write(&script_path, script_content).await?;

if !cfg!(windows) {
use std::os::unix::fs::PermissionsExt;

Check failure on line 185 in screenpipe-core/src/pipes.rs

View workflow job for this annotation

GitHub Actions / test-windows

failed to resolve: could not find `unix` in `os`

Check failure on line 185 in screenpipe-core/src/pipes.rs

View workflow job for this annotation

GitHub Actions / test-windows

failed to resolve: could not find `unix` in `os`

Check failure on line 185 in screenpipe-core/src/pipes.rs

View workflow job for this annotation

GitHub Actions / test-windows

failed to resolve: could not find `unix` in `os`
let mut perms = tokio::fs::metadata(&script_path).await?.permissions();
perms.set_mode(0o755);

Check failure on line 187 in screenpipe-core/src/pipes.rs

View workflow job for this annotation

GitHub Actions / test-windows

no method named `set_mode` found for struct `Permissions` in the current scope

Check failure on line 187 in screenpipe-core/src/pipes.rs

View workflow job for this annotation

GitHub Actions / test-windows

no method named `set_mode` found for struct `Permissions` in the current scope

Check failure on line 187 in screenpipe-core/src/pipes.rs

View workflow job for this annotation

GitHub Actions / test-windows

no method named `set_mode` found for struct `Permissions` in the current scope
tokio::fs::set_permissions(&script_path, perms).await?;
}

Ok(script_path)
}

async fn spawn_watchdog(parent_pid: u32, child_pid: u32) -> Result<tokio::process::Child> {
let script_path = create_watchdog_script(parent_pid, child_pid).await?;

info!(
"Spawning watchdog process for parent_pid={}, child_pid={}",
parent_pid, child_pid
);
info!("Watchdog script path: {:?}", script_path);

// Create a log file for the watchdog
let log_path =
std::env::temp_dir().join(format!("watchdog_{}_{}.log", parent_pid, child_pid));

let child = if cfg!(windows) {
Command::new("powershell")
.arg("-ExecutionPolicy")
.arg("Bypass")
.arg("-File")
.arg(&script_path)
.stdout(std::fs::File::create(&log_path)?)
.stderr(std::fs::File::create(&log_path)?)
.spawn()?
} else {
Command::new("bash")
.arg(&script_path)
.stdout(std::fs::File::create(&log_path)?)
.stderr(std::fs::File::create(&log_path)?)
.spawn()?
};

if let Some(id) = child.id() {
info!("Watchdog process spawned with PID: {}", id);

// Verify the watchdog is running
tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(1)).await;
let output = Command::new("ps")
.args(["-p", &id.to_string()])
.output()
.await;

match output {
Ok(output) => {
if output.status.success() {
info!("Watchdog process verified running with PID: {}", id);
} else {
error!("Watchdog process not found after spawn! PID: {}", id);
}
}
Err(e) => error!("Failed to verify watchdog process: {}", e),
}
});
}

// Clean up script after a delay
let script_path_clone = script_path.clone();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(30)).await;
if let Err(e) = tokio::fs::remove_file(&script_path_clone).await {
error!("Failed to remove watchdog script: {}", e);
}
});

Ok(child)
}

pub async fn run_pipe(
pipe: &str,
screenpipe_dir: PathBuf,
Expand Down Expand Up @@ -318,6 +495,24 @@ mod pipes {
debug!("[{}] streaming logs for next.js pipe", pipe);
stream_logs(pipe, &mut child).await?;

let child_pid = child.id().expect("Failed to get child PID") as u32;
let parent_pid = std::process::id();

info!("Spawned bun process with PID: {}", child_pid);

// Spawn watchdog process
match spawn_watchdog(parent_pid, child_pid).await {
Ok(watchdog) => {
debug!(
"Watchdog process spawned successfully with PID: {:?}",
watchdog.id()
);
}
Err(e) => {
warn!("Failed to spawn watchdog process: {}", e);
}
}

return Ok((child, PipeState::Port(port)));
}

Expand Down

0 comments on commit 067a739

Please sign in to comment.