Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 34 additions & 33 deletions tableauserverclient/exponential_backoff.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,34 @@
import time

# Polling for server-side events (such as job completion) uses exponential backoff for the sleep intervals between polls
ASYNC_POLL_MIN_INTERVAL = 0.5
ASYNC_POLL_MAX_INTERVAL = 30
ASYNC_POLL_BACKOFF_FACTOR = 1.4


class ExponentialBackoffTimer:
"""Timer for polling server-side events with exponential backoff between attempts."""

def __init__(self, *, timeout=None):
self.start_time = time.time()
self.timeout = timeout
self.current_sleep_interval = ASYNC_POLL_MIN_INTERVAL

def sleep(self):
"""Sleep for the next backoff interval, raising TimeoutError if the timeout deadline has passed."""
max_sleep_time = ASYNC_POLL_MAX_INTERVAL
if self.timeout is not None:
elapsed = time.time() - self.start_time
if elapsed >= self.timeout:
raise TimeoutError(f"Timeout after {elapsed} seconds waiting for asynchronous event")
remaining_time = self.timeout - elapsed
# Usually, we would sleep for `ASYNC_POLL_MAX_INTERVAL`, but we don't want to sleep over the timeout
max_sleep_time = min(ASYNC_POLL_MAX_INTERVAL, remaining_time)
# We want to sleep at least for `ASYNC_POLL_MIN_INTERVAL`. This is important to ensure that, as we get
# closer to the timeout, we don't accidentally wake up multiple times and hit the server in rapid succession
# due to waking up to early from the `sleep`.
max_sleep_time = max(max_sleep_time, ASYNC_POLL_MIN_INTERVAL)

time.sleep(min(self.current_sleep_interval, max_sleep_time))
self.current_sleep_interval *= ASYNC_POLL_BACKOFF_FACTOR
from datetime import datetime, timedelta
import time

# Polling for server-side events (such as job completion) uses exponential backoff for the sleep intervals between polls
ASYNC_POLL_MIN_INTERVAL = 0.5
ASYNC_POLL_MAX_INTERVAL = 30
ASYNC_POLL_BACKOFF_FACTOR = 1.4


class ExponentialBackoffTimer:
"""Timer for polling server-side events with exponential backoff between attempts."""

def __init__(self, *, timeout=None):
self.start_time = datetime.now()
self.timeout = timeout
self.current_sleep_interval = ASYNC_POLL_MIN_INTERVAL

def sleep(self):
"""Sleep for the next backoff interval, raising TimeoutError if the timeout deadline has passed."""
max_sleep_time = ASYNC_POLL_MAX_INTERVAL
if self.timeout is not None:
elapsed = (datetime.now() - self.start_time).total_seconds()
if elapsed >= self.timeout:
raise TimeoutError(f"Timeout after {elapsed} seconds waiting for asynchronous event")
remaining_time = self.timeout - elapsed
# Usually, we would sleep for `ASYNC_POLL_MAX_INTERVAL`, but we don't want to sleep over the timeout
max_sleep_time = min(ASYNC_POLL_MAX_INTERVAL, remaining_time)
# We want to sleep at least for `ASYNC_POLL_MIN_INTERVAL`. This is important to ensure that, as we get
# closer to the timeout, we don't accidentally wake up multiple times and hit the server in rapid succession
# due to waking up to early from the `sleep`.
max_sleep_time = max(max_sleep_time, ASYNC_POLL_MIN_INTERVAL)

time.sleep(min(self.current_sleep_interval, max_sleep_time))
self.current_sleep_interval *= ASYNC_POLL_BACKOFF_FACTOR