Skip to content

Commit 33c5786

Browse files
authored
Merge pull request #157 from pnuu/feature-sigterm-geographic-gatherer
Add SIGTERM handling to geographic gatherer
2 parents f032ca1 + e866900 commit 33c5786

File tree

2 files changed

+109
-2
lines changed

2 files changed

+109
-2
lines changed

pytroll_collectors/geographic_gatherer.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"""Geographic segment gathering."""
2626

2727
import logging
28+
import signal
2829
import time
2930

3031
from configparser import NoOptionError, ConfigParser
@@ -56,6 +57,8 @@ def __init__(self, opts):
5657
self.triggers = []
5758
self.return_status = 0
5859

60+
self._sigterm_caught = False
61+
5962
self._clean_config()
6063
self._setup_publisher()
6164
try:
@@ -103,8 +106,9 @@ def _setup_triggers(self):
103106

104107
def run(self):
105108
"""Run granule triggers."""
109+
signal.signal(signal.SIGTERM, self._handle_sigterm)
106110
try:
107-
while True:
111+
while self._keep_running():
108112
time.sleep(1)
109113
for trigger in self.triggers:
110114
if not trigger.is_alive():
@@ -119,9 +123,26 @@ def run(self):
119123

120124
return self.return_status
121125

126+
def _handle_sigterm(self, signum, frame):
127+
logger.info("Caught SIGTERM, shutting down when all collections are finished.")
128+
self._sigterm_caught = True
129+
130+
def _keep_running(self):
131+
keep_running = True
132+
if self._sigterm_caught:
133+
keep_running = self._trigger_collectors_have_granules()
134+
return keep_running
135+
136+
def _trigger_collectors_have_granules(self):
137+
for t in self.triggers:
138+
for c in t.collectors:
139+
if c.granules:
140+
return True
141+
return False
142+
122143
def stop(self):
123144
"""Stop the gatherer."""
124-
logger.info('Ending publication the gathering of granules...')
145+
logger.info('Ending the gathering of granules...')
125146
for trigger in self.triggers:
126147
trigger.stop()
127148
self.publisher.stop()

pytroll_collectors/tests/test_geographic_gatherer.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,3 +527,89 @@ def test_full_pass(self, sub_factory, monkeypatch, tmp_tle):
527527
assert snd_msg.data == expected_msg.data
528528
finally:
529529
gatherer.stop()
530+
531+
532+
def test_sigterm(tmp_config_file, tmp_config_parser):
533+
"""Test that SIGTERM signal is handled."""
534+
import os
535+
import signal
536+
import time
537+
from multiprocessing import Process
538+
539+
from pytroll_collectors.geographic_gatherer import GeographicGatherer
540+
541+
with open(tmp_config_file, mode="w") as fp:
542+
tmp_config_parser.write(fp)
543+
544+
opts = arg_parse(["-c", "minimal_config", "-p", "40000", "-n", "false", "-i", "localhost:12345",
545+
str(tmp_config_file)])
546+
# We don't need the triggers here. They also interfere with completing the test (the test never exits)
547+
with patch("pytroll_collectors.geographic_gatherer.TriggerFactory.create"):
548+
gatherer = GeographicGatherer(opts)
549+
proc = Process(target=gatherer.run)
550+
proc.start()
551+
time.sleep(1)
552+
os.kill(proc.pid, signal.SIGTERM)
553+
proc.join()
554+
555+
assert proc.exitcode == 0
556+
557+
558+
def test_sigterm_with_collection(tmp_config_file, tmp_config_parser):
559+
"""Test that SIGTERM signal is handled when there is collection ongoing."""
560+
import os
561+
import signal
562+
import time
563+
from multiprocessing import Process
564+
565+
from pytroll_collectors.geographic_gatherer import GeographicGatherer
566+
567+
with open(tmp_config_file, mode="w") as fp:
568+
tmp_config_parser.write(fp)
569+
570+
opts = arg_parse(["-c", "posttroll_section", "-p", "40000", "-n", "false", "-i", "localhost:12345",
571+
str(tmp_config_file)])
572+
# Use a fake trigger that initially sets some granules and after a while clears them
573+
with patch("pytroll_collectors.geographic_gatherer.PostTrollTrigger",
574+
new=FakeTriggerWithGranules):
575+
gatherer = GeographicGatherer(opts)
576+
proc = Process(target=gatherer.run)
577+
proc.start()
578+
time.sleep(1)
579+
os.kill(proc.pid, signal.SIGTERM)
580+
proc.join()
581+
582+
assert proc.exitcode == 0
583+
584+
585+
class FakeTriggerWithGranules:
586+
"""Fake trigger class used in testing SIGTERM handling.
587+
588+
At creation, adds "foo" to collector granules. When is_alive() is called the second time, it clears the granules.
589+
"""
590+
591+
def __init__(self, collectors, *args, **kwargs):
592+
"""Initialize the trigger class."""
593+
self.collectors = collectors
594+
for col in self.collectors:
595+
col.granules.append("foo")
596+
self._args = args
597+
self._kwargs = kwargs
598+
self._counter = 0
599+
600+
def is_alive(self):
601+
"""Return True for alive thread."""
602+
if self._counter > 0:
603+
# On the second call clear the granules
604+
for col in self.collectors:
605+
col.granules = []
606+
self._counter += 1
607+
return True
608+
609+
def start(self):
610+
"""Start the trigger."""
611+
pass
612+
613+
def stop(self):
614+
"""Stop the trigger."""
615+
pass

0 commit comments

Comments
 (0)