Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ConsumingQueueIterator is not thread safe #6141

Closed
bjrke opened this issue Aug 9, 2022 · 4 comments · Fixed by #6306
Closed

ConsumingQueueIterator is not thread safe #6141

bjrke opened this issue Aug 9, 2022 · 4 comments · Fixed by #6306

Comments

@bjrke
Copy link

bjrke commented Aug 9, 2022

The code of ConsumingQueueIterator is not thread safe because it is not using an atomic operation in computeNext

we have this stacktrace once in our whole cluster

java.util.NoSuchElementException: null
    at java.util.AbstractQueue.remove(AbstractQueue.java:117)
    at com.google.common.collect.ConsumingQueueIterator.computeNext(ConsumingQueueIterator.java:44)
    at com.google.common.collect.AbstractIterator.tryToComputeNext(AbstractIterator.java:146)
    at com.google.common.collect.AbstractIterator.hasNext(AbstractIterator.java:141)
    ...

Instead of

  public T computeNext() {
    // TODO(b/192579700): Use a ternary once it no longer confuses our nullness checker.
    if (queue.isEmpty()) {
      return endOfData();
    }
    return queue.remove();
  }

I suggest sth like this but this changes the semantics since queued nulls mess this up... ofc its questionable if it makes sense at all

  protected T computeNext() {
    final var result = queue.poll();
    return result == null ? endOfData() : result;
  }

for me its also unclear why computeNext is public...

@dcm03
Copy link

dcm03 commented Dec 25, 2022

The ConsumingQueueIterator class is an iterator that is designed to consume elements from a queue as they are encountered. The computeNext method is used to compute the next element in the iterator, by removing the element from the queue and returning it.

As you pointed out, the current implementation of the computeNext method is not thread-safe, because it is not using an atomic operation to remove the element from the queue. This means that if multiple threads are accessing the iterator concurrently, it is possible for one thread to remove an element from the queue just before another thread tries to remove the same element, causing the second thread to throw a NoSuchElementException.

To fix this issue, you could modify the computeNext method to use an atomic operation, such as poll, to remove the element from the queue. This would ensure that the element is removed from the queue in a thread-safe manner, preventing the NoSuchElementException from being thrown.

However, it's worth noting that this change would modify the semantics of the ConsumingQueueIterator, as poll does not throw an exception when the queue is empty, unlike remove. This means that the computeNext method would return null when the queue is empty, instead of throwing a NoSuchElementException.

It's also unclear why the computeNext method is public, as it is intended to be called by the iterator itself, rather than by external code. You may want to consider changing the visibility of the computeNext method to protected or private to reflect this.

@cpovirk
Copy link
Member

cpovirk commented Dec 27, 2022

This means that the computeNext method would return null when the queue is empty, instead of throwing a NoSuchElementException.

Thanks, yes, that's the reason. This might be nice to document, since users might reasonably assume thread-safety for a Queue.

It's also unclear why the computeNext method is public, as it is intended to be called by the iterator itself, rather than by external code. You may want to consider changing the visibility of the computeNext method to protected or private to reflect this.

That does sound like an accident. The method needs to be at least protected (since it overrides a protected abstract method), but there's no need for public. It looks like we did the same thing inside symmetricDifference.

@cpovirk cpovirk added type=api-docs Change/add API documentation and removed type=defect Bug, not working as expected labels Dec 27, 2022
@ayushsinghal90
Copy link
Contributor

Raised a cr to contribute. [First contribution to the package 🙂].

copybara-service bot pushed a commit that referenced this issue Jan 21, 2023
…ngQueueIterator.computeNext.

Fixes #6141
Fixes #6305

RELNOTES=n/a
PiperOrigin-RevId: 503643637
copybara-service bot pushed a commit that referenced this issue Jan 23, 2023
related to #6141
addressing comment #6305 (comment)
Fixes #6307

RELNOTES=n/a
PiperOrigin-RevId: 503964969
copybara-service bot pushed a commit that referenced this issue Jan 23, 2023
related to #6141
addressing comment #6305 (comment)
Fixes #6307

RELNOTES=n/a
PiperOrigin-RevId: 504046688
@Grumpy2869
Copy link

The code of ConsumingQueueIterator is not thread safe because it is not using an atomic operation in computeNext

we have this stacktrace once in our whole clus
Duplicate of #ter

java.util.NoSuchElementException: null
    at java.util.AbstractQueue.remove(AbstractQueue.java:117)
    at com.google.common.collect.ConsumingQueueIterator.computeNext(ConsumingQueueIterator.java:44)
    at com.google.common.collect.AbstractIterator.tryToComputeNext(AbstractIterator.java:146)
    at com.google.common.collect.AbstractIterator.hasNext(AbstractIterator.java:141)
    ...

Instead of

  public T computeNext() {
    // TODO(b/192579700): Use a ternary once it no longer confuses our nullness checker.
    if (queue.isEmpty()) {
      return endOfData();
    }
    return queue.remove();
  }

I suggest sth like this but this changes the semantics since queued nulls mess this up... ofc its questionable if it makes sense at all

  protected T computeNext() {
    final var result = queue.poll();
    return result == null ? endOfData() : result;
  }

for me its also unclear why computeNext is public...

#6141 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
6 participants