Skip to content

Lectures: Create lecture series #1626

Lectures: Create lecture series

Lectures: Create lecture series #1626

name: Check Bean Instantiations on Startup and with deferred eager Initialization
on:
push:
branches: [ develop ]
paths:
- src/main/java/**
pull_request:
paths:
- src/main/java/**
jobs:
bean-instantiation-check:
runs-on: ubuntu-latest
env:
MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH: 9
MAX_DEFERRED_CHAIN_LENGTH: 16
MIN_INSTANTIATED_BEANS: 20
MAX_INSTANTIATED_BEANS: 98
MIN_DEFERRED_CHAIN_LENGTH: 1
steps:
- name: Check out code
uses: actions/checkout@v5
- name: Validate threshold consistency between BeanInstantiationTracer and GitHub Action
shell: bash
run: |
set -Eeuo pipefail
echo "Validating that thresholds in BeanInstantiationTracer match GitHub Action values..."
echo "GitHub Action thresholds:"
echo " MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH = $MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH"
echo " MAX_DEFERRED_CHAIN_LENGTH = $MAX_DEFERRED_CHAIN_LENGTH"
echo " MIN_INSTANTIATED_BEANS = $MIN_INSTANTIATED_BEANS"
echo " MAX_INSTANTIATED_BEANS = $MAX_INSTANTIATED_BEANS"
echo " MIN_DEFERRED_CHAIN_LENGTH = $MIN_DEFERRED_CHAIN_LENGTH"
JAVA_FILE="src/main/java/de/tum/cit/aet/artemis/core/config/BeanInstantiationTracer.java"
if [[ ! -f "$JAVA_FILE" ]]; then
echo "❌ BeanInstantiationTracer.java not found at expected location: $JAVA_FILE"
echo "Searching for the file..."
find . -name "BeanInstantiationTracer.java" -type f || echo "File not found anywhere"
exit 1
fi
EXTRACT_NUMBER_REGEX='.*=\s*([0-9]+).*'
JAVA_MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH=$(grep -E "STARTUP_MAX_DEPENDENCY_CHAIN_THRESHOLD\s*=" "$JAVA_FILE" | sed -E "s/${EXTRACT_NUMBER_REGEX}/\1/" | head -1 || true)
JAVA_MAX_DEFERRED_CHAIN_LENGTH=$(grep -E "DEFERRED_INIT_MAX_DEPENDENCY_CHAIN_THRESHOLD\s*=" "$JAVA_FILE" | sed -E "s/${EXTRACT_NUMBER_REGEX}/\1/" | head -1 || true)
echo "Extracted thresholds from Java file:"
echo "STARTUP_MAX_DEPENDENCY_CHAIN_THRESHOLD = ${JAVA_MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH:-<empty>}"
echo "DEFERRED_INIT_MAX_DEPENDENCY_CHAIN_THRESHOLD = ${JAVA_MAX_DEFERRED_CHAIN_LENGTH:-<empty>}"
if [[ -z "${JAVA_MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH:-}" || -z "${JAVA_MAX_DEFERRED_CHAIN_LENGTH:-}" ]]; then
echo "❌ Failed to extract threshold values from Java file"
echo "Java file contents around threshold definitions:"
grep -A2 -B2 -E "(STARTUP_MAX_DEPENDENCY_CHAIN_THRESHOLD|DEFERRED_INIT_MAX_DEPENDENCY_CHAIN_THRESHOLD)" "$JAVA_FILE" || echo "Threshold constants not found"
exit 1
fi
THRESHOLD_MISMATCH=false
if [[ "$JAVA_MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH" != "$MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH" ]]; then
echo "❌ STARTUP_MAX_DEPENDENCY_CHAIN_THRESHOLD mismatch:"
echo " Java: $JAVA_MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH"
echo " GitHub Action: $MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH"
THRESHOLD_MISMATCH=true
fi
if [[ "$JAVA_MAX_DEFERRED_CHAIN_LENGTH" != "$MAX_DEFERRED_CHAIN_LENGTH" ]]; then
echo "❌ DEFERRED_INIT_MAX_DEPENDENCY_CHAIN_THRESHOLD mismatch:"
echo " Java: $JAVA_MAX_DEFERRED_CHAIN_LENGTH"
echo " GitHub Action: $MAX_DEFERRED_CHAIN_LENGTH"
THRESHOLD_MISMATCH=true
fi
if [[ "$THRESHOLD_MISMATCH" == true ]]; then
echo ""
echo "🔧 Please update either the Java constants or GitHub Action variables to match:"
echo " - Java file: $JAVA_FILE"
echo " - GitHub Action env variables at the job level"
exit 1
fi
echo "✅ All thresholds match between Java file and GitHub Action"
- name: Set up JDK 25
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '25'
- name: Build application
shell: bash
run: |
set -Eeuo pipefail
./gradlew clean bootJar -x test -x webapp
- name: Start Spring Boot app
shell: bash
run: |
set -Eeuo pipefail
PROFILES=dev,localci,lti,aeolus,theia,iris,localvc,artemis,scheduling,buildagent,core,ldap
JAR=$(ls build/libs/Artemis*.jar | head -n1)
nohup java -jar "$JAR" \
--spring.profiles.active="$PROFILES" \
--artemis.user-management.passkey.enabled=true \
--artemis.user-management.use-external=false \
--artemis.iris.url=http://iris.fake \
--artemis.iris.secret-token=token \
--spring.datasource.url="jdbc:h2:mem:mydb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE" \
--spring.datasource.username=sa \
--spring.datasource.password= \
--eureka.client.enabled=false \
--aeolus.url=http://aeolus.fake \
> app.log 2>&1 &
echo $! > app.pid
- name: Wait for the application to start
shell: bash
run: |
set -Eeuo pipefail
RUNNING_MESSAGE="'Artemis' is running!"
STARTUP_TIMEOUT_ATTEMPTS=30
STARTUP_RETRY_INTERVAL=2
STARTUP_TOTAL_TIMEOUT=$((STARTUP_TIMEOUT_ATTEMPTS * STARTUP_RETRY_INTERVAL))
LOG_FILE="app.log"
echo "Waiting up to ${STARTUP_TOTAL_TIMEOUT}s for $RUNNING_MESSAGE to appear in $LOG_FILE..."
isAppRunning=false
for i in $(seq 1 "$STARTUP_TIMEOUT_ATTEMPTS"); do
if grep -q "$RUNNING_MESSAGE" "$LOG_FILE"; then
isAppRunning=true
echo "✅ Found $RUNNING_MESSAGE in $LOG_FILE after $i attempts"
break
fi
echo " attempt $i/$STARTUP_TIMEOUT_ATTEMPTS: not found yet"
sleep "$STARTUP_RETRY_INTERVAL"
done
if [[ "$isAppRunning" == false ]]; then
echo "❌ Timeout: $RUNNING_MESSAGE not found in $LOG_FILE"
cat "$LOG_FILE"
exit 1
fi
- name: Extract and validate startup bean instantiation metrics
shell: bash
run: |
set -Eeuo pipefail
# Use a safe, single-quoted regex and a simple search prefix
STARTUP_LOG_PATTERN='Bean instantiation graph exported to startupBeans\.dot \(([0-9]+) edges, longest dependency chain length: ([0-9]+)\)'
STARTUP_SEARCH='Bean instantiation graph exported to startupBeans\.dot'
LOG_FILE="app.log"
LINE=$(grep -E "$STARTUP_SEARCH" "$LOG_FILE" || true)
if [[ -z "$LINE" ]]; then
echo "❌ No startup metrics line found"
cat "$LOG_FILE"
exit 1
fi
if [[ "$LINE" =~ $STARTUP_LOG_PATTERN ]]; then
INSTANTIATED_BEANS="${BASH_REMATCH[1]}"
LONGEST_CHAIN_LENGTH="${BASH_REMATCH[2]}"
else
echo "❌ Failed to parse startup metrics from: $LINE"
exit 1
fi
echo "• Number of instantiated beans = $INSTANTIATED_BEANS"
echo "• Longest dependency chain length = $LONGEST_CHAIN_LENGTH"
echo "Validating against thresholds: MIN_INSTANTIATED_BEANS=$MIN_INSTANTIATED_BEANS (expected ≥ $MIN_INSTANTIATED_BEANS), MAX_INSTANTIATED_BEANS=$MAX_INSTANTIATED_BEANS (expected ≤ $MAX_INSTANTIATED_BEANS), MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH=$MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH (expected ≤ $MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH)"
STARTUP_VALIDATION_FAILED=false
if (( INSTANTIATED_BEANS < MIN_INSTANTIATED_BEANS )); then
echo "❌ $INSTANTIATED_BEANS < $MIN_INSTANTIATED_BEANS beans. Something seems to be wrong, as usually more beans are instantiated."
STARTUP_VALIDATION_FAILED=true
fi
if (( INSTANTIATED_BEANS > MAX_INSTANTIATED_BEANS )); then
echo "❌ $INSTANTIATED_BEANS > $MAX_INSTANTIATED_BEANS threshold"
STARTUP_VALIDATION_FAILED=true
fi
if (( LONGEST_CHAIN_LENGTH > MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH )); then
echo "❌ Longest dependency chain length $LONGEST_CHAIN_LENGTH > $MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH threshold"
STARTUP_VALIDATION_FAILED=true
fi
if [[ "$STARTUP_VALIDATION_FAILED" == false ]]; then
echo "✅ Final values → Beans: $INSTANTIATED_BEANS; Longest dependency chain length: $LONGEST_CHAIN_LENGTH"
echo "✅ Startup bean instantiation metrics within expected ranges"
fi
# Export variables for next step
{
echo "LONGEST_CHAIN_LENGTH=$LONGEST_CHAIN_LENGTH"
echo "STARTUP_VALIDATION_FAILED=$STARTUP_VALIDATION_FAILED"
} >> "$GITHUB_ENV"
- name: Check for startup dependency chains exceeding threshold
shell: bash
run: |
set -Eeuo pipefail
STARTUP_DEEP_CHAIN_PATTERN='Startup long bean instantiation chain'
LOG_FILE="app.log"
if (( LONGEST_CHAIN_LENGTH > MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH )); then
echo "❌ Startup dependency chain length $LONGEST_CHAIN_LENGTH > $MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH threshold"
echo "Chains exceeding threshold:"
(grep -E "$STARTUP_DEEP_CHAIN_PATTERN" "$LOG_FILE" || echo " (No detailed chains found in log)")
echo "🔧 These chains violate the threshold MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH. Please refactor them to break these chains."
echo "You can find a visualization of the bean instantiation graph in the artifacts of this job in startupBeans.dot"
echo "You can visualize the file on http://webgraphviz.com/"
else
echo "✅ No startup dependency chains exceed the threshold of $MAX_STARTUP_DEPENDENCY_CHAIN_LENGTH"
fi
if [[ "$STARTUP_VALIDATION_FAILED" == "true" ]]; then
exit 1
fi
- name: Extract and validate deferred eager bean chain length
shell: bash
run: |
set -Eeuo pipefail
DEFERRED_BEAN_PATTERN='Maximum dependency chain length during deferred eager init: ([0-9]+)'
DEFERRED_BEAN_SEARCH='Maximum dependency chain length during deferred eager init'
DEFERRED_TIMEOUT_ATTEMPTS=10
DEFERRED_RETRY_INTERVAL=12
DEFERRED_TOTAL_TIMEOUT=$((DEFERRED_TIMEOUT_ATTEMPTS * DEFERRED_RETRY_INTERVAL))
LOG_FILE="app.log"
echo "Waiting for deferred eager bean initialization to complete..."
DEFERRED_LINE=""
for i in $(seq 1 "$DEFERRED_TIMEOUT_ATTEMPTS"); do
DEFERRED_LINE=$(grep -E "$DEFERRED_BEAN_SEARCH" "$LOG_FILE" 2>/dev/null || true)
if [[ -n "$DEFERRED_LINE" ]]; then
echo "✅ Found deferred eager bean initialization log after $i attempts"
break
fi
echo " attempt $i/$DEFERRED_TIMEOUT_ATTEMPTS: deferred eager bean initialization not completed yet"
sleep "$DEFERRED_RETRY_INTERVAL"
done
if [[ -z "$DEFERRED_LINE" ]]; then
echo "❌ No deferred eager bean chain length line found after ${DEFERRED_TOTAL_TIMEOUT}s"
cat "$LOG_FILE"
exit 1
fi
if [[ "$DEFERRED_LINE" =~ $DEFERRED_BEAN_PATTERN ]]; then
DEFERRED_CHAIN_LENGTH="${BASH_REMATCH[1]}"
else
echo "❌ Failed to parse deferred eager bean chain length from: $DEFERRED_LINE"
exit 1
fi
echo "• Maximum dependency chain length during deferred eager bean initialization = $DEFERRED_CHAIN_LENGTH"
echo "Validating against thresholds: MIN_DEFERRED_CHAIN_LENGTH=$MIN_DEFERRED_CHAIN_LENGTH (expected ≥ $MIN_DEFERRED_CHAIN_LENGTH), MAX_DEFERRED_CHAIN_LENGTH=$MAX_DEFERRED_CHAIN_LENGTH (expected ≤ $MAX_DEFERRED_CHAIN_LENGTH)"
DEFERRED_VALIDATION_FAILED=false
if (( DEFERRED_CHAIN_LENGTH < MIN_DEFERRED_CHAIN_LENGTH )); then
echo "❌ Deferred chain length $DEFERRED_CHAIN_LENGTH < $MIN_DEFERRED_CHAIN_LENGTH. Something seems to be wrong, as usually some deferred beans are instantiated."
echo "Was the deferred eager bean initialization removed? If so, you have to adapt this Github Action."
DEFERRED_VALIDATION_FAILED=true
fi
if (( DEFERRED_CHAIN_LENGTH > MAX_DEFERRED_CHAIN_LENGTH )); then
echo "❌ Deferred chain length $DEFERRED_CHAIN_LENGTH > $MAX_DEFERRED_CHAIN_LENGTH threshold"
DEFERRED_VALIDATION_FAILED=true
fi
if [[ "$DEFERRED_VALIDATION_FAILED" == false ]]; then
echo "✅ Deferred eager bean chain length: $DEFERRED_CHAIN_LENGTH"
echo "✅ Deferred eager bean dependency chain length within expected ranges"
fi
{
echo "DEFERRED_CHAIN_LENGTH=$DEFERRED_CHAIN_LENGTH"
echo "DEFERRED_VALIDATION_FAILED=$DEFERRED_VALIDATION_FAILED"
} >> "$GITHUB_ENV"
- name: Check for deferred dependency chains exceeding threshold
shell: bash
run: |
set -Eeuo pipefail
DEFERRED_LONG_CHAIN_PATTERN='Deferred long bean instantiation chain'
LOG_FILE="app.log"
echo "Checking for deferred dependency chains exceeding threshold..."
if (( DEFERRED_CHAIN_LENGTH > MAX_DEFERRED_CHAIN_LENGTH )); then
echo "❌ Deferred dependency chain length $DEFERRED_CHAIN_LENGTH > $MAX_DEFERRED_CHAIN_LENGTH threshold"
echo "Chains exceeding threshold:"
(grep -E "$DEFERRED_LONG_CHAIN_PATTERN" "$LOG_FILE" || echo " (No detailed chains found in log)")
echo "🔧 These chains violate the threshold MAX_DEFERRED_CHAIN_LENGTH. Please refactor them to break these chains."
echo "You can find a visualization of the bean instantiation graph in in the artifacts of this job in deferredEagerBeanInstantiationViolations.dot."
echo "You can visualize the file on http://webgraphviz.com/"
else
echo "✅ No deferred dependency chains exceed the threshold of $MAX_DEFERRED_CHAIN_LENGTH"
fi
if [[ "$DEFERRED_VALIDATION_FAILED" == "true" ]]; then
exit 1
fi
- name: Stop application
if: always()
shell: bash
run: |
set -Eeuo pipefail
kill "$(cat app.pid)" || true
- name: Upload artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: bean-instantiation-graphs-and-logs
path: |
startupBeans.dot
deferredEagerBeanInstantiationViolations.dot
app.log