Skip to content

Commit e2b0f3b

Browse files
committed
fix: better shutdown
1 parent 1505808 commit e2b0f3b

File tree

2 files changed

+48
-6
lines changed

2 files changed

+48
-6
lines changed

sentry_dynamic_sampling_lib/sampler.py

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,41 @@
11
import logging
2-
from threading import Thread
2+
import signal
3+
from threading import Event, Thread
34
from time import sleep
45

56
import schedule
67
from requests.exceptions import RequestException
78
from requests_cache import CachedSession
89

910
from sentry_dynamic_sampling_lib.shared import Config, Metric, MetricType
11+
from sentry_dynamic_sampling_lib.utils import Singleton
12+
13+
try:
14+
from celery.signals import worker_shutdown
15+
except ModuleNotFoundError:
16+
worker_shutdown = None
1017

1118
LOGGER = logging.getLogger("SentryWrapper")
1219

1320

21+
def on_exit(*args, **kwargs):
22+
ts = TraceSampler()
23+
ts.kill()
24+
raise KeyboardInterrupt
25+
26+
1427
class ControllerClient(Thread):
15-
def __init__(self, config, metric, *args, **kwargs) -> None:
28+
def __init__(self, stop, config, metric, *args, **kwargs) -> None:
1629
self.poll_interval = kwargs.pop("poll_interval")
1730
self.metric_interval = kwargs.pop("metric_interval")
1831
self.controller_endpoint = kwargs.pop("controller_endpoint")
1932
self.metric_endpoint = kwargs.pop("metric_endpoint")
2033
self.app_key = kwargs.pop("app_key")
34+
self.stop: Event = stop
2135
self.config: Config = config
2236
self.metrics: Metric = metric
2337
self.session = CachedSession(backend="memory", cache_control=True)
24-
super().__init__(*args, name="SentryControllerClient", daemon=True, **kwargs)
38+
super().__init__(*args, name="SentryControllerClient", **kwargs)
2539

2640
def run(self):
2741
# HACK: Django change the timezone mid startup
@@ -30,7 +44,7 @@ def run(self):
3044
sleep(5)
3145
schedule.every(self.poll_interval).seconds.do(self.update_config)
3246
schedule.every(self.metric_interval).seconds.do(self.update_metrics)
33-
while True:
47+
while not self.stop.is_set():
3448
schedule.run_pending()
3549
sleep(1)
3650

@@ -55,6 +69,8 @@ def update_config(self):
5569
def update_metrics(self):
5670
for metric_type in MetricType:
5771
counter = self.metrics.get_and_reset(metric_type)
72+
if len(counter) == 0:
73+
return
5874
data = {
5975
"app": self.app_key,
6076
"type": metric_type.value,
@@ -70,13 +86,30 @@ def update_metrics(self):
7086
return
7187

7288

73-
class TraceSampler:
89+
class TraceSampler(metaclass=Singleton):
7490
def __init__(self, *args, **kwargs) -> None:
91+
self.stop = Event()
7592
self.config = Config()
7693
self.metrics = Metric()
77-
self.controller = ControllerClient(*args, self.config, self.metrics, **kwargs)
94+
self.controller = ControllerClient(
95+
*args, self.stop, self.config, self.metrics, **kwargs
96+
)
7897
self.controller.start()
7998

99+
signal.signal(signal.SIGINT, on_exit)
100+
101+
# HACK: Celery has a built in signal mechanism
102+
# so we use it
103+
if worker_shutdown:
104+
worker_shutdown.connect(on_exit)
105+
106+
def __del__(self):
107+
on_exit(self.stop, self.controller)
108+
109+
def kill(self):
110+
self.stop.set()
111+
self.controller.join()
112+
80113
def __call__(self, sampling_context):
81114
if sampling_context:
82115
if "wsgi_environ" in sampling_context:

sentry_dynamic_sampling_lib/utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,12 @@ def synchronized(wrapped, instance, args, kwargs):
77
lock = getattr(instance, "_lock")
88
with lock:
99
return wrapped(*args, **kwargs)
10+
11+
12+
class Singleton(type):
13+
_instances = {}
14+
15+
def __call__(cls, *args, **kwargs):
16+
if cls not in cls._instances:
17+
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
18+
return cls._instances[cls]

0 commit comments

Comments
 (0)