From fd84e8f7980e41b003d9d83f0073df4d32d370d0 Mon Sep 17 00:00:00 2001 From: Bunlong Heng Date: Tue, 11 Aug 2026 15:14:43 -0400 Subject: [PATCH 1/8] fix: add shared integration request timeout constant --- api/integrations/common/constants.py | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 api/integrations/common/constants.py diff --git a/api/integrations/common/constants.py b/api/integrations/common/constants.py new file mode 100644 index 000000000000..d17252d73f14 --- /dev/null +++ b/api/integrations/common/constants.py @@ -0,0 +1,5 @@ +# Default timeout (in seconds) for outbound HTTP requests made by integration +# wrappers. Without an explicit timeout, ``requests`` waits indefinitely, so an +# unresponsive third-party endpoint can hang the worker thread that dispatches +# the event, leading to resource exhaustion. +INTEGRATION_REQUEST_TIMEOUT_SECONDS = 10 From 2a64d4dadf8941af229eafd11b613b5e58799651 Mon Sep 17 00:00:00 2001 From: Bunlong Heng Date: Tue, 11 Aug 2026 15:14:44 -0400 Subject: [PATCH 2/8] fix: add request timeout to grafana integration --- api/integrations/grafana/grafana.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/api/integrations/grafana/grafana.py b/api/integrations/grafana/grafana.py index 21289d13aa51..cbb21c3c4597 100644 --- a/api/integrations/grafana/grafana.py +++ b/api/integrations/grafana/grafana.py @@ -4,6 +4,10 @@ import requests +from integrations.common.constants import ( + INTEGRATION_REQUEST_TIMEOUT_SECONDS, +) + from audit.models import AuditLog from integrations.common.wrapper import AbstractBaseEventIntegrationWrapper from integrations.grafana.mappers import ( @@ -36,6 +40,7 @@ def _track_event(self, event: dict[str, Any]) -> None: url=self.url, headers=self._headers(), data=json.dumps(event), + timeout=INTEGRATION_REQUEST_TIMEOUT_SECONDS, ) logger.debug( From 612c5bca3aea74cbf4b67857a0c2e182543d979a Mon Sep 17 00:00:00 2001 From: Bunlong Heng Date: Tue, 11 Aug 2026 15:14:46 -0400 Subject: [PATCH 3/8] fix: add request timeout to new_relic integration --- api/integrations/new_relic/new_relic.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/api/integrations/new_relic/new_relic.py b/api/integrations/new_relic/new_relic.py index 622756f45d90..4b7a23a65b18 100644 --- a/api/integrations/new_relic/new_relic.py +++ b/api/integrations/new_relic/new_relic.py @@ -3,6 +3,10 @@ import requests +from integrations.common.constants import ( + INTEGRATION_REQUEST_TIMEOUT_SECONDS, +) + from audit.models import AuditLog from integrations.common.wrapper import AbstractBaseEventIntegrationWrapper @@ -20,7 +24,10 @@ def __init__(self, base_url: str, api_key: str, app_id: str): def _track_event(self, event: dict) -> None: # type: ignore[type-arg] response = requests.post( - self.url, headers=self._headers(), data=json.dumps(event) + self.url, + headers=self._headers(), + data=json.dumps(event), + timeout=INTEGRATION_REQUEST_TIMEOUT_SECONDS, ) logger.debug( "Sent event to NewRelic. Response code was %s" % response.status_code From 15ea8df9ea8cc14eac1fcf1d00bf249c50afaa1d Mon Sep 17 00:00:00 2001 From: Bunlong Heng Date: Tue, 11 Aug 2026 15:14:47 -0400 Subject: [PATCH 4/8] fix: add request timeout to dynatrace integration --- api/integrations/dynatrace/dynatrace.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/api/integrations/dynatrace/dynatrace.py b/api/integrations/dynatrace/dynatrace.py index 75bc41df566e..1f2f908ef1fc 100644 --- a/api/integrations/dynatrace/dynatrace.py +++ b/api/integrations/dynatrace/dynatrace.py @@ -3,6 +3,10 @@ import requests +from integrations.common.constants import ( + INTEGRATION_REQUEST_TIMEOUT_SECONDS, +) + from audit.models import AuditLog from audit.services import get_audited_instance_from_audit_log_record from features.models import Feature, FeatureState @@ -29,7 +33,10 @@ def __init__(self, base_url: str, api_key: str, entity_selector: str): def _track_event(self, event: dict) -> None: # type: ignore[type-arg] event["entitySelector"] = self.entity_selector response = requests.post( - self.url, headers=self._headers(), data=json.dumps(event) + self.url, + headers=self._headers(), + data=json.dumps(event), + timeout=INTEGRATION_REQUEST_TIMEOUT_SECONDS, ) logger.debug( "Sent event to Dynatrace. Response code was %s" % response.status_code From 5c11d863601d00b36a6db37854b7873b94f8ac32 Mon Sep 17 00:00:00 2001 From: Bunlong Heng Date: Tue, 11 Aug 2026 15:14:48 -0400 Subject: [PATCH 5/8] fix: add request timeout to datadog integration --- api/integrations/datadog/datadog.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/api/integrations/datadog/datadog.py b/api/integrations/datadog/datadog.py index 7fbd759b0dd9..f106c8f24f94 100644 --- a/api/integrations/datadog/datadog.py +++ b/api/integrations/datadog/datadog.py @@ -3,6 +3,10 @@ import requests +from integrations.common.constants import ( + INTEGRATION_REQUEST_TIMEOUT_SECONDS, +) + from audit.models import AuditLog from integrations.common.wrapper import AbstractBaseEventIntegrationWrapper @@ -46,7 +50,9 @@ def _track_event(self, event: dict) -> None: # type: ignore[type-arg] event["source_type_name"] = FLAGSMITH_SOURCE_TYPE_NAME response = self.session.post( - f"{self.events_url}?api_key={self.api_key}", data=json.dumps(event) + f"{self.events_url}?api_key={self.api_key}", + data=json.dumps(event), + timeout=INTEGRATION_REQUEST_TIMEOUT_SECONDS, ) logger.debug( "Sent event to DataDog. Response code was %s" % response.status_code From 79a8d61e2b6bbf8953dfa19ff15af5adf0590ce8 Mon Sep 17 00:00:00 2001 From: Bunlong Heng Date: Tue, 11 Aug 2026 15:14:49 -0400 Subject: [PATCH 6/8] fix: add request timeout to mixpanel integration --- api/integrations/mixpanel/mixpanel.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/api/integrations/mixpanel/mixpanel.py b/api/integrations/mixpanel/mixpanel.py index cc2820aaa53c..e391e47979c9 100644 --- a/api/integrations/mixpanel/mixpanel.py +++ b/api/integrations/mixpanel/mixpanel.py @@ -3,6 +3,10 @@ import requests +from integrations.common.constants import ( + INTEGRATION_REQUEST_TIMEOUT_SECONDS, +) + from environments.identities.models import Identity from environments.identities.traits.models import Trait from features.models import FeatureState @@ -30,7 +34,12 @@ def __init__(self, config: MixpanelConfiguration): } def _identify_user(self, user_data: MixpanelUserData) -> None: - response = requests.post(self.url, headers=self.headers, json=user_data) + response = requests.post( + self.url, + headers=self.headers, + json=user_data, + timeout=INTEGRATION_REQUEST_TIMEOUT_SECONDS, + ) logger.debug( "Sent event to Mixpanel. Response code was: %s" % response.status_code ) From 9b0a6dc359a54902fd225bea88de69ffc29aafe2 Mon Sep 17 00:00:00 2001 From: Bunlong Heng Date: Tue, 11 Aug 2026 15:14:50 -0400 Subject: [PATCH 7/8] fix: add request timeout to heap integration --- api/integrations/heap/heap.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/api/integrations/heap/heap.py b/api/integrations/heap/heap.py index 5b15d4ca1c17..5bec9548ac16 100644 --- a/api/integrations/heap/heap.py +++ b/api/integrations/heap/heap.py @@ -3,6 +3,10 @@ import requests +from integrations.common.constants import ( + INTEGRATION_REQUEST_TIMEOUT_SECONDS, +) + from environments.identities.models import Identity from environments.identities.traits.models import Trait from features.models import FeatureState @@ -21,7 +25,11 @@ def __init__(self, config: HeapConfiguration): self.url = f"{base_url}/api/track" def _identify_user(self, user_data: dict) -> None: # type: ignore[type-arg] - response = requests.post(self.url, json=user_data) + response = requests.post( + self.url, + json=user_data, + timeout=INTEGRATION_REQUEST_TIMEOUT_SECONDS, + ) logger.debug("Sent event to Heap. Response code was: %s" % response.status_code) def generate_user_data( From a44eef2d88f2421a1d6feb2f4f8a3925a25955a3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Aug 2026 19:15:23 +0000 Subject: [PATCH 8/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- api/integrations/datadog/datadog.py | 3 +-- api/integrations/dynatrace/dynatrace.py | 7 +++---- api/integrations/grafana/grafana.py | 3 +-- api/integrations/heap/heap.py | 7 +++---- api/integrations/mixpanel/mixpanel.py | 7 +++---- api/integrations/new_relic/new_relic.py | 3 +-- 6 files changed, 12 insertions(+), 18 deletions(-) diff --git a/api/integrations/datadog/datadog.py b/api/integrations/datadog/datadog.py index f106c8f24f94..b62d2b78679d 100644 --- a/api/integrations/datadog/datadog.py +++ b/api/integrations/datadog/datadog.py @@ -3,11 +3,10 @@ import requests +from audit.models import AuditLog from integrations.common.constants import ( INTEGRATION_REQUEST_TIMEOUT_SECONDS, ) - -from audit.models import AuditLog from integrations.common.wrapper import AbstractBaseEventIntegrationWrapper logger = logging.getLogger(__name__) diff --git a/api/integrations/dynatrace/dynatrace.py b/api/integrations/dynatrace/dynatrace.py index 1f2f908ef1fc..41b20c994c49 100644 --- a/api/integrations/dynatrace/dynatrace.py +++ b/api/integrations/dynatrace/dynatrace.py @@ -3,14 +3,13 @@ import requests -from integrations.common.constants import ( - INTEGRATION_REQUEST_TIMEOUT_SECONDS, -) - from audit.models import AuditLog from audit.services import get_audited_instance_from_audit_log_record from features.models import Feature, FeatureState from features.versioning.models import EnvironmentFeatureVersion +from integrations.common.constants import ( + INTEGRATION_REQUEST_TIMEOUT_SECONDS, +) from integrations.common.wrapper import AbstractBaseEventIntegrationWrapper from segments.models import Segment diff --git a/api/integrations/grafana/grafana.py b/api/integrations/grafana/grafana.py index cbb21c3c4597..8da5e68973f7 100644 --- a/api/integrations/grafana/grafana.py +++ b/api/integrations/grafana/grafana.py @@ -4,11 +4,10 @@ import requests +from audit.models import AuditLog from integrations.common.constants import ( INTEGRATION_REQUEST_TIMEOUT_SECONDS, ) - -from audit.models import AuditLog from integrations.common.wrapper import AbstractBaseEventIntegrationWrapper from integrations.grafana.mappers import ( map_audit_log_record_to_grafana_annotation, diff --git a/api/integrations/heap/heap.py b/api/integrations/heap/heap.py index 5bec9548ac16..bf419182baed 100644 --- a/api/integrations/heap/heap.py +++ b/api/integrations/heap/heap.py @@ -3,13 +3,12 @@ import requests -from integrations.common.constants import ( - INTEGRATION_REQUEST_TIMEOUT_SECONDS, -) - from environments.identities.models import Identity from environments.identities.traits.models import Trait from features.models import FeatureState +from integrations.common.constants import ( + INTEGRATION_REQUEST_TIMEOUT_SECONDS, +) from integrations.common.wrapper import AbstractBaseIdentityIntegrationWrapper from .constants import DEFAULT_HEAP_API_URL diff --git a/api/integrations/mixpanel/mixpanel.py b/api/integrations/mixpanel/mixpanel.py index e391e47979c9..ea9333eefa4d 100644 --- a/api/integrations/mixpanel/mixpanel.py +++ b/api/integrations/mixpanel/mixpanel.py @@ -3,13 +3,12 @@ import requests -from integrations.common.constants import ( - INTEGRATION_REQUEST_TIMEOUT_SECONDS, -) - from environments.identities.models import Identity from environments.identities.traits.models import Trait from features.models import FeatureState +from integrations.common.constants import ( + INTEGRATION_REQUEST_TIMEOUT_SECONDS, +) from integrations.common.wrapper import AbstractBaseIdentityIntegrationWrapper from .constants import DEFAULT_MIXPANEL_API_URL diff --git a/api/integrations/new_relic/new_relic.py b/api/integrations/new_relic/new_relic.py index 4b7a23a65b18..eb4b8513ae74 100644 --- a/api/integrations/new_relic/new_relic.py +++ b/api/integrations/new_relic/new_relic.py @@ -3,11 +3,10 @@ import requests +from audit.models import AuditLog from integrations.common.constants import ( INTEGRATION_REQUEST_TIMEOUT_SECONDS, ) - -from audit.models import AuditLog from integrations.common.wrapper import AbstractBaseEventIntegrationWrapper logger = logging.getLogger(__name__)