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

GH-129: Implement Consumer Seek #173

Closed
wants to merge 8 commits into from

Conversation

garyrussell
Copy link
Contributor

Resolves #129

Review only @mbogoevici @artembilan

TODO: docs, pass callback through adapters.

if (theListener instanceof MessageListener) {
this.listener = (MessageListener<K, V>) theListener;
if (this.theListener instanceof MessageListener) {
this.listener = (MessageListener<K, V>) this.theListener;
this.batchListener = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I see, this code is a part of constructor. In this case initializing object's field with null is unnecessary - java does this. So now it's just extra code, which makes the method longer and less readable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is incorrect - since the field is marked final, the compiler requires it to be explicitly initialized.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, missed final.

@artembilan
Copy link
Member

Must be rebased to the latest master.

@@ -896,6 +921,11 @@ private void commitIfNecessary() {
}
}

@Override
public void seek(String topic, int partition, long offset) {
this.seeks.add(new TopicPartitionInitialOffset(topic, partition, offset));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder why we can't reach the same via just top-level container operation.
Moreover expose it to JMX.
What am I missing?
Thanks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it somehow needs to be directed to the right consumer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

KafkaMessageListenerContainer has 1-1 relationship with the ListenerConsumer. So, I believe, the question is closed here.

The ConcurrentMessageListenerContainer builds a set of containers based on the partitionSubset(containerProperties, i). Together with the Collection<TopicPartition> getAssignedPartitions(), it seems for me we can reach a uniqueness and correctly assign requested seek to the appropriate KafkaMessageListenerContainer.
No ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My concern is that the topics/partitions can move when the broker does a rebalance of the assignment - I want to be sure that the seek is done in the context of the current state at the time the listener is called. That way, if the consumer no longer owns that partition the seek operation will fail.

@artembilan
Copy link
Member

The solution looks cool, but I'm curious what drove you away of the simple seek() on the AbstractMessageListenerContainer directly.

With good valuable argument I can merge it as is or let you go ahead with Docs 😄
Thanks

}
Assert.state(!this.isBatchListener || !this.isRecordAck, "Cannot use AckMode.RECORD with a batch listener");
if (this.autoCommit && this.theListener instanceof ConsumerSeekAware) {
((ConsumerSeekAware) this.theListener).registerSeekCallback(this);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this one if we registerSeekCallback() any way in the ListenerInvoker.run() ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because with autoCommit = true, the listener is invoked on the consumer thread.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I wasn't able to step in here in debug mode during testing. Maybe we just don't have any test on the matter?..
  2. Having your condition, "the listener is invoked on the consumer thread." and "listeners should store the callback in a {@code ThreadLocal}.", plus the test-case in the EnableKafkaIntegrationTests with exactly a ThreadLocal, I dug further and found this code in the KafkaMessageListenerContainer.doStart():
this.listenerConsumer = new ListenerConsumer(this.listener, this.acknowledgingMessageListener);
        setRunning(true);
        this.listenerConsumerFuture = containerProperties
                .getConsumerTaskExecutor()
                .submitListenable(this.listenerConsumer);

So, we create ListenerConsumer instance in the KafkaMessageListenerContainer.doStart() Thread, but run() it in a different Thread.

My concern that this .registerSeekCallback(this) for the autoCommit = true is done in a wrong Thread. Therefore I won't be able to get a proper ThreadLocal value, when I consume messages.

Am I missing anything?
Thanks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we don't have a test case; will add one - good catch - I had a similar problem with the other case - I called from the ctor instead of the run method in the ListenerInvoker. See the second commit where I moved that code.

@garyrussell
Copy link
Contributor Author

Hmmm - no travis check - weird.

@artembilan
Copy link
Member

no travis check

This branch has conflicts that must be resolved

Can't that be a reason?

* @param <T> the delegate type.
*
* @author Gary Russell
* @since 5.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1.1 😄

@artembilan
Copy link
Member

Fine with me.

Do you still have something else to do here, e.g. Docs?
Otherwise I can merge.

@garyrussell
Copy link
Contributor Author

Will add a docs commit tomorrow.

/**
* Identifies the current position of a TopicPartition.
*
* @author Artem Bilan
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is your class and @since 1.1
😄

Although I still try to understand if we need it...
Maybe simple Map<TopicPartition, Long> could be enough?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe we can just rename TopicPartitionInitialOffset to TopicPartitionOffset...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it to a Map.

@artembilan
Copy link
Member

I think we need What's New note also.

@artembilan
Copy link
Member

Rebase is needed as well.

@artembilan
Copy link
Member

That's all for this round.

@garyrussell
Copy link
Contributor Author

Needs rebase

- support seek from a `@KafkaListener`
- pass the callback through the adapters
- set the callback on the correct thread when not auto-commit
@artembilan
Copy link
Member

I think I'm fine with the fix.

Polling locally for the last review round and probably merge...


/**
* Register the callback to use when seeking at some arbitrary time. When used with a
* {@code ConcurrentMessageListenerContainer} or the same listener instance in multupe
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: multiple

@artembilan
Copy link
Member

Merged as b18ef3e

@artembilan artembilan closed this Aug 30, 2016
@garyrussell garyrussell deleted the GH-129 branch March 30, 2018 21:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants