-
Notifications
You must be signed in to change notification settings - Fork 306
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[statsd] Add ability to toggle statsd.disable_buffering
state during runtime
#700
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -304,59 +304,109 @@ def __init__( | |
|
||
self._reset_buffer() | ||
|
||
# This lock is used for backwards compatibility to prevent concurrent | ||
# changes to the buffer when the user is managing the buffer themselves | ||
# via deprecated `open_buffer()` and `close_buffer()` functions. | ||
self._manual_buffer_lock = RLock() | ||
# This lock is used for all cases where buffering functionality is | ||
# being toggled (by `open_buffer()`, `close_buffer()`, or | ||
# `self._disable_buffering` calls). | ||
self._buffering_toggle_lock = RLock() | ||
|
||
# If buffering is disabled, we bypass the buffer function. | ||
self._send = self._send_to_buffer | ||
if disable_buffering: | ||
log.info("Statsd buffering is disabled") | ||
self._disable_buffering = disable_buffering | ||
if self._disable_buffering: | ||
self._send = self._send_to_server | ||
log.info("Statsd buffering is disabled") | ||
|
||
# Start the flush thread if buffering is enabled and the interval is above | ||
# a reasonable range. This both prevents thrashing and allow us to use "0.0" | ||
# as a value for disabling the automatic flush timer as well. | ||
if not disable_buffering and flush_interval >= MIN_FLUSH_INTERVAL: | ||
self._register_flush_thread(flush_interval) | ||
log.debug( | ||
"Statsd flush thread registered with period of %s", | ||
flush_interval, | ||
) | ||
else: | ||
log.info("Statsd periodic buffer flush is disabled") | ||
self._flush_interval = flush_interval | ||
self._flush_thread_stop = threading.Event() | ||
self._start_flush_thread(self._flush_interval) | ||
|
||
def disable_telemetry(self): | ||
self._telemetry = False | ||
|
||
def enable_telemetry(self): | ||
self._telemetry = True | ||
|
||
def _register_flush_thread(self, sleep_duration): | ||
def _flush_thread_loop(self, sleep_duration): | ||
while True: | ||
time.sleep(sleep_duration) | ||
# Note: Invocations of this method should be thread-safe | ||
def _start_flush_thread(self, flush_interval): | ||
if self._disable_buffering or self._flush_interval <= MIN_FLUSH_INTERVAL: | ||
log.info("Statsd periodic buffer flush is disabled") | ||
return | ||
|
||
def _flush_thread_loop(self, flush_interval): | ||
while not self._flush_thread_stop.is_set(): | ||
time.sleep(flush_interval) | ||
self.flush() | ||
|
||
self._flush_thread = threading.Thread( | ||
name="{}_flush_thread".format(self.__class__.__name__), | ||
target=_flush_thread_loop, | ||
args=(self, sleep_duration,), | ||
args=(self, flush_interval,), | ||
) | ||
self._flush_thread.daemon = True | ||
self._flush_thread.start() | ||
|
||
log.debug( | ||
"Statsd flush thread registered with period of %s", | ||
self._flush_interval, | ||
) | ||
|
||
# Note: Invocations of this method should be thread-safe | ||
def _stop_flush_thread(self): | ||
if not self._flush_thread: | ||
log.warning("No statsd flush thread to stop") | ||
return | ||
|
||
try: | ||
self.flush() | ||
finally: | ||
pass | ||
|
||
self._flush_thread_stop.set() | ||
|
||
self._flush_thread.join() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not really a big deal I guess but if two threads are calling There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. True that this method isn't thread-safe in isolation but the comment on the method mentions this as a warning and the only caller is lock-protected. I would have preferred to use the lock directly in this method like you imply but then the toggle to stop would needed 2 lock acquires (1 for toggle method and 1 for stop method; possibly a reentrant one) which seemed like an overkill and over-complication for a helper only called from a single spot. |
||
self._flush_thread = None | ||
|
||
self._flush_thread_stop.clear() | ||
|
||
def _dedicated_telemetry_destination(self): | ||
return bool(self.telemetry_socket_path or self.telemetry_host) | ||
|
||
# Context manager helper | ||
def __enter__(self): | ||
self.open_buffer() | ||
return self | ||
|
||
# Context manager helper | ||
def __exit__(self, exc_type, value, traceback): | ||
self.close_buffer() | ||
|
||
@property | ||
def disable_buffering(self): | ||
with self._buffering_toggle_lock: | ||
return self._disable_buffering | ||
|
||
@disable_buffering.setter | ||
def disable_buffering(self, is_disabled): | ||
with self._buffering_toggle_lock: | ||
# If the toggle didn't change anything, this method is a noop | ||
if self._disable_buffering == is_disabled: | ||
return | ||
|
||
self._disable_buffering = is_disabled | ||
|
||
# If buffering has been disabled, flush and kill the background thread | ||
# otherwise start up the flushing thread and enable the buffering. | ||
if is_disabled: | ||
self._send = self._send_to_server | ||
self._stop_flush_thread() | ||
log.info("Statsd buffering is disabled") | ||
else: | ||
self._send = self._send_to_buffer | ||
self._start_flush_thread(self._flush_interval) | ||
|
||
@staticmethod | ||
def resolve_host(host, use_default_route): | ||
""" | ||
|
@@ -448,7 +498,7 @@ def open_buffer(self, max_buffer_size=None): | |
Note: This method must be called before close_buffer() matching invocation. | ||
""" | ||
|
||
self._manual_buffer_lock.acquire() | ||
self._buffering_toggle_lock.acquire() | ||
|
||
# XXX Remove if `disable_buffering` default is changed to False | ||
self._send = self._send_to_buffer | ||
|
@@ -471,7 +521,7 @@ def close_buffer(self): | |
# XXX Remove if `disable_buffering` default is changed to False | ||
self._send = self._send_to_server | ||
|
||
self._manual_buffer_lock.release() | ||
self._buffering_toggle_lock.release() | ||
|
||
def _reset_buffer(self): | ||
with self._buffer_lock: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it named
disable_buffering
because the plan is to have the buffering enabled by default in the future? If that's the case, don't mind the following remark.Having a parameter "disabling" something (and defaulting to True) is always weird to think about opposed to a parameter "enabling" a feature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct. The long story is that it's currently a bit of an awkward flag name like you mention because we enabled-then-disabled buffering-by-default already and the flag that was already in the API was
disable_buffering
. If/when we re-enable buffering-by-default, this flag will be a better indication of the functionality it's just that for now we're a bit stuck with a mid-way change in architectural direction and the flag was already named in the published API.