Skip to content

Commit 1a4b113

Browse files
authored
Merge pull request #37 from wlgns5376/issue-36
v1.0.3 배포
2 parents db4a01b + 1a09fa5 commit 1a4b113

21 files changed

Lines changed: 1885 additions & 195 deletions

Dockerfile

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,14 @@ RUN pnpm run build
2828
# Stage 2: Production stage
2929
FROM node:20-alpine AS production
3030

31-
# Install system dependencies
31+
# Install system dependencies including tini for zombie process handling
3232
RUN apk add --no-cache \
3333
git \
3434
openssh-client \
3535
curl \
3636
bash \
37-
sudo
37+
sudo \
38+
tini
3839

3940
# Install utilities
4041
RUN apk add --no-cache \
@@ -109,8 +110,8 @@ USER root
109110
RUN chmod +x /app/entrypoint.sh
110111
USER appuser
111112

112-
# Set entrypoint and default command
113-
ENTRYPOINT ["/app/entrypoint.sh"]
113+
# Set entrypoint and default command with tini as init process
114+
ENTRYPOINT ["/sbin/tini", "--", "/app/entrypoint.sh"]
114115
CMD ["node", "dist/index.js"]
115116

116117
# Labels for metadata

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ai-devteam-node",
3-
"version": "1.0.2",
3+
"version": "1.0.3",
44
"description": "AI DevTeam automation system using Claude Code and Gemini CLI for terminal-based development",
55
"main": "dist/index.js",
66
"directories": {

scripts/entrypoint.sh

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,24 @@
11
#!/bin/bash
22
set -e
33

4+
# Signal handling for graceful shutdown
5+
cleanup() {
6+
echo "Received shutdown signal, cleaning up..."
7+
# Send SIGTERM to all child processes
8+
if [ -n "$MAIN_PID" ]; then
9+
echo "Terminating main process (PID: $MAIN_PID)"
10+
kill -TERM "$MAIN_PID" 2>/dev/null || true
11+
wait "$MAIN_PID" 2>/dev/null || true
12+
fi
13+
echo "Cleanup completed"
14+
exit 0
15+
}
16+
17+
# Set up signal handlers
18+
trap cleanup SIGTERM SIGINT
19+
420
echo "=== AI DevTeam Starting ==="
21+
echo "Container init process: tini (zombie process reaper enabled)"
522
echo "Node.js version: $(node --version)"
623
echo "npm version: $(npm --version)"
724
echo "Git version: $(git --version)"
@@ -56,7 +73,17 @@ if [ ! -z "$GIT_ACCEPT_HOST_KEY" ] && [ "$GIT_ACCEPT_HOST_KEY" = "true" ]; then
5673
fi
5774

5875
echo "=== Configuration Complete ==="
59-
echo "Starting application..."
76+
echo "Starting application with PID tracking..."
77+
78+
# Execute the main application in background and track PID
79+
"$@" &
80+
MAIN_PID=$!
81+
82+
echo "Main application started (PID: $MAIN_PID)"
83+
84+
# Wait for the main process to complete
85+
wait "$MAIN_PID"
86+
EXIT_CODE=$?
6087

61-
# Execute the main application
62-
exec "$@"
88+
echo "Main application exited with code: $EXIT_CODE"
89+
exit $EXIT_CODE

src/app.ts

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -370,20 +370,67 @@ export class AIDevTeamApp {
370370

371371
// Graceful shutdown을 위한 신호 핸들러 설정
372372
setupSignalHandlers(): void {
373+
let shutdownInProgress = false;
374+
373375
const signalHandler = (signal: string) => {
376+
if (shutdownInProgress) {
377+
console.log(`\n⚠️ ${signal} 신호가 이미 처리 중입니다. 강제 종료하려면 다시 한 번 신호를 보내세요.`);
378+
return;
379+
}
380+
381+
shutdownInProgress = true;
374382
console.log(`\n📡 ${signal} 신호 수신됨. Graceful shutdown 시작...`);
383+
384+
// 강제 종료 타이머 (30초 후)
385+
const forceExitTimeout = setTimeout(() => {
386+
console.error('⚠️ Graceful shutdown이 30초 내에 완료되지 않아 강제 종료합니다.');
387+
process.exit(1);
388+
}, 30000);
389+
375390
this.stop()
376391
.then(() => {
392+
clearTimeout(forceExitTimeout);
377393
console.log('✅ Graceful shutdown 완료');
378394
process.exit(0);
379395
})
380396
.catch((error) => {
397+
clearTimeout(forceExitTimeout);
381398
console.error('❌ Graceful shutdown 실패:', error);
382399
process.exit(1);
383400
});
384401
};
385402

386-
process.on('SIGTERM', () => signalHandler('SIGTERM'));
387-
process.on('SIGINT', () => signalHandler('SIGINT'));
403+
// 두 번째 신호 수신 시 즉시 강제 종료
404+
let signalCount = 0;
405+
const forceSignalHandler = (signal: string) => {
406+
signalCount++;
407+
408+
if (signalCount === 1) {
409+
signalHandler(signal);
410+
} else if (signalCount >= 2) {
411+
console.log(`\n⚡ 두 번째 ${signal} 신호 수신됨. 즉시 강제 종료합니다.`);
412+
process.exit(1);
413+
}
414+
};
415+
416+
process.on('SIGTERM', () => forceSignalHandler('SIGTERM'));
417+
process.on('SIGINT', () => forceSignalHandler('SIGINT'));
418+
419+
// 처리되지 않은 promise rejection 핸들링
420+
process.on('unhandledRejection', (reason, promise) => {
421+
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
422+
this.logger?.error('Unhandled promise rejection', { reason, promise });
423+
});
424+
425+
// 처리되지 않은 예외 핸들링
426+
process.on('uncaughtException', (error) => {
427+
console.error('Uncaught Exception:', error);
428+
this.logger?.error('Uncaught exception', { error });
429+
430+
// 정리 후 종료
431+
this.stop()
432+
.finally(() => process.exit(1))
433+
.catch(() => process.exit(1));
434+
});
388435
}
389436
}

0 commit comments

Comments
 (0)