11import logging
2- from threading import Thread
2+ import signal
3+ from threading import Event , Thread
34from time import sleep
45
56import schedule
67from requests .exceptions import RequestException
78from requests_cache import CachedSession
89
910from 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
1118LOGGER = logging .getLogger ("SentryWrapper" )
1219
1320
21+ def on_exit (* args , ** kwargs ):
22+ ts = TraceSampler ()
23+ ts .kill ()
24+ raise KeyboardInterrupt
25+
26+
1427class 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 :
0 commit comments