dsuhinin commented on PR #68393:
URL: https://github.com/apache/airflow/pull/68393#issuecomment-5093745951

   > The consolidation is a genuine improvement and mirroring the traces shape 
is the right direction — thanks for pushing this through so many rounds.
   > 
   > Caveat up front: I have **not** run this locally, so treat all of this as 
reading-the-diff impressions rather than verified behaviour. Most of it is 
small stuff left inline. One thing does worry me though.
   > 
   > **Removing the `lifespan` stats init.** `settings.initialize()` runs 
exactly once, from `airflow/__init__.py:79`, at first `import airflow` — it is 
_not_ re-run in a forked child. Every call site this PR removes was a post-fork 
re-init, and the API server one looks like the exposed case: it runs gunicorn 
with `"preload_app": True` 
(`airflow-core/src/airflow/api_fastapi/gunicorn_app.py:276`), so `import 
airflow` happens in the master and workers are forked from it. `lifespan` was 
the only per-worker hook.
   > 
   > `stats.initialize()` sets `_backend = None`, and that is what forces the 
OTel `MeterProvider` — and its `PeriodicExportingMetricReader` background 
thread — to be rebuilt in the child. That thread doesn't survive `fork()`. If 
anything touches `Stats` during gunicorn preload, `_backend` gets built in the 
master and every worker inherits a provider whose exporter thread is dead, with 
nothing left to reset it. That's the same failure mode #64703 fixed, and 
`_initialize_api_server_stats()` was added deliberately in #68514 for it.
   > 
   > Centralising the _configuration_ in `settings.initialize()` seems right to 
me — it's the per-process reset I think needs to stay at each fork boundary, 
either as the explicit calls or an `os.register_at_fork` hook. Could you 
sanity-check it against a multi-worker api-server before this goes in? It may 
also be related to what @xBis7 reported today about task-SDK metrics reaching 
Prometheus but querying empty, which is still open.
   > 
   > The rest is inline — the traces exporter default change is the one I'd 
most like a newsfragment for.
   
   Thanks for the thorough read.                                                
                                                                                
                                                         
                                                                                
                                                                                
                                                                                
                                                                               
     On the fork boundary reset: it does stay — it just doesn't live at the 
call sites anymore. stats.py registers an 
os.register_at_fork(after_in_child=...) hook at module level (added in #63932, 
before this PR) that resets _backend in every forked child, so the first metric 
emission in a worker rebuilds the backend 
     through the factory. The dead-exporter-thread case is handled too: 
get_otel_logger() resets the OTel SDK's Once() guard before 
set_meter_provider() (the #64690 fix), so the child installs a fresh 
MeterProvider with a live PeriodicExportingMetricReader thread instead of 
silently keeping the parent's stale one. The
     calls this PR removes were redundant with that hook — the lifespan init 
wasn't the only per-worker reset.                                               
                                                                                
                                                                                
  
                                                                                
                                                                                
                                                                                
                                                                               
     Sanity check, as requested: I ran the API server with gunicorn 
(preload_app, 2 workers) and a test plugin that touches Stats at import time — 
i.e. the backend and its exporter thread get built in the master before any 
fork, which is the worst case you describe. The master exported its metric, and 
after forking   
     both workers exported request-time metrics tagged with their own PIDs via 
the console exporter. So the per-worker rebuild works end to end.               
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
                                                                               
     Test coverage: the at-fork reset had no test, so I added one — it forks a 
real child and asserts the backend is reset and rebuilt fresh 
(shared/observability/tests/.../test_stats.py::TestBackendResetAfterFork).      
                                                                                
                  
                                                                                
                                                                                
                                                                                
                                                                               
     Newsfragment: added for the traces default transport change (otlp now 
defaults to http/protobuf per the OTel spec instead of gRPC), including how to 
restore the old behaviour via OTEL_EXPORTER_OTLP_PROTOCOL=grpc.                 
                                                                                
     
                                                                                
                                                                                
                                                                                
                                                                               
     On the task-SDK metrics report — I don't think it's related to this reset 
path (that's the supervisor/task runner side, not a gunicorn fork), but happy 
to look at it separately.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to