Skip to content
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

Parent IDs are null for spawned spans with gevent auto-instrumentation #4379

Open
CagriYonca opened this issue Jan 6, 2025 · 2 comments
Open
Labels
bug Something isn't working

Comments

@CagriYonca
Copy link

Describe your environment

OS: MacOS
Python version: 3.12.4
telemetry.sdk.version: 1.28.2
telemetry.auto.version: 0.49b2

What happened?

I'm trying to use auto-instrument with gevent, but the parent ids are null for spawned spans. Am I missing anything or do you have any suggestion?

image

Steps to Reproduce

Server Code

from gevent import monkey

monkey.patch_all()

from flask import Flask, request

app = Flask(__name__)


@app.route("/server_request")
def server_request():
    print(request.args.get("param"))
    return "served"


if __name__ == "__main__":
    app.run(port=8082)
  • Run with:
    opentelemetry-instrument --traces_exporter console --metrics_exporter none python server_automatic.py

Client Code

import gevent

from sys import argv

from requests import get

from opentelemetry import trace
from opentelemetry.propagate import inject
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
    BatchSpanProcessor,
    ConsoleSpanExporter,
)
from opentelemetry.instrumentation.requests import RequestsInstrumentor

RequestsInstrumentor().instrument()

trace.set_tracer_provider(TracerProvider())
tracer = trace.get_tracer_provider().get_tracer(__name__)

trace.get_tracer_provider().add_span_processor(
    BatchSpanProcessor(ConsoleSpanExporter())
)

assert len(argv) == 2


def make_http_call(n=None):
    headers = {}
    inject(headers)
    return get(
        "http://localhost:8082/server_request",
        params={"param": argv[1]},
        headers=headers,
    )


def spawn_calls():
    with tracer.start_as_current_span("spawn_calls"):
        jobs = []
        jobs.append(gevent.spawn(make_http_call))
        jobs.append(gevent.spawn(make_http_call))
        jobs.append(gevent.spawn(make_http_call))
        gevent.joinall(jobs, timeout=2)


def launch_gevent_chain():
    with tracer.start_as_current_span("test"):
        gevent.spawn(spawn_calls).join()


gevent.spawn(launch_gevent_chain)

gevent.sleep(2)
  • Run with:
    python client.py otel-flask-gevent-testing

Expected Result

I was expecting to see spawn_calls's span id as spawned spans' pids.

Actual Result

Parent IDs in the results are null

Additional context

No response

Would you like to implement a fix?

None

@CagriYonca CagriYonca added the bug Something isn't working label Jan 6, 2025
@aabmass
Copy link
Member

aabmass commented Jan 10, 2025

Thanks for the clear bug description. We've had a few questions related to gevent lately but I don't think we explicitly support it today. We use the builtin contextvars module for propagating parent information which already works nicely with sync and asyncio call stacks.

Does gevent work with contextvars?

@sruggier
Copy link

Thanks for the clear bug description. We've had a few questions related to gevent lately but I don't think we explicitly support it today. We use the builtin contextvars module for propagating parent information which already works nicely with sync and asyncio call stacks.

Does gevent work with contextvars?

gevent uses greenlet internally, and greenlet has a Context Variables page in its docs, which explains that they deliberately adopt the same behaviour as threads: each greenlet has a fresh context by default.

I see there's an opentelemetry-instrumentation-threading package that propagates OpenTelemetry context into new threads. By extension, it would make sense to make an opentelemetry-instrumentation-greenlet package that similarly propagates OpenTelemetry context into greenlets.

I've found that the following works fine as a workaround, if one is able to modify or monkeypatch their codebase to ensure that it happens for every spawned greenlet:

g = Greenlet(...)
g.gr_context = contextvars.copy_context()
g.start()

To ensure only the OpenTelemetry context is propagated, one can do this instead of calling copy_context:

parent_context = opentelemetry.context.get_current()
new_context = contextvars.Context()
new_context.run(lambda: opentelemetry.context.attach(parent_context))
g.gr_context = new_context

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants