ferruzzi opened a new issue, #65570:
URL: https://github.com/apache/airflow/issues/65570

   We have four `WatchedSubprocess` subclasses that each implement 
`_handle_request` with large if/elif chains dispatching comms messages.  Many 
of these handlers are duplicated inline across multiple supervisors:
   
   - ActivitySubprocess (task-sdk, supervisor.py)
   - CallbackSubprocess (task-sdk, callback_supervisor.py)
   - TriggerRunnerSupervisor (airflow-core, triggerer_job_runner.py)
   - DagFileProcessorProcess (airflow-core, processor.py)
   
   A `request_handlers.py` module already exists in 
`task-sdk/src/airflow/sdk/execution_time/` with shared handlers for 
`GetConnection`, `GetVariable`, `GetAssetByName`, `GetAssetByUri`, and 
`MaskSecret`.  The Callback and Activity supervisors use these, but the 
Triggerer and DFP still have inline duplicates.
   
   ## The Task:
   
   Extract the remaining duplicated handler logic into shared functions in 
`request_handlers.py` and have all four supervisors call them.
   
   Note: The implementations across supervisors are similar but not identical.  
Some include `isinstance()` guards on the response; some pass different 
parameter subsets; some set `dump_opts` while others don't.  When extracting, 
don't just copy one supervisor's version verbatim.  Compare all implementations 
of each handler and produce a single version that incorporates the best 
practices from each (e.g.  proper response type checking, consistent dump_opts, 
full parameter forwarding).
   
   Here is my suggestion, but you may come up with a better set of rules once 
you start working on it:
   
   0. Use the existing shared handlers as a guide, but don't be afraid to 
modify them if you come up with a new standard.
   1. Each new handler should be a standalone function with this signature:
       ```python
       def handle_<message_type>(client: Client, msg: <MessageType>) -> 
tuple[BaseModel | None, dict[str, bool]]:
       ```
   2. Always guard the response with `isinstance` before converting it to a 
comms result model.  The API client can return `ErrorResponse` on failure; when 
that happens, pass it through unchanged.
   3. Always return dump_opts as the second tuple element.  Use 
`{"exclude_unset": True}` for result models that wrap API responses to avoid 
serializing `None` fields.  Add `{"by_alias": True}` when the result model uses 
field aliases (currently only `ConnectionResult`).  For simple pass-through 
responses or error responses, return `{}`.
   4. Forward all message fields to the client call.  Some supervisors 
currently pass fewer parameters than others for the same message type (e.g.  
the Triggerer's `GetXCom` omits `include_prior_dates`).  The shared handler 
should forward every field that the message carries and let the client and 
server handle defaults for optional fields.
   5. Always mask secrets.  If the response contains sensitive data (passwords, 
tokens, variable values), call `mask_secret()` before returning.  Look at 
`handle_get_connection` and `handle_get_variable` for examples.
   6. Handlers that don't return a response such as `PutVariable` and 
`DeleteXCom` can use a simpler signature.  `return (None, {})` or return `None` 
directly like `handle_mask_secret` does.  Pick whichever is cleaner for the 
specific case, but be consistent within a batch.
   7. Keep handlers stateless.  They should not touch subprocess internals 
(exit codes, terminal states, etc.).  If a message type requires updating 
subprocess state, it belongs inline in that supervisor's `_handle_request`, not 
in a shared handler.
   
   ### Phase 1: High-overlap handlers (three or more supervisors share this 
logic)
   
   - GetXCom - Activity, Triggerer, DFP
   - PutVariable - Activity, Triggerer, DFP
   - DeleteVariable - Activity, Triggerer, DFP
   - GetTICount - Activity, Triggerer, DFP
   - GetTaskStates - Activity, Triggerer, DFP
   - GetPreviousTI - Activity, Triggerer, DFP
   
   ### Phase 2: Medium-overlap handlers (two supervisors share these)
   
   - SetXCom - Activity, Triggerer
   - DeleteXCom - Activity, Triggerer
   - GetDRCount - Activity, Triggerer
   - GetDagRunState - Activity, Triggerer
   - GetPreviousDagRun - Activity, DFP
   - GetPrevSuccessfulDagRun - Activity, DFP
   - GetXComCount - Activity, DFP
   - GetXComSequenceItem - Activity, DFP
   - GetXComSequenceSlice - Activity, DFP
   
   ### Phase 2.5: Callback Supervisor
   
   Comms channels were added to the Callback supervisor in 
https://github.com/apache/airflow/pull/65269 and all of the comms channels it 
has as of the time I am writing this are already in shared helpers, but have a 
look and make sure that is still true while you are doing this.
   
   ### Phase 3: Migrate DagFileProcessorProcess onto shared handlers
   
   The DFP currently has fully inline versions of `GetConnection`, 
`GetVariable`, and `MaskSecret` even though shared handlers already exist.  One 
thing to note: the DFP's inline `GetConnection` handler skips the 
`mask_secret()` calls on `password` and `extra` that the shared handler 
performs, which I personally feel is a bug but may require discussion.  Maybe 
there's a reason I'm not aware of.
   
   ## Out of Scope:
   
   Supervisor-specific messages (e.g.  `TaskState`, `DeferTask`, 
`TriggerStateChanges`, `DagFileParsingResult`) should either stay inline or 
move to a supervisor-specific helper/utils module (I vote leave alone, 
personally) since they interact with internal subprocess state.
   


-- 
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