scwhittle commented on code in PR #40593:
URL: https://github.com/apache/beam/pull/40593#discussion_r4292549671


##########
runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/util/BoundedQueueExecutor.java:
##########
@@ -328,49 +346,129 @@ public synchronized void close() {
     }
   }
 
+  /**
+   * A {@link Runnable} for an {@link ExecutableWork} that can be claimed 
either by an executor
+   * thread in {@link #run} or by a multi-key bundle in {@link #pollWork}.
+   *
+   * <p>Thread safety: {@link #state} is {@code volatile} and transitions at 
most once from {@link
+   * #QUEUED} to {@link #STARTED} or {@link #STOLEN} via {@link #STATE}{@code 
.compareAndSet}.
+   * Non-final fields {@link #work} and {@link #handle} are written before 
{@code state} is
+   * initialized in the constructor, and are subsequently read and cleared 
only by the single thread
+   * that wins that {@code compareAndSet}. All other accesses read only {@code 
final} fields or
+   * {@code volatile state}.
+   */
   static final class QueuedWork implements Runnable {
 
-    private final ExecutableWork work;
-    private final BoundedQueueExecutorWorkHandleImpl handle;
-
-    public QueuedWork(ExecutableWork work, BoundedQueueExecutorWorkHandleImpl 
handle) {
+    private static final int QUEUED = 0;
+    private static final int STARTED = 1; // Claimed by an executor thread in 
run().
+    private static final int STOLEN = 2; // Claimed by a multi-key bundle in 
pollWork().
+
+    private static final AtomicIntegerFieldUpdater<QueuedWork> STATE =
+        AtomicIntegerFieldUpdater.newUpdater(QueuedWork.class, "state");
+
+    // Non-null iff the work is eligible to be stolen by a multi-key bundle.
+    private final @Nullable KeyGroupWorkIndex keyGroupWorkIndex;
+    private final KeyGroupWorkIndex.@Nullable QueueKey queueKey;
+    // Cleared upon claim so stale shells and claimed index entries do not 
retain work references.
+    private @Nullable ExecutableWork work;
+    private @Nullable BoundedQueueExecutorWorkHandleImpl handle;
+    private volatile int state = QUEUED;
+
+    QueuedWork(
+        ExecutableWork work,
+        BoundedQueueExecutorWorkHandleImpl handle,
+        @Nullable KeyGroupWorkIndex keyGroupWorkIndex) {
       this.work = work;
       this.handle = handle;
+      Work w = work.work();
+      if (keyGroupWorkIndex != null
+          && !w.getKeyGroup().equals(Work.KeyGroup.DEFAULT)
+          && !w.isMultiKeyBatchingDisabled()) {
+        this.keyGroupWorkIndex = keyGroupWorkIndex;
+        this.queueKey = new KeyGroupWorkIndex.QueueKey(w.getComputationId(), 
w.getKeyGroup());
+      } else {
+        this.keyGroupWorkIndex = null;
+        this.queueKey = null;
+      }
     }
 
     public ExecutableWork getWork() {

Review Comment:
   I think that this and getHandle are maybe only used by the other methods and 
could be removed?
   See if other methods could be made private or removed as well.



##########
runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/util/BoundedQueueExecutor.java:
##########
@@ -403,25 +501,52 @@ BoundedQueueExecutorWorkHandleImpl 
createBudgetHandle(Work work, long bytes) {
     checkArgument(handle instanceof BoundedQueueExecutorWorkHandleImpl);
     checkStateNotNull(onFailedWorkHandler);
     BoundedQueueExecutorWorkHandleImpl internalHandle = 
(BoundedQueueExecutorWorkHandleImpl) handle;
-    if (keyGroupWorkQueue == null) {
+    @Nullable KeyGroupWorkIndex index = keyGroupWorkIndex;
+    if (index == null) {
       return null;
     }
     while (true) {
-      @Nullable QueuedWork queuedWork = 
keyGroupWorkQueue.pollWork(computationId, keyGroup);
-      if (queuedWork == null) {
+      @Nullable QueuedWork stolen = index.pollAndClaim(computationId, 
keyGroup);
+      if (stolen == null) {
         return null;
       }
-      Work work = queuedWork.getWork().work();
+      ExecutableWork stolenWork = stolen.getWork();
+      BoundedQueueExecutorWorkHandleImpl stolenHandle = stolen.getHandle();
+      stolen.clearClaimedWork();
+      onWorkStolen();
+      Work work = stolenWork.work();
       if (work.isFailed()) {
-        queuedWork.getHandle().close();
+        stolenHandle.close();
         onFailedWorkHandler.onFailedWork(work);
         continue;
       }
-      internalHandle.merge(queuedWork.getHandle());
-      return queuedWork.getWork();
+      internalHandle.merge(stolenHandle);
+      return stolenWork;
     }
   }
 
+  /**
+   * Purges stale shells in bulk if they accumulate while all executor threads 
are busy running
+   * multi-key bundles.
+   */
+  private void onWorkStolen() {
+    if (workQueue.size() <= (long) elementsOutstanding.get() + 
maximumElementsOutstanding

Review Comment:
   could we remove checking elementsOutstanding and revert changing it to an 
atomic?
   
   seems like we could just use
   workQueue.size() < 2 * maximumElementsOutstanding
   
   That seems like it woudl trigger only if maximumElementsOutstanding stale 
entries are there



##########
runners/google-cloud-dataflow-java/worker/src/test/java/org/apache/beam/runners/dataflow/worker/util/BoundedQueueExecutorTest.java:
##########
@@ -742,25 +688,26 @@ public void 
testPollWork_purgeBacksOffWhileQueueHoldsMostlyUnstartedWork() throw
           createWorkWithCompIdAndKeyGroup("compA", keyGroup, ignored -> {}), 
10);
     }
 
-    // The first steal purges its own stale shell; the remaining steals back 
off until the queue
-    // grows by more than maximumElementsOutstanding.
-    for (int i = 0; i < numWorks; i++) {
-      assertNotNull(
-          testExecutor.pollWork("compA", keyGroup, stealHandle, 
FAILING_FAILED_WORK_HANDLER));
-    }
-    assertEquals(numWorks - 1, testExecutor.executorQueueSize());
-
-    for (int i = 0; i <= maximumElementsOutstanding; i++) {
-      testExecutor.forceExecute(
-          createWorkWithCompIdAndKeyGroup("compA", keyGroup, ignored -> {}), 
10);
+    // Each steal leaves a stale shell in the executor queue. While bundle1 is 
open, its stolen
+    // work is still counted in elementsOutstanding, so there is nothing to 
purge.
+    int numStolenByBundle1 = maximumElementsOutstanding + 2;
+    for (int i = 0; i < numStolenByBundle1; i++) {
+      assertNotNull(testExecutor.pollWork("compA", keyGroup, bundle1, 
FAILING_FAILED_WORK_HANDLER));
     }
-    assertNotNull(
-        testExecutor.pollWork("compA", keyGroup, stealHandle, 
FAILING_FAILED_WORK_HANDLER));
-    assertEquals(maximumElementsOutstanding, testExecutor.executorQueueSize());
+    assertEquals(numWorks, testExecutor.rawQueueSizeForTesting());

Review Comment:
   can we verify elementsOutstanding here and below not just the rawQeueuSize?



##########
runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/util/BoundedQueueExecutor.java:
##########


Review Comment:
   how about getQueueKey? at call-sites it is confusing with performing queuing 
action



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