#4267: stopProjectForge.sh

site/uploads/stopProjectForge.sh

Path: site/uploads/stopProjectForge.sh · Lines: 34 · Shebang: #!/bin/bash

Purpose: Production shutdown script — implements a two-phase termination protocol: SIGTERM (graceful, 60s wait) → SIGKILL (forceful). Companion to startProjectForge.sh (#4096).

Source: GitHub

34 lines · 24 code · 2 comments · 8 blank
CommitMessage
ac75fabf3Github pages migrated to Asciidoc

Two-phase shutdown protocol

The script identifies the ProjectForge Java process via pgrep -f "java.*projectforge-application" (pattern matching on the JVM command line), then executes:

  1. Phase 1 — Graceful (60 seconds): kill $pid sends SIGTERM (signal 15). The JVM catches this and triggers Spring's @PreDestroy lifecycle callbacks — Hibernate flushes caches, connection pools close, Quartz jobs stop, Tomcat shuts down. The script loops 20 times × 3 seconds, checking if the process is gone.
  2. Phase 2 — Forceful: If still alive after 60s, kill -9 $pid sends SIGKILL (signal 9) — uncatchable, immediate termination. No cleanup: database connections orphaned, Lucene indexes potentially corrupted, log buffers lost. The script waits 2 seconds and checks again.

This two-phase approach prevents the most common production issue: database connection leaks from killed Java processes. The 60-second window is generous — most Spring Boot apps shut down in 5-15 seconds.

Script analysis — key lines

checkStopped() {
  pid=$(pgrep -f $PROCESS_IDENTIFIER)      # Re-check if process exists
  if [[ -z $pid ]]; then                     # If no PID found
    echo "${APP_NAME} $1"                    # Print success message
    exit 0                                   # Exit immediately
  fi
  if [[ -n $2 ]]; then                       # If second arg given (warning mode)
    echo "${APP_NAME} $2"                    # Print warning but continue
  fi
}

Dual-mode function: checkStopped serves two purposes depending on argument count — with 1 arg it's a "success check" (exits if stopped), with 2 args it's a "status report" (prints and continues).

Variable scope bug: $pid is set inside the function (line 7) but used outside on line 20 — this works because pid isn't declared local, so it leaks to the caller's scope. Fragile — if a second matching process appears between checks, only the first PID gets killed.

Key takeaways