From 5e1c5d793cbdba1ce7d142fe42b6e80d911ddcba Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Thu, 2 Apr 2026 14:28:41 +0800 Subject: [PATCH] fix(scripts): improve Windows PowerShell compatibility - Add note recommending Git Bash over PowerShell for best compatibility - start-celery.ps1: use cmd /c approach with --pool solo to avoid billiard multiprocessing issues on Windows; improve process tracking - Remove Set-Location from scripts as it's unnecessary --- scripts/start-backend.ps1 | 7 ++- scripts/start-celery.ps1 | 102 +++++++++++++++++++++++++++++-------- scripts/start-frontend.ps1 | 7 ++- 3 files changed, 93 insertions(+), 23 deletions(-) diff --git a/scripts/start-backend.ps1 b/scripts/start-backend.ps1 index f0ff906..8aa37ac 100644 --- a/scripts/start-backend.ps1 +++ b/scripts/start-backend.ps1 @@ -1,6 +1,12 @@ # ============================================================ # Start Backend Service (Windows PowerShell) # Usage: .\start-backend.ps1 [dev|prod] +# +# NOTE: For best compatibility on Windows, consider using Git Bash: +# bash scripts/start-backend.sh +# +# The PowerShell version uses Start-Process which may have issues +# with some Python servers on Windows. # ============================================================ $ErrorActionPreference = "Stop" @@ -32,7 +38,6 @@ if (Test-Path $PID_FILE) { } } -Set-Location $BackendDir # Check dependencies try { diff --git a/scripts/start-celery.ps1 b/scripts/start-celery.ps1 index c403e20..8de48a4 100644 --- a/scripts/start-celery.ps1 +++ b/scripts/start-celery.ps1 @@ -1,6 +1,15 @@ # ============================================================ # Start Celery Services (Windows PowerShell) # Usage: .\start-celery.ps1 [dev|prod] +# +# IMPORTANT: Celery on Windows has known issues with PowerShell's +# Start-Process. For reliable operation on Windows, use Git Bash instead: +# +# bash scripts/start-celery.sh +# +# The bash script uses nohup which works correctly with Celery's +# multiprocessing pool (billiard). Start-Process in PowerShell +# causes billiard to fail with "PermissionError" or "OSError: handle invalid". # ============================================================ $ErrorActionPreference = "Stop" @@ -19,21 +28,33 @@ if (!(Test-Path $LOG_DIR)) { New-Item -ItemType Directory -Path $LOG_DIR -Force $WORKER_PID_FILE = Join-Path $PID_DIR "celery_worker.pid" $BEAT_PID_FILE = Join-Path $PID_DIR "celery_beat.pid" +$PYTHON_EXE = Join-Path $BackendDir ".venv\Scripts\python.exe" +$CELERY_EXE = Join-Path $BackendDir ".venv\Scripts\celery.exe" + $WORKER_LOG = Join-Path $LOG_DIR "celery_worker.log" $BEAT_LOG = Join-Path $LOG_DIR "celery_beat.log" -Set-Location $BackendDir - -# Set Django environment +# Set Django environment for current process $env:DJANGO_SETTINGS_MODULE = "application.settings" +# Helper function to find celery process by port or log +function Find-CeleryProcess { + param([string]$Type) + $allProcs = Get-Process -Name "celery" -ErrorAction SilentlyContinue + return $allProcs +} + # Check if Worker is already running $workerRunning = $false +$existingWorker = $null if (Test-Path $WORKER_PID_FILE) { $PID = Get-Content $WORKER_PID_FILE -ErrorAction SilentlyContinue - if ($PID -and (Get-Process -Id $PID -ErrorAction SilentlyContinue)) { - $workerRunning = $true - Write-Host "[Celery] Worker already running (PID: $PID), skipping" + if ($PID) { + $existingWorker = Get-Process -Id $PID -ErrorAction SilentlyContinue + if ($existingWorker) { + $workerRunning = $true + Write-Host "[Celery] Worker already running (PID: $PID), skipping" + } } } @@ -42,36 +63,73 @@ if (-not $workerRunning) { # Check if celery is installed try { - python -c "import celery" 2>$null + & $PYTHON_EXE -c "import celery" 2>$null if ($LASTEXITCODE -ne 0) { throw "celery not found" } } catch { Write-Host "[Error] Celery not installed" exit 1 } - # Start Celery Worker - $proc = Start-Process -FilePath "celery" -ArgumentList "-A application worker -l info --logfile=$WORKER_LOG --pidfile=$WORKER_PID_FILE" -WorkingDirectory $BackendDir -PassThru -NoNewWindow -WindowStyle Hidden - $proc.Id | Set-Content $WORKER_PID_FILE - Write-Host "[Celery] Worker started (PID: $($proc.Id))" + # Get count of existing celery processes + $beforeCount = (Get-Process -Name "celery" -ErrorAction SilentlyContinue).Count + + # Start Celery Worker using cmd /c to run in background session + # Using --pool solo --concurrency 1 to avoid Windows multiprocessing issues with billiard + $cmdArgs = "/c cd /d `"$BackendDir`" && set DJANGO_SETTINGS_MODULE=application.settings && start /b /wait cmd /k `"$CELERY_EXE -A application worker -l info --logfile=$WORKER_LOG --pidfile=$WORKER_PID_FILE --pool solo --concurrency 1`"" + Start-Process -FilePath "cmd.exe" -ArgumentList $cmdArgs -WindowStyle Hidden + + # Wait for process to start + Start-Sleep -Seconds 3 + + # Find the new celery worker process + $afterProcs = Get-Process -Name "celery" -ErrorAction SilentlyContinue + $newProc = $afterProcs | Where-Object { $_.Id -ne (Get-Content $BEAT_PID_FILE -ErrorAction SilentlyContinue) } | Select-Object -First 1 + + if ($newProc) { + $newProc.Id | Set-Content $WORKER_PID_FILE + Write-Host "[Celery] Worker started (PID: $($newProc.Id))" + } else { + Write-Host "[Celery] Worker started (process tracking may be inaccurate on Windows)" + } } # Check Beat $beatRunning = $false +$existingBeat = $null if (Test-Path $BEAT_PID_FILE) { $PID = Get-Content $BEAT_PID_FILE -ErrorAction SilentlyContinue - if ($PID -and (Get-Process -Id $PID -ErrorAction SilentlyContinue)) { - $beatRunning = $true - Write-Host "[Celery Beat] Already running (PID: $PID), skipping" + if ($PID) { + $existingBeat = Get-Process -Id $PID -ErrorAction SilentlyContinue + if ($existingBeat) { + $beatRunning = $true + Write-Host "[Celery Beat] Already running (PID: $PID), skipping" + } } } if (-not $beatRunning) { Write-Host "[Celery Beat] Starting scheduler..." + # Get count before + $beforeCount = (Get-Process -Name "celery" -ErrorAction SilentlyContinue).Count + # Start Celery Beat - $proc = Start-Process -FilePath "celery" -ArgumentList "-A application beat -l info --logfile=$BEAT_LOG --pidfile=$BEAT_PID_FILE --scheduler django_celery_beat.schedulers:DatabaseScheduler" -WorkingDirectory $BackendDir -PassThru -NoNewWindow -WindowStyle Hidden - $proc.Id | Set-Content $BEAT_PID_FILE - Write-Host "[Celery Beat] Started (PID: $($proc.Id))" + $cmdArgs = "/c cd /d `"$BackendDir`" && set DJANGO_SETTINGS_MODULE=application.settings && start /b /wait cmd /k `"$CELERY_EXE -A application beat -l info --logfile=$BEAT_LOG --pidfile=$BEAT_PID_FILE --scheduler django_celery_beat.schedulers:DatabaseScheduler`"" + Start-Process -FilePath "cmd.exe" -ArgumentList $cmdArgs -WindowStyle Hidden + + # Wait for process to start + Start-Sleep -Seconds 3 + + # Find the new celery beat process + $afterProcs = Get-Process -Name "celery" -ErrorAction SilentlyContinue + $newBeat = $afterProcs | Select-Object -Last 1 + + if ($newBeat) { + $newBeat.Id | Set-Content $BEAT_PID_FILE + Write-Host "[Celery Beat] Started (PID: $($newBeat.Id))" + } else { + Write-Host "[Celery Beat] Started (process tracking may be inaccurate on Windows)" + } } # Wait for startup @@ -79,14 +137,16 @@ Start-Sleep -Seconds 3 # Show status if (Test-Path $WORKER_PID_FILE) { - Write-Host "[Celery] Worker PID: $(Get-Content $WORKER_PID_FILE)" - Write-Host "[Celery] Worker log: $WORKER_LOG" + $wpid = Get-Content $WORKER_PID_FILE + Write-Host "[Celery] Worker PID: $wpid" } +Write-Host "[Celery] Worker log: $WORKER_LOG" if (Test-Path $BEAT_PID_FILE) { - Write-Host "[Celery Beat] PID: $(Get-Content $BEAT_PID_FILE)" - Write-Host "[Celery Beat] log: $BEAT_LOG" + $bpid = Get-Content $BEAT_PID_FILE + Write-Host "[Celery Beat] PID: $bpid" } +Write-Host "[Celery Beat] log: $BEAT_LOG" Write-Host "[Celery] Startup complete" exit 0 diff --git a/scripts/start-frontend.ps1 b/scripts/start-frontend.ps1 index 1dadf82..546c340 100644 --- a/scripts/start-frontend.ps1 +++ b/scripts/start-frontend.ps1 @@ -1,6 +1,12 @@ # ============================================================ # Start Frontend Service (Windows PowerShell) # Usage: .\start-frontend.ps1 [dev|prod] +# +# NOTE: For best compatibility on Windows, consider using Git Bash: +# bash scripts/start-frontend.sh +# +# The PowerShell version uses Start-Process which may have issues +# with some Node.js development servers. # ============================================================ $ErrorActionPreference = "Stop" @@ -33,7 +39,6 @@ if (Test-Path $PID_FILE) { } } -Set-Location $FrontendDir # Check if yarn is available $yarnCmd = Get-Command yarn -ErrorAction SilentlyContinue