sentry-arroyo 2.37.1__tar.gz → 2.38.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. {sentry_arroyo-2.37.1/sentry_arroyo.egg-info → sentry_arroyo-2.38.0}/PKG-INFO +1 -1
  2. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/processing/processor.py +4 -22
  3. sentry_arroyo-2.38.0/arroyo/utils/stuck_detector.py +58 -0
  4. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0/sentry_arroyo.egg-info}/PKG-INFO +1 -1
  5. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/sentry_arroyo.egg-info/SOURCES.txt +3 -1
  6. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/setup.py +1 -1
  7. sentry_arroyo-2.38.0/tests/utils/test_stuck_detector.py +46 -0
  8. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/LICENSE +0 -0
  9. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/MANIFEST.in +0 -0
  10. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/README.md +0 -0
  11. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/__init__.py +0 -0
  12. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/backends/__init__.py +0 -0
  13. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/backends/abstract.py +0 -0
  14. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/backends/kafka/__init__.py +0 -0
  15. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/backends/kafka/commit.py +0 -0
  16. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/backends/kafka/configuration.py +0 -0
  17. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/backends/kafka/consumer.py +0 -0
  18. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/backends/local/__init__.py +0 -0
  19. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/backends/local/backend.py +0 -0
  20. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/backends/local/storages/__init__.py +0 -0
  21. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/backends/local/storages/abstract.py +0 -0
  22. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/backends/local/storages/memory.py +0 -0
  23. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/commit.py +0 -0
  24. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/dlq.py +0 -0
  25. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/errors.py +0 -0
  26. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/processing/__init__.py +0 -0
  27. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/processing/strategies/__init__.py +0 -0
  28. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/processing/strategies/abstract.py +0 -0
  29. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/processing/strategies/batching.py +0 -0
  30. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/processing/strategies/buffer.py +0 -0
  31. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/processing/strategies/commit.py +0 -0
  32. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/processing/strategies/filter.py +0 -0
  33. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/processing/strategies/guard.py +0 -0
  34. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/processing/strategies/healthcheck.py +0 -0
  35. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/processing/strategies/noop.py +0 -0
  36. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/processing/strategies/produce.py +0 -0
  37. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/processing/strategies/reduce.py +0 -0
  38. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/processing/strategies/run_task.py +0 -0
  39. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/processing/strategies/run_task_in_threads.py +0 -0
  40. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/processing/strategies/run_task_with_multiprocessing.py +0 -0
  41. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/processing/strategies/unfold.py +0 -0
  42. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/py.typed +0 -0
  43. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/types.py +0 -0
  44. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/utils/__init__.py +0 -0
  45. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/utils/clock.py +0 -0
  46. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/utils/codecs.py +0 -0
  47. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/utils/concurrent.py +0 -0
  48. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/utils/logging.py +0 -0
  49. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/utils/metricDefs.json +0 -0
  50. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/utils/metric_defs.py +0 -0
  51. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/utils/metrics.py +0 -0
  52. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/utils/profiler.py +0 -0
  53. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/arroyo/utils/retries.py +0 -0
  54. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/examples/transform_and_produce/__init__.py +0 -0
  55. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/examples/transform_and_produce/batched.py +0 -0
  56. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/examples/transform_and_produce/script.py +0 -0
  57. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/examples/transform_and_produce/simple.py +0 -0
  58. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/requirements.txt +0 -0
  59. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/sentry_arroyo.egg-info/dependency_links.txt +0 -0
  60. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/sentry_arroyo.egg-info/not-zip-safe +0 -0
  61. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/sentry_arroyo.egg-info/requires.txt +0 -0
  62. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/sentry_arroyo.egg-info/top_level.txt +0 -0
  63. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/setup.cfg +0 -0
  64. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/backends/__init__.py +0 -0
  65. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/backends/mixins.py +0 -0
  66. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/backends/test_commit.py +0 -0
  67. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/backends/test_confluent_producer.py +0 -0
  68. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/backends/test_kafka.py +0 -0
  69. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/backends/test_kafka_commit_callback.py +0 -0
  70. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/backends/test_kafka_producer.py +0 -0
  71. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/backends/test_local.py +0 -0
  72. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/processing/__init__.py +0 -0
  73. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/processing/strategies/__init__.py +0 -0
  74. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/processing/strategies/test_all.py +0 -0
  75. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/processing/strategies/test_batching.py +0 -0
  76. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/processing/strategies/test_buffer.py +0 -0
  77. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/processing/strategies/test_commit.py +0 -0
  78. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/processing/strategies/test_filter.py +0 -0
  79. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/processing/strategies/test_guard.py +0 -0
  80. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/processing/strategies/test_noop.py +0 -0
  81. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/processing/strategies/test_produce.py +0 -0
  82. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/processing/strategies/test_reduce.py +0 -0
  83. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/processing/strategies/test_run_task.py +0 -0
  84. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/processing/strategies/test_run_task_in_threads.py +0 -0
  85. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/processing/strategies/test_run_task_with_multiprocessing.py +0 -0
  86. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/processing/strategies/test_unfold.py +0 -0
  87. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/processing/test_processor.py +0 -0
  88. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/test_commit.py +0 -0
  89. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/test_dlq.py +0 -0
  90. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/test_kip848_e2e.py +0 -0
  91. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/test_types.py +0 -0
  92. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/utils/__init__.py +0 -0
  93. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/utils/test_concurrent.py +0 -0
  94. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/utils/test_metrics.py +0 -0
  95. {sentry_arroyo-2.37.1 → sentry_arroyo-2.38.0}/tests/utils/test_retries.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sentry-arroyo
3
- Version: 2.37.1
3
+ Version: 2.38.0
4
4
  Summary: Arroyo is a Python library for working with streaming data.
5
5
  Home-page: https://github.com/getsentry/arroyo
6
6
  Author: Sentry
@@ -30,6 +30,7 @@ from arroyo.processing.strategies.abstract import (
30
30
  from arroyo.types import BrokerValue, Message, Partition, Topic, TStrategyPayload
31
31
  from arroyo.utils.logging import handle_internal_error
32
32
  from arroyo.utils.metrics import get_metrics
33
+ from arroyo.utils.stuck_detector import get_all_thread_stacks
33
34
 
34
35
  logger = logging.getLogger(__name__)
35
36
 
@@ -40,25 +41,6 @@ DEFAULT_JOIN_TIMEOUT = 25.0 # In seconds
40
41
  F = TypeVar("F", bound=Callable[[Any], Any])
41
42
 
42
43
 
43
- def get_all_thread_stacks() -> str:
44
- """Get stack traces from all threads without using signals."""
45
- import sys
46
- import threading
47
- import traceback
48
-
49
- stacks = []
50
- frames = sys._current_frames()
51
- threads_by_id = {t.ident: t for t in threading.enumerate()}
52
-
53
- for thread_id, frame in frames.items():
54
- thread = threads_by_id.get(thread_id)
55
- thread_name = thread.name if thread else f"Unknown-{thread_id}"
56
- stack = "".join(traceback.format_stack(frame))
57
- stacks.append(f"Thread {thread_name} ({thread_id}):\n{stack}")
58
-
59
- return "\n\n".join(stacks)
60
-
61
-
62
44
  def _rdkafka_callback(metrics: MetricsBuffer) -> Callable[[F], F]:
63
45
  def decorator(f: F) -> F:
64
46
  @functools.wraps(f)
@@ -166,9 +148,9 @@ class StreamProcessor(Generic[TStrategyPayload]):
166
148
  self.__processor_factory = processor_factory
167
149
  self.__metrics_buffer = MetricsBuffer()
168
150
 
169
- self.__processing_strategy: Optional[
170
- ProcessingStrategy[TStrategyPayload]
171
- ] = None
151
+ self.__processing_strategy: Optional[ProcessingStrategy[TStrategyPayload]] = (
152
+ None
153
+ )
172
154
 
173
155
  self.__message: Optional[BrokerValue[TStrategyPayload]] = None
174
156
 
@@ -0,0 +1,58 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import sys
5
+ import threading
6
+ import time
7
+ import traceback
8
+ from contextlib import contextmanager
9
+ from typing import Iterator
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ def get_all_thread_stacks() -> str:
15
+ """Get stack traces from all threads without using signals."""
16
+ stacks = []
17
+ frames = sys._current_frames()
18
+ threads_by_id = {t.ident: t for t in threading.enumerate()}
19
+
20
+ for thread_id, frame in frames.items():
21
+ thread = threads_by_id.get(thread_id)
22
+ thread_name = thread.name if thread else f"Unknown-{thread_id}"
23
+ stack = "".join(traceback.format_stack(frame))
24
+ stacks.append(f"Thread {thread_name} ({thread_id}):\n{stack}")
25
+
26
+ return "\n\n".join(stacks)
27
+
28
+
29
+ @contextmanager
30
+ def stuck_detector(
31
+ name: str = "stuck-detector",
32
+ timeout_seconds: float = 30.0,
33
+ ) -> Iterator[None]:
34
+ """
35
+ Context manager that spawns a daemon thread to detect stuck operations.
36
+ If the wrapped code block doesn't complete within timeout_seconds,
37
+ logs all thread stack traces for debugging.
38
+ """
39
+ done = threading.Event()
40
+
41
+ def detector() -> None:
42
+ start = time.time()
43
+ while not done.wait(timeout=1):
44
+ if time.time() - start > timeout_seconds:
45
+ logger.warning(
46
+ "%s: Operation stuck for %s seconds, stacks:\n%s",
47
+ name,
48
+ timeout_seconds,
49
+ get_all_thread_stacks(),
50
+ )
51
+ return
52
+
53
+ thread = threading.Thread(target=detector, daemon=True, name=name)
54
+ thread.start()
55
+ try:
56
+ yield
57
+ finally:
58
+ done.set()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sentry-arroyo
3
- Version: 2.37.1
3
+ Version: 2.38.0
4
4
  Summary: Arroyo is a Python library for working with streaming data.
5
5
  Home-page: https://github.com/getsentry/arroyo
6
6
  Author: Sentry
@@ -47,6 +47,7 @@ arroyo/utils/metric_defs.py
47
47
  arroyo/utils/metrics.py
48
48
  arroyo/utils/profiler.py
49
49
  arroyo/utils/retries.py
50
+ arroyo/utils/stuck_detector.py
50
51
  examples/transform_and_produce/__init__.py
51
52
  examples/transform_and_produce/batched.py
52
53
  examples/transform_and_produce/script.py
@@ -88,4 +89,5 @@ tests/processing/strategies/test_unfold.py
88
89
  tests/utils/__init__.py
89
90
  tests/utils/test_concurrent.py
90
91
  tests/utils/test_metrics.py
91
- tests/utils/test_retries.py
92
+ tests/utils/test_retries.py
93
+ tests/utils/test_stuck_detector.py
@@ -10,7 +10,7 @@ def get_requirements() -> Sequence[str]:
10
10
 
11
11
  setup(
12
12
  name="sentry-arroyo",
13
- version="2.37.1",
13
+ version="2.38.0",
14
14
  author="Sentry",
15
15
  author_email="oss@sentry.io",
16
16
  license="Apache-2.0",
@@ -0,0 +1,46 @@
1
+ from __future__ import annotations
2
+
3
+ import threading
4
+ from unittest.mock import patch
5
+
6
+ from arroyo.utils.stuck_detector import get_all_thread_stacks, stuck_detector
7
+
8
+
9
+ def test_get_all_thread_stacks() -> None:
10
+ """Test that get_all_thread_stacks returns stack traces for all threads."""
11
+ stacks = get_all_thread_stacks()
12
+ assert "MainThread" in stacks
13
+ assert "test_get_all_thread_stacks" in stacks # Current function should be in stack
14
+
15
+
16
+ def test_stuck_detector_does_not_trigger_before_timeout() -> None:
17
+ """Test that stuck detector doesn't trigger when operation completes quickly."""
18
+ with patch.object(
19
+ __import__("arroyo.utils.stuck_detector", fromlist=["logger"]).logger,
20
+ "warning",
21
+ ) as mock_warn:
22
+ with stuck_detector(timeout_seconds=30):
23
+ pass
24
+
25
+ mock_warn.assert_not_called()
26
+
27
+
28
+ def test_stuck_detector_logs_after_timeout() -> None:
29
+ """Test that stuck detector logs warning with stack traces when timeout exceeded."""
30
+ warning_logged = threading.Event()
31
+
32
+ def mock_warning(*args: object, **kwargs: object) -> None:
33
+ warning_logged.set()
34
+
35
+ with patch.object(
36
+ __import__("arroyo.utils.stuck_detector", fromlist=["logger"]).logger,
37
+ "warning",
38
+ side_effect=mock_warning,
39
+ ) as mock_warn:
40
+ with stuck_detector(timeout_seconds=0.05):
41
+ warning_logged.wait(timeout=2)
42
+
43
+ mock_warn.assert_called_once()
44
+ call_args = mock_warn.call_args
45
+ assert "Operation stuck" in call_args[0][0]
46
+ assert "MainThread" in call_args[0][3]
File without changes
File without changes
File without changes