Skip to content
Merged
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
104 changes: 81 additions & 23 deletions crates/goose/src/temporal_scheduler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,13 +305,33 @@ impl TemporalScheduler {

// Set the PORT environment variable for the service to use and properly daemonize it
// Create a new process group to ensure the service survives parent termination
let mut command = Command::new("./temporal-service");
let mut command = Command::new(&binary_path);
command
.current_dir(working_dir)
.env("PORT", self.port_config.http_port.to_string())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.stdin(std::process::Stdio::null());
.env("PORT", self.port_config.http_port.to_string());

// Platform-specific process configuration based on Electron app approach
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
// On Windows, prevent console window and run detached:
// - Use CREATE_NO_WINDOW (0x08000000) to prevent console window
// - Use DETACHED_PROCESS (0x00000008) for independence
// - Redirect output to null to prevent console attachment
command
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.stdin(std::process::Stdio::null())
.creation_flags(0x08000000 | 0x00000008); // CREATE_NO_WINDOW | DETACHED_PROCESS
}

#[cfg(not(windows))]
{
command
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.stdin(std::process::Stdio::null());
}

// On Unix systems, create a new process group
#[cfg(unix)]
Expand All @@ -333,12 +353,27 @@ impl TemporalScheduler {
pid, self.port_config.http_port
);

// Give the process a moment to start up
sleep(Duration::from_millis(100)).await;
// Platform-specific process handling
#[cfg(windows)]
{
// On Windows, wait longer for initialization and use unref-like behavior
sleep(Duration::from_millis(1000)).await; // Wait 1 second for Windows initialization

// Use a different approach - spawn a monitoring thread that waits longer
std::thread::spawn(move || {
// Give the process significant time to initialize on Windows
std::thread::sleep(std::time::Duration::from_secs(5));
// After 5 seconds, let it run completely independently
let _ = child.wait();
});
}

// Verify the process is still running
#[cfg(unix)]
{
// Give the process a moment to start up
sleep(Duration::from_millis(100)).await;

// Verify the process is still running
use std::process::Command as StdCommand;
let ps_check = StdCommand::new("ps")
.arg("-p")
Expand All @@ -359,13 +394,13 @@ impl TemporalScheduler {
warn!("Could not verify Temporal service process status: {}", e);
}
}
}

// Detach the child process by not waiting for it
// This allows it to continue running independently
std::thread::spawn(move || {
let _ = child.wait();
});
// Detach the child process by not waiting for it
// This allows it to continue running independently
std::thread::spawn(move || {
let _ = child.wait();
});
}

Ok(())
}
Expand All @@ -374,17 +409,16 @@ impl TemporalScheduler {
// Try to find the Go service binary by looking for it relative to the current executable
// or in common locations

let possible_paths = vec![
// Relative to current working directory (original behavior)
"./temporal-service/temporal-service",
];

// Also try to find it relative to the current executable path
// First try to find it relative to the current executable path (most common for bundled apps)
if let Ok(exe_path) = std::env::current_exe() {
if let Some(exe_dir) = exe_path.parent() {
// Try various relative paths from the executable directory
let exe_relative_paths = vec![
// First check in the same directory as the executable (bundled location)
// First check in resources/bin subdirectory (bundled Electron app location)
exe_dir.join("resources/bin/temporal-service"),
exe_dir.join("resources/bin/temporal-service.exe"), // Windows version
exe_dir.join("resources\\bin\\temporal-service.exe"), // Windows with backslashes
// Then check in the same directory as the executable
exe_dir.join("temporal-service"),
exe_dir.join("temporal-service.exe"), // Windows version
// Then check in temporal-service subdirectory
Expand All @@ -399,21 +433,45 @@ impl TemporalScheduler {

for path in exe_relative_paths {
if path.exists() {
tracing::debug!("Found temporal-service binary at: {}", path.display());
return Ok(path.to_string_lossy().to_string());
}
}
}
}

// Try the original relative paths
// Try relative to current working directory (original behavior)
let possible_paths = vec![
"./temporal-service/temporal-service",
"./temporal-service.exe", // Windows in current dir
"./resources/bin/temporal-service.exe", // Windows bundled in current dir
];

for path in &possible_paths {
if std::path::Path::new(path).exists() {
tracing::debug!("Found temporal-service binary at: {}", path);
return Ok(path.to_string());
}
}

// Check environment variable override
if let Ok(binary_path) = std::env::var("GOOSE_TEMPORAL_BIN") {
if std::path::Path::new(&binary_path).exists() {
tracing::info!(
"Using temporal-service binary from GOOSE_TEMPORAL_BIN: {}",
binary_path
);
return Ok(binary_path);
} else {
tracing::warn!(
"GOOSE_TEMPORAL_BIN points to non-existent file: {}",
binary_path
);
}
}

Err(SchedulerError::SchedulerInternalError(
"Go service binary not found. Tried paths relative to current executable and working directory. Please ensure the temporal-service binary is built and available.".to_string()
"Go service binary not found. Tried paths relative to current executable and working directory. Please ensure the temporal-service binary is built and available, or set GOOSE_TEMPORAL_BIN environment variable.".to_string()
))
}

Expand Down
21 changes: 21 additions & 0 deletions temporal-service/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,12 +331,33 @@ func ensureTemporalServerRunning(ports *PortConfig) error {

func main() {
log.Println("Starting Temporal service...")
log.Printf("Runtime OS: %s", runtime.GOOS)
log.Printf("Runtime ARCH: %s", runtime.GOARCH)

// Log current working directory for debugging
if cwd, err := os.Getwd(); err == nil {
log.Printf("Current working directory: %s", cwd)
}

// Log environment variables that might affect behavior
if port := os.Getenv("PORT"); port != "" {
log.Printf("PORT environment variable: %s", port)
}
if rustLog := os.Getenv("RUST_LOG"); rustLog != "" {
log.Printf("RUST_LOG environment variable: %s", rustLog)
}
if temporalLog := os.Getenv("TEMPORAL_LOG_LEVEL"); temporalLog != "" {
log.Printf("TEMPORAL_LOG_LEVEL environment variable: %s", temporalLog)
}

// Create Temporal service (this will find available ports automatically)
log.Println("Creating Temporal service...")
service, err := NewTemporalService()
if err != nil {
log.Printf("ERROR: Failed to create Temporal service: %v", err)
log.Fatalf("Failed to create Temporal service: %v", err)
}
log.Println("✓ Temporal service created successfully")

// Use the dynamically assigned HTTP port
httpPort := service.GetHTTPPort()
Expand Down
Binary file modified temporal-service/temporal-service
Binary file not shown.
10 changes: 9 additions & 1 deletion ui/desktop/src/goosed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,17 @@ export const findAvailablePort = (): Promise<number> => {
// Check if goosed server is ready by polling the status endpoint
const checkServerStatus = async (
port: number,
maxAttempts: number = 80,
maxAttempts?: number,
interval: number = 100
): Promise<boolean> => {
if (maxAttempts === undefined) {
const isTemporalEnabled = process.env.GOOSE_SCHEDULER_TYPE === 'temporal';
maxAttempts = isTemporalEnabled ? 200 : 80;
log.info(
`Using ${maxAttempts} max attempts (temporal scheduling: ${isTemporalEnabled ? 'enabled' : 'disabled'})`
);
}

const statusUrl = `http://127.0.0.1:${port}/status`;
log.info(`Checking server status at ${statusUrl}`);

Expand Down
Loading