[Bug] lambda_worker otel.apply_defaults leaks a span exporter thread and gRPC channel on every warm invocation
I maintainer di solito rispondono entro 1 giorno
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 1-2 giorni
- Idoneità per principianti
- 30/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Ferma
- Stack tecnologico
- aws, grpc, python
- Ambito
- backend, observability
Direzione di ricerca
Start in temporalio/contrib/aws/lambda_worker/otel.py at apply_defaults, then read the run_worker configuration path and its existing tests. The issue provides a minimal reproduction using _run_worker_internal; run it across repeated invocations and check whether exporter threads and gRPC channels keep accumulating. Done means repeated calls do not grow those resources, including when another provider already owns the global slot.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
What are you really trying to do?
Run a Temporal worker on AWS Lambda with tracing, following the README "Observability" example: call otel.apply_defaults(config, OtelOptions()) inside the configure callback passed to run_worker.
Describe the bug
run_worker calls the user's configure callback on every Lambda invocation (its docstring says so), and a warm container keeps the same process alive. apply_defaults builds a new replay-safe TracerProvider with a new BatchSpanProcessor(OTLPSpanExporter) each time (one daemon export thread and one gRPC channel) and then calls set_tracer_provider(). OpenTelemetry only lets the global provider be set once, so from the second invocation on:
- OpenTelemetry logs
Overriding of current TracerProvider is not allowed, - the new provider is never installed and never used (
OpenTelemetryPlugintakes the global one), and - it is never shut down, so its export thread and channel stay alive for the life of the container.
Result: one extra OtelBatchSpanRecordProcessor thread and gRPC channel per warm invocation, forever.
Expected: the documented per-invocation configure + apply_defaults pattern should be safe to run repeatedly, i.e. no growth in threads or channels across warm invocations.
Minimal Reproduction
Drives the real _run_worker_internal handler with a fake connect and a fake worker, so it needs no Lambda and no collector:
import gc, logging, threading
from opentelemetry import trace
from temporalio.common import WorkerDeploymentVersion
from temporalio.contrib.aws.lambda_worker import LambdaWorkerConfig, otel
from temporalio.contrib.aws.lambda_worker._run_worker import _run_worker_internal, _WorkerDeps
from temporalio.envconfig import ClientConfigProfile
logging.basicConfig(level=logging.WARNING, format="LOG %(levelname)s %(name)s: %(message)s")
class FakeWorker:
async def run(self):
return
async def fake_connect(**kwargs):
return object()
def configure(config: LambdaWorkerConfig) -> None: # the README example
config.worker_config["task_queue"] = "tq"
otel.apply_defaults(config, otel.OtelOptions())
deps = _WorkerDeps(
connect=fake_connect,
create_worker=lambda client, **kw: FakeWorker(),
load_config=lambda: ClientConfigProfile(address="localhost:7233"),
getenv=lambda k: None,
)
handler = _run_worker_internal(WorkerDeploymentVersion("svc", "v1"), configure, deps)
providers = []
for invocation in range(1, 6):
handler({}, None) # one Lambda invocation in a warm container
gc.collect()
providers.append(trace.get_tracer_provider())
n = sum(1 for t in threading.enumerate() if t.name == "OtelBatchSpanRecordProcessor")
print(f"after invocation {invocation}: OTLP span-export threads alive={n}, total threads={threading.active_count()}")
print("global tracer provider is the same object every time:", len({id(p) for p in providers}) == 1)
Output on main:
LOG WARNING opentelemetry.trace: Overriding of current TracerProvider is not allowed (x4)
after invocation 1: OTLP span-export threads alive=1, total threads=2
after invocation 2: OTLP span-export threads alive=2, total threads=3
after invocation 3: OTLP span-export threads alive=3, total threads=4
after invocation 4: OTLP span-export threads alive=4, total threads=5
after invocation 5: OTLP span-export threads alive=5, total threads=6
global tracer provider is the same object every time: True
Environment/Versions
- OS and processor: macOS (Apple Silicon), Python 3.12.14
- Temporal Version:
temporaliomainat2a7d321c(version 1.35.0), with the native bridge from the 1.34.0 wheel; only pure-Python code frommaindiffers. Extrasopentelemetry-exporter-otlp-proto-grpcandopentelemetry-sdk-extension-awsinstalled. - Docker or Kubernetes or building Temporal from source? No. This is a simulated invocation loop; I have not run it on a real Lambda or against a real collector.
Additional context
The relevant code is temporalio/contrib/aws/lambda_worker/otel.py (apply_defaults). I have a fix ready that installs the provider once per process and reuses it on later calls (and shuts an unused provider down when another provider already owns the global slot, e.g. ADOT auto-instrumentation), with tests. Design notes I would like your view on: the first call wins (endpoint and service name are fixed for the process, which matches how they are configured today), and a foreign global provider still triggers OpenTelemetry's own "Overriding" warning on each call.
- Lingua principale
- Python
- Stelle
- 1.2k
- Fork
- 285
- Merge medio
- 2g 1h
- PR unite (30g)
- 81
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di temporalio/sdk-python
-
[Bug] An activity method with an annotated `self` receives its arguments without their typesForse già presa @VegetarianOrc l’ha presa oggi. Apertabug sdk-python
temporalio/sdk-python#2063 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
Cloud CI Skips Nexus TestsForse di nuovo libera @tconley1428 l’ha presa 83 giorni fa e non c’è nessuna pull request aperta. Aperta
temporalio/sdk-python#1704 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
Windows ARM64 wheel supportAperta
Difficoltà 4/5 3-5 giorni Idoneità per principianti 45/100
temporalio/sdk-python#1592 · 8 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 4/5 3-5 giorni Idoneità per principianti 48/100
temporalio/sdk-python#1578 · 2 commenti · 2 reazioni ·
I maintainer di solito rispondono entro 1 giorno
-
[Bug] Headers should always run through external storageForse di nuovo libera @jmaeagle99 l’ha presa 120 giorni fa e non c’è nessuna pull request aperta. Apertabug
temporalio/sdk-python#1395 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di temporalio/sdk-python
Issue simili
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 78/100
521xueweihan/HelloGitHub#4237 ·
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
jejjohnson/gaussx#721 ·
I maintainer di solito rispondono entro 1 giorno
-
mock-core-configs/etc/mock/rocky-10-riscv64.cfg contains the wrong architecture in config_opts['root']Forse già presa @jasonmontleon l’ha presa 4 giorni fa. Aperta
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 94/100
rpm-software-management/mock#1834 ·
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 75/100
elastic/elastic-otel-python#683 ·
I maintainer di solito rispondono entro 1 giorno
-
ci-rerun: collect.py fails a whole pr-babysit cycle on one transient GitHub 5xx and never retriesApertafollow-up
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
I maintainer di solito rispondono entro 1 giorno