Skip to content

Commit 7a2354f

Browse files
Fix Docker container exit behavior and improve CI testing (#45)
* Fix Docker container exit behavior and improve CI testing - Fixed main function to properly handle signals and prevent premature exit - Enhanced signal handling for SIGINT, SIGTERM and SIGKILL - Improved GitHub Actions workflow with better container status checks - Added better error reporting in CI pipeline * Update README with Docker container behavior information - Added robust signal handling as a feature - Added a new section explaining Docker container behavior - Updated testing section to mention Docker container lifecycle tests * Improve GitHub Actions workflow reliability - Split Docker test into separate steps for better error isolation - Fix grep command to avoid pipe failures - Add 'always' condition to ensure container cleanup - Add better error reporting and success confirmation --------- Co-authored-by: Fedor Batonogov <f.batonogov@yandex.ru>
1 parent 7c09851 commit 7a2354f

File tree

3 files changed

+77
-14
lines changed

3 files changed

+77
-14
lines changed

.github/workflows/tests.yml

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,41 @@ jobs:
4545
run: |
4646
docker build . --tag gron:test
4747
48-
- name: Test Docker image
48+
- name: Start Docker container for testing
4949
run: |
5050
docker run -d --name gron-test \
5151
-e "TASK_TEST=@every 5s echo 'Test task running'" \
5252
gron:test
53+
54+
# Wait for tasks to run
5355
sleep 10
54-
docker logs gron-test | grep "Test task running"
55-
docker stop gron-test
56-
docker rm gron-test
56+
57+
- name: Check if container is running
58+
run: |
59+
CONTAINER_STATUS=$(docker inspect --format='{{.State.Status}}' gron-test)
60+
echo "Container status: $CONTAINER_STATUS"
61+
if [ "$CONTAINER_STATUS" != "running" ]; then
62+
echo "Error: Container is not running"
63+
docker logs gron-test
64+
exit 1
65+
fi
66+
67+
- name: Check for task execution
68+
run: |
69+
# Redirect stderr to stdout to avoid pipe failure
70+
LOGS=$(docker logs gron-test 2>&1)
71+
echo "$LOGS"
72+
73+
# Count task executions carefully to avoid pipe failures
74+
if ! echo "$LOGS" | grep -q "Test task running"; then
75+
echo "Error: No tasks executed"
76+
exit 1
77+
else
78+
echo "✅ Tasks executed successfully"
79+
fi
80+
81+
- name: Clean up container
82+
if: always()
83+
run: |
84+
docker stop gron-test || true
85+
docker rm gron-test || true

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[![Test Coverage: 80%](https://img.shields.io/badge/Test%20Coverage-80%25-success)](./)
44
[![Tests](https://github.com/batonogov/gron/actions/workflows/tests.yml/badge.svg)](https://github.com/batonogov/gron/actions/workflows/tests.yml)
5-
[![Docker Image](https://github.com/batonogov/gron/actions/workflows/release.yml/badge.svg)](https://github.com/batonogov/gron/actions/workflows/release.yml)
5+
[![Docker Image](https://github.com/batonogov/gron/actions/workflows/go.yml/badge.svg)](https://github.com/batonogov/gron/actions/workflows/go.yml)
66

77
A simple and flexible task scheduler in a Docker container that supports both standard cron syntax and simplified intervals.
88

@@ -12,6 +12,8 @@ A simple and flexible task scheduler in a Docker container that supports both st
1212
- Simplified interval syntax (`@every`)
1313
- Script execution from mounted directory
1414
- Easy configuration via environment variables
15+
- Robust signal handling for graceful container shutdown
16+
- Continuous task execution without premature exit
1517

1618
## Usage
1719

@@ -127,13 +129,23 @@ task docker:logs # View logs
127129
task docker:stop # Stop container
128130
```
129131

132+
### Docker Container Behavior
133+
134+
The application is designed to run continuously in a Docker container and will:
135+
136+
- Properly handle SIGTERM and SIGINT signals for graceful shutdown
137+
- Continue running tasks until explicitly stopped
138+
- Never exit prematurely, ensuring all scheduled tasks run as expected
139+
- Automatically restart the scheduler if it unexpectedly exits
140+
130141
### Testing
131142

132143
The project has comprehensive test coverage (80%) including:
133144

134145
- Unit tests for parsing cron expressions
135146
- Tests for command execution logic
136147
- Tests for scheduler functionality
148+
- Docker container lifecycle tests
137149

138150
To run tests with coverage report:
139151

main.go

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -368,22 +368,44 @@ func startCronScheduler(tasks []*CronSchedule) {
368368
func main() {
369369
// Setup signal handling for graceful shutdown
370370
sigChan := make(chan os.Signal, 1)
371-
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
371+
// Listen for both SIGINT (Ctrl+C) and SIGTERM (docker stop)
372+
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM, syscall.SIGKILL)
372373

373374
tasks := loadTasks()
374375

376+
// If no tasks are loaded, log a warning but don't exit
377+
if len(tasks) == 0 {
378+
log.Printf("Warning: No tasks loaded from environment variables")
379+
}
380+
375381
// Start scheduler in a goroutine
376-
done := make(chan bool)
377382
go func() {
383+
// Recover from panics in the scheduler
384+
defer func() {
385+
if r := recover(); r != nil {
386+
log.Printf("Recovered from panic in scheduler: %v", r)
387+
}
388+
}()
378389
startCronScheduler(tasks)
379-
done <- true
390+
// This should never happen, but if it does, log it
391+
log.Printf("Scheduler completed unexpectedly, restarting...")
392+
// Restart the scheduler if it exits unexpectedly
393+
go startCronScheduler(tasks)
380394
}()
381395

382-
// Wait for either a signal or scheduler completion
383-
select {
384-
case sig := <-sigChan:
396+
// Create a channel for graceful exit
397+
done := make(chan struct{})
398+
399+
// Handle signals
400+
go func() {
401+
sig := <-sigChan
385402
log.Printf("Received signal %v, shutting down...", sig)
386-
case <-done:
387-
log.Printf("Scheduler completed unexpectedly")
388-
}
403+
// Close the done channel to signal exit
404+
close(done)
405+
}()
406+
407+
// Block until done
408+
<-done
409+
// Exit with success code
410+
os.Exit(0)
389411
}

0 commit comments

Comments
 (0)