FrankYang0529 commented on code in PR #75086:
URL: https://github.com/apache/airflow/pull/75086#discussion_r4345272155


##########
airflow-core/src/airflow/executors/local_executor.py:
##########
@@ -337,17 +338,74 @@ def _terminate_worker_process(self, proc: 
multiprocessing.Process) -> None:
     def _process_workloads(self, workload_list):
         for workload in workload_list:
             key = self.get_workload_key(workload)
-            self._read_results()
-            self.activity_queue.put(workload)
             removed = self.executor_queues[workload.type].pop(key, None)
             if not removed:
                 raise KeyError(f"Workload {key} was not found in any queue")
+            # Register before the put: results read while the put is in flight 
are dropped for keys
+            # that are not running yet.
             self.running.add(key)
             self._dispatch_counts[key] = self._dispatch_counts.get(key, 0) + 1
-        with self._unread_messages:
-            self._unread_messages.value += len(workload_list)
+            # Counted before the put so a worker taking the message never 
decrements below zero, and so
+            # _check_workers sees it as outstanding while the put is blocked.
+            with self._unread_messages:
+                self._unread_messages.value += 1
+            self._read_results()
+            if (put_error := self._put_workload(workload)) is not None:
+                self._undo_dispatch(key, workload)
+                raise put_error
         self._check_workers()
 
+    def _undo_dispatch(self, key: WorkloadKey, workload: ExecutorWorkload) -> 
None:
+        """Reverse the bookkeeping of a workload whose put failed, leaving it 
queued as it was before."""
+        self.executor_queues[workload.type][key] = workload
+        remaining = self._dispatch_counts.pop(key, 1) - 1
+        if remaining > 0:
+            self._dispatch_counts[key] = remaining

Review Comment:
   The test case `test_process_workloads_undoes_dispatch_when_put_fails` only 
covers a key with no earlier dispatch, so the test never reaches the `remaining 
> 0` branch of `_undo_dispatch()`. How about changing the test case like 
following?
   
   ```py
       @pytest.mark.parametrize(
           ("earlier_dispatches", "still_running"),
           [
               pytest.param(0, False, id="no-earlier-dispatch"),
               pytest.param(1, True, id="earlier-dispatch-still-running"),
           ],
       )
       def test_process_workloads_undoes_dispatch_when_put_fails(self, 
earlier_dispatches, still_running):
           """A failed put undoes only its own dispatch, so an earlier dispatch 
of the same key stays running."""
           executor = LocalExecutor(parallelism=1)
           executor.workers = {}
           executor.activity_queue = mock.MagicMock(spec=SimpleQueueType)
           executor.activity_queue.put.side_effect = ValueError("unpicklable")
           executor._read_results = mock.MagicMock(spec=executor._read_results)
           executor._unread_messages = multiprocessing.Value("I", 0)
           executor._check_workers = 
mock.MagicMock(spec=executor._check_workers)
           workload = _make_task_workload()
           key = executor.get_workload_key(workload)
           executor.executor_queues.setdefault(workload.type, {})[key] = 
workload
           if earlier_dispatches:
               # A resumed attempt reuses the key while the earlier dispatch 
has not reported its result yet.
               executor.running.add(key)
               executor._dispatch_counts[key] = earlier_dispatches
   
           with pytest.raises(ValueError, match="unpicklable"):
               executor._process_workloads([workload])
   
           assert (key in executor.running) is still_running
           assert executor._dispatch_counts.get(key, 0) == earlier_dispatches
           assert executor.executor_queues[workload.type][key] is workload
           assert executor._unread_messages.value == 0
   ```
   



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