Skip to content
This repository was archived by the owner on May 14, 2026. It is now read-only.
This repository was archived by the owner on May 14, 2026. It is now read-only.

gax-java: Flaky BatcherImplTest.testThrottlingBlocking - Zero interactions with this mock #1193

Description

@burkedavison

The below test failed and then passed on rerun.

Error:  Failures: 
Error:    BatcherImplTest.testThrottlingBlocking:897 
Wanted but not invoked:
apiCallContext.withOption(
    <Capturing argument>,
    <Capturing argument>
);
-> at com.google.api.gax.batching.BatcherImplTest.testThrottlingBlocking(BatcherImplTest.java:897)
Actually, there were zero interactions with this mock.

Activity

  1. added
    type: bugError or flaw in code with unintended results or allowing sub-optimal usage patterns.
    priority: p2Moderately-important priority. Fix may not be included in next release.
    on Dec 21, 2022
  2. meltsufin commented on Dec 22, 2022

    @meltsufin
    Contributor

    There's been many attempts to fix it with no luck: googleapis/gax-java#1615.

  3. blakeli0 commented on Dec 22, 2022

    @blakeli0
    Contributor

    @burkedavison How often did you see this? I haven't seen it in a while, like Mike mentioned, there has been some attempts and it should be less frequent now. If this still happens pretty often, we should prioritize it again.

  4. burkedavison commented on Dec 22, 2022

    @burkedavison
    MemberAuthor

    So far, it's happened only once.

  5. blakeli0 commented on Dec 27, 2022

    @blakeli0
    Contributor

    Noticed a different flaky tests in ScheduledRetryingExecutorTest.testCancelOuterFutureBeforeStart

    Error:  Tests run: 30, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 4.38 s <<< FAILURE! - in com.google.api.gax.retrying.ScheduledRetryingExecutorTest
    Error:  com.google.api.gax.retrying.ScheduledRetryingExecutorTest.testCancelOuterFutureBeforeStart[Custom retry settings: false]  Time elapsed: 0.023 s  <<< FAILURE!
    org.mockito.exceptions.verification.NoInteractionsWanted: 
    
    No interactions wanted here:
    -> at com.google.api.gax.retrying.AbstractRetryingExecutorTest.testCancelOuterFutureBeforeStart(AbstractRetryingExecutorTest.java:247)
    But found this interaction on mock 'tracer':
    -> at com.google.api.gax.retrying.FailingCallable.call(FailingCallable.java:85)
    Actually, above is the only interaction with this mock.
    	at com.google.api.gax.retrying.AbstractRetryingExecutorTest.testCancelOuterFutureBeforeStart(AbstractRetryingExecutorTest.java:247)
    

    See the exact log here

  6. blakeli0 commented on Jan 6, 2023

    @blakeli0
    Contributor

    Noticed another occurrence of this flaky test.

  7. burkedavison commented on Jan 17, 2023

    @burkedavison
    MemberAuthor

    Another, different flaky test in HttpJsonDirectServerStreamingCallableTest.testServerStreaming

    Error:  com.google.api.gax.httpjson.HttpJsonDirectServerStreamingCallableTest.testServerStreaming  Time elapsed: 10.068 s  <<< FAILURE!
    value of: await(...)
    expected to be true
    	at com.google.api.gax.httpjson.HttpJsonDirectServerStreamingCallableTest.testServerStreaming(HttpJsonDirectServerStreamingCallableTest.java:211)
    

    Build log

  8. burkedavison commented on Jan 18, 2023

    @burkedavison
    MemberAuthor

    Noticed another occurrence of this flaky test.

  9. burkedavison commented on Jan 19, 2023

    @burkedavison
    MemberAuthor
  10. burkedavison commented on Jan 24, 2023

    @burkedavison
    MemberAuthor

    Another, different flaky test in AbstractRetryingExecutorTest.testCancelOuterFutureBeforeStart

    Error:  Failures: 
    Error:    ScheduledRetryingExecutorTest>AbstractRetryingExecutorTest.testCancelOuterFutureBeforeStart:247 
    No interactions wanted here:
    -> at com.google.api.gax.retrying.AbstractRetryingExecutorTest.testCancelOuterFutureBeforeStart(AbstractRetryingExecutorTest.java:247)
    But found this interaction on mock 'tracer':
    -> at com.google.api.gax.retrying.FailingCallable.call(FailingCallable.java:85)
    Actually, above is the only interaction with this mock.
    

    Build log

  11. burkedavison commented on Jan 24, 2023

    @burkedavison
    MemberAuthor
  12. burkedavison commented on Feb 2, 2023

    @burkedavison
    MemberAuthor

    Flaked last night on Mac

  13. burkedavison commented on Feb 13, 2023

    @burkedavison
    MemberAuthor

    Flaked on nightly Ubuntu

  14. burkedavison commented on Mar 1, 2023

    @burkedavison
    MemberAuthor

    Flake on Mac Java 11 and Java 17

  15. 7 remaining items

  16. diegomarquezp commented on Apr 18, 2023

    @diegomarquezp
    Contributor

    https://github.com/googleapis/gapic-generator-java/actions/runs/4727585757/jobs/8388342665

    Error:  Tests run: 25, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 36.4 s <<< FAILURE! - in com.google.api.gax.batching.BatcherImplTest
    Error:  com.google.api.gax.batching.BatcherImplTest.testThrottlingBlocking  Time elapsed: 0.136 s  <<< FAILURE!
    adding elements to batcher should be blocked by FlowControlled
    	at com.google.api.gax.batching.BatcherImplTest.testThrottlingBlocking(BatcherImplTest.java:885)
    
  17. burkedavison commented on May 16, 2023

    @burkedavison
    MemberAuthor

    https://github.com/googleapis/sdk-platform-java/actions/runs/4986876999/jobs/8928104258

    Error:  com.google.api.gax.batching.BatcherImplTest.testThrottlingBlocking  Time elapsed: 0.083 s  <<< FAILURE!
    expected to be at least: 50
    but was                : 47
    	at com.google.api.gax.batching.BatcherImplTest.testThrottlingBlocking(BatcherImplTest.java:902)
    
  18. alicejli commented on Jun 22, 2023

    @alicejli
    Contributor

    I spent a little time when I first got it to try and replicate it using the steps from googleapis/gax-java#1775 but unfortunately wasn't able to replicate it. I chatted with @burkedavison about whether increasing the timeout was worth doing but ultimately since this happens infrequently, I didn't implement anything and haven't looked at it in a while. I'm going to unassign myself and bump down to a P3 as it currently is not a priority.

  19. removed their assignment
    on Jun 22, 2023
  20. added
    priority: p3Desirable enhancement or fix. May not be included in next release.
    and removed
    priority: p2Moderately-important priority. Fix may not be included in next release.
    on Jun 22, 2023
  21. alicejli commented on Jul 11, 2023

    @alicejli
    Contributor

    Failed on Java8 for Windows:

    Error:  Failures: 
    Error:    BatcherImplTest.testThrottlingBlocking:899 
    apiCallContext.withOption(
        <Capturing argument>,
        <Capturing argument>
    );
    Wanted 1 time:
    -> at com.google.api.gax.batching.BatcherImplTest.testThrottlingBlocking(BatcherImplTest.java:899)
    But was 0 times.
    

    #1838

  22. alicejli commented on Sep 6, 2023

    @alicejli
    Contributor

    Failed on Java8 for Windows:

    Error:  Failures: 
    Error:    BatcherImplTest.testThrottlingBlocking:899 
    apiCallContext.withOption(
        <Capturing argument>,
        <Capturing argument>
    );
    Wanted 1 time:
    -> at com.google.api.gax.batching.BatcherImplTest.testThrottlingBlocking(BatcherImplTest.java:899)
    But was 0 times.
    
    [INFO] 
    Error:  Tests run: 552, Failures: 1, Errors: 0, Skipped: 0
    
  23. alicejli commented on Sep 6, 2023

    @alicejli
    Contributor

    #1957
    Failed on Java 11 for Windows:

    Error:  Failures: 
    Error:    BatcherImplTest.testThrottlingBlocking:899 
    Wanted but not invoked:
    apiCallContext.withOption(
        <Capturing argument>,
        <Capturing argument>
    );
    -> at com.google.api.gax.batching.BatcherImplTest.testThrottlingBlocking(BatcherImplTest.java:899)
    Actually, there were zero interactions with this mock.
    
  24. alicejli commented on Sep 6, 2023

    @alicejli
    Contributor

    #1955
    Fialed on Java 8 for Windows:

    Error:  Failures: 
    Error:    BatcherImplTest.testThrottlingBlocking:899 
    apiCallContext.withOption(
        <Capturing argument>,
        <Capturing argument>
    );
    Wanted 1 time:
    -> at com.google.api.gax.batching.BatcherImplTest.testThrottlingBlocking(BatcherImplTest.java:899)
    But was 0 times.
    
  25. changed the title [-]gax-java: Flaky BatcherImplTest.testThrottlingBlocking[/-] [+]gax-java: Flaky BatcherImplTest.testThrottlingBlocking - Zero interactions with this mock[/+] on Sep 7, 2023
  26. suztomo commented on Sep 13, 2023

    @suztomo
    Member

    Reproduce the issue

    Install the required modules locally

    ~/sdk-platform-java $ mvn -am -pl gax-java/gax install
    

    IntelliJ's repeat test N times (This didn't work)

    I tried to run the test 100 times and it failed at 2nd run.

    Screenshot 2023-09-12 at 11 14 52 PM
    java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask@497486b3[Not completed, task = java.util.concurrent.Executors$RunnableAdapter@736caf7a[Wrapped task = com.google.api.gax.batching.BatcherImpl$PushCurrentBatchRunnable@4ce7fffa]] rejected from java.util.concurrent.ScheduledThreadPoolExecutor@63f8276e[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 74]
    
    	at java.base/java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2055)
    	at java.base/java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:825)
    	at java.base/java.util.concurrent.ScheduledThreadPoolExecutor.delayedExecute(ScheduledThreadPoolExecutor.java:340)
    	at java.base/java.util.concurrent.ScheduledThreadPoolExecutor.scheduleWithFixedDelay(ScheduledThreadPoolExecutor.java:680)
    	at java.base/java.util.concurrent.Executors$DelegatedScheduledExecutorService.scheduleWithFixedDelay(Executors.java:788)
    	at com.google.api.gax.batching.BatcherImpl.<init>(BatcherImpl.java:206)
    	at com.google.api.gax.batching.BatcherImplTest.testThrottlingBlocking(BatcherImplTest.java:855)
    

    Maybe the test method is not meant to run twice on the same instance.

    Repeat 100 times with shell script (It didn't work)

    at sdk-platform-java repository clone directory:

    $ repeat 100 mvn -pl gax-java/gax test -Dtest="BatcherImplTest#testThrottlingBlocking" -Dfmt.skip
    

    It all succeeded, unfortunately. I think the sequential execution nature does not work to reproduce the issue.

    Run the tests concurrently (It worked)

    I created a project to run the test concurrently.

    https://github.com/suztomo/gax-batcher-impl-test-reproducer/blob/main/README.md

    I'm now able to reproduce the problem.

    Understand the test

    Next step: read the test code.

    • Batcher: "Represents a batching context where individual elements will be accumulated and flushed in a large batch request at some point in the future. The buffered elements can be flushed manually or when triggered by an internal threshold. This is intended to be used for high throughput scenarios at the cost of latency. Batcher instances are not thread safe. To use across different threads, create a new Batcher instance per thread."
      new BatcherImpl<>(
                SQUARER_BATCHING_DESC_V2,
                callLabeledIntSquarer,
                labeledIntList,
                settings,
                EXECUTOR,
                flowController,
                callContext)
      
      
    • FlowController: "Provides flow control capability."
        FlowController flowController =
            new FlowController(
                FlowControlSettings.newBuilder()
                    .setLimitExceededBehavior(LimitExceededBehavior.Block)
                    .setMaxOutstandingElementCount(1L)
                    .build());
      

    image

    Next step: Where should callContext.withOption(key, value) be supposed to be called?

    It seems within BatcherImpl.sendOutstanding()

    ApiCallContext callContextWithOption = null;
    if (callContext != null) {
    callContextWithOption =
    callContext.withOption(THROTTLED_TIME_KEY, accumulatedBatch.totalThrottledTimeMs);
    }
    . BatcherImpl$PushCurrentBatchRunnable's run() method calls sendOutstanding(). PushCurrentBatchRunnable is submitted in BatcherImpl's constructor:

          scheduledFuture =
              executor.scheduleWithFixedDelay(runnable, delay, delay, TimeUnit.MILLISECONDS);
    

    With debug logging, logback, and jul-to-slf4j bridge (776eb1e), I got the following logging output (a normal successful invocation):

    09:51:52.154 [main] INFO  c.g.api.gax.batching.BatcherImplTest -- Starting testThrottlingBlocking
    09:51:53.089 [pool-2-thread-1] DEBUG c.g.api.gax.batching.BatcherImplTest -- calling batcher.add(1)
    09:51:53.113 [main] DEBUG c.g.api.gax.batching.BatcherImplTest -- Caught TimeoutException as expected
    09:51:53.113 [main] DEBUG c.g.api.gax.batching.BatcherImplTest -- calling future.get(3, TimeUnit.SECONDS)
    09:51:53.152 [pool-2-thread-2] DEBUG c.g.api.gax.batching.BatcherImplTest -- calling flowController.release()
    09:51:53.153 [pool-2-thread-2] DEBUG c.g.api.gax.batching.BatcherImplTest -- finished flowController.release()
    09:51:53.175 [pool-2-thread-1] DEBUG c.g.api.gax.batching.BatcherImplTest -- batcher.add(1) finished
    09:51:53.176 [main] DEBUG c.g.api.gax.batching.BatcherImplTest -- finished future.get(3, TimeUnit.SECONDS)
    09:51:53.186 [pool-1-thread-1] DEBUG c.g.api.gax.batching.BatcherImpl -- callContext.withOption called
    09:51:53.193 [main] DEBUG c.g.api.gax.batching.BatcherImplTest -- Mockito.verify(callContext, ...) succeeded
    09:51:53.215 [main] INFO  c.g.api.gax.batching.BatcherImplTest -- Finishing testThrottlingBlocking
    

    There are three threads: main, pool-1-thread-1, pool-2-thread-1, and pool-2-thread-2.

    BatcherImpl$PushCurrentBatchRunnable uses pool-1-thread-1. While it tries to check the need of callContext.withOption every 1 millisecond, there's no guarantee that the event on the pool-1-thread-1 thread runs before main thread's Mockito.verify(callContext, Mockito.timeout.. operation.

    Simply increasing the timeout value in Mockito.verify(callContext, Mockito.timeout...) from 100 to 1000 resulted in another error:

    [ERROR] Failures: 
    [ERROR]   BatcherImplTest.testThrottlingBlocking:903 expected to be at least: 50
    but was                : 48
    

    What does that callContext.withOptions(total_throttled_time, 48) mean?

    The Javadoc tells it's "for tracking batch total throttled time".

    callContext.withOption(THROTTLED_TIME_KEY, accumulatedBatch.totalThrottledTimeMs);

    It's how much FlowController blocked.

    Screenshot 2023-09-15 at 10 33 33 AM

    While it got 10 milliseconds lag, there's no guarantee that batcher.add is called before Thread.sleep(throttledTime).

    image

    02:49:56.358 [gax-test06][main] INFO  c.g.api.gax.batching.BatcherImplTest -- Starting testThrottlingBlocking
    02:49:59.283 [gax-test06][pool-3-thread-1] DEBUG c.g.api.gax.batching.BatcherImplTest -- calling batcher.add(1)
    02:49:59.287 [gax-test06][pool-3-thread-2] DEBUG c.g.api.gax.batching.BatcherImplTest -- start sleeping 50 ms
    02:49:59.293 [gax-test06][pool-3-thread-1] DEBUG c.g.api.gax.batching.BatcherImpl -- stopwatch started
    02:49:59.297 [gax-test06][main] DEBUG c.g.api.gax.batching.BatcherImplTest -- Caught TimeoutException as expected
    02:49:59.297 [gax-test06][main] DEBUG c.g.api.gax.batching.BatcherImplTest -- calling future.get(3, TimeUnit.SECONDS)
    02:49:59.337 [gax-test06][pool-3-thread-2] DEBUG c.g.api.gax.batching.BatcherImplTest -- Sleep finished. Calling flowController.release()
    02:49:59.338 [gax-test06][pool-3-thread-2] DEBUG c.g.api.gax.batching.BatcherImplTest -- finished flowController.release()
    02:49:59.340 [gax-test06][pool-3-thread-1] DEBUG c.g.api.gax.batching.BatcherImpl -- stopwatch.elapsed (throttledTimeMs): 48
    02:49:59.355 [gax-test06][pool-3-thread-1] DEBUG c.g.api.gax.batching.BatcherImpl -- add() called with throttledTimeMs= 48 (now totalThrottledTimeMs: 48)
    02:49:59.364 [gax-test06][pool-2-thread-1] DEBUG c.g.api.gax.batching.BatcherImpl -- callContext.withOption called
    02:49:59.371 [gax-test06][pool-3-thread-1] DEBUG c.g.api.gax.batching.BatcherImplTest -- batcher.add(1) finished
    02:49:59.376 [gax-test06][main] DEBUG c.g.api.gax.batching.BatcherImplTest -- finished future.get(3, TimeUnit.SECONDS)
    02:49:59.391 [gax-test06][main] DEBUG c.g.api.gax.batching.BatcherImplTest -- Mockito.verify(callContext, ...) succeeded
    [ERROR] com.google.api.gax.batching.BatcherImplTest.testThrottlingBlocking_gax_test06 -- Time elapsed: 5.138 s <<< FAILURE!
    expected to be at least: 50
    but was                : 48
    	at com.google.api.gax.batching.BatcherImplTest.testThrottlingBlocking_gax_test06(BatcherImplTest.java:926)
    

    When the sleeping(50 ms) before flowController.release starts earlier than
    BatcherImpl's stopwatch start time, the recorded throttledTime may
    be shorter than throttledTime.

    Normal case:

    1. throttle start (stopwatch start)
    2. sleep(50 ms) starts
    3. sleep(50 ms) finishes and calls flowController.release
    4. stopwatch finishes, recording more than or equal to 50 ms.

    Rare case:

    1. sleep(50 ms) starts
    2. throttle start (stopwatch start)
    3. sleep(50 ms) finishes and calls flowController.release
    4. stopwatch finishes, recording less than 50 ms. assertThat(value.getValue()).isAtLeast(throttledTime) fails.

    To avoid such bad operation order, we can make the "sleep(50 ms)" start only after the thread of "throttle start (stopwatch start)" becomes WAITING state:

          List<Thread> batcherAddThreadHolder = Collections.synchronizedList(new ArrayList<>());
          Future future =
              executor.submit(
                  new Runnable() {
                    Thread batcherAddThread;
                    @Override
                    public void run() {
                      batcherAddThreadHolder.add(Thread.currentThread());
                      logger.fine("calling batcher.add(1)");
                      batcher.add(1);
                      logger.fine("batcher.add(1) finished");
                    }
                  });
          do {
            // Wait until batcher.add blocks and starts the stopwatch.
            Thread.sleep(10);
          } while (batcherAddThreadHolder.isEmpty() || batcherAddThreadHolder.get(0).getState() != Thread.State.WAITING);
    
  27. self-assigned this
    on Sep 13, 2023
  28. added a commit that references this issue on Sep 18, 2023
    8fb0bf3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

priority: p3Desirable enhancement or fix. May not be included in next release.type: bugError or flaw in code with unintended results or allowing sub-optimal usage patterns.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions