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
Add OffsetsForTimes API to Consumer #235
Changes from 5 commits
8ff96ae
112b286
62a0421
a4158e5
1089287
b4f54dd
8b4c360
34c1ca0
f366588
938aca2
ba83138
52bd0a4
9293799
a517357
c930399
14dbe3a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -148,6 +148,7 @@ static LibRdKafka() | |
_poll_set_consumer = NativeMethods.rd_kafka_poll_set_consumer; | ||
_query_watermark_offsets = NativeMethods.rd_kafka_query_watermark_offsets; | ||
_get_watermark_offsets = NativeMethods.rd_kafka_get_watermark_offsets; | ||
_offsets_for_times = NativeMethods.rd_kafka_offsets_for_times; | ||
_mem_free = NativeMethods.rd_kafka_mem_free; | ||
_subscribe = NativeMethods.rd_kafka_subscribe; | ||
_unsubscribe = NativeMethods.rd_kafka_unsubscribe; | ||
|
@@ -391,6 +392,11 @@ internal static SafeTopicHandle topic_new(IntPtr rk, string topic, IntPtr conf) | |
out long low, out long high) | ||
=> _get_watermark_offsets(rk, topic, partition, out low, out high); | ||
|
||
private delegate ErrorCode OffsetsForTimes(IntPtr rk, IntPtr topics, IntPtr timeout_ms); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. offsets, not topics (by reading rdkafka.h, may change this comment later finaly) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
private static OffsetsForTimes _offsets_for_times; | ||
internal static ErrorCode offsets_for_times(IntPtr rk, IntPtr topics, IntPtr timeout_ms) | ||
=> _offsets_for_times(rk, topics, timeout_ms); | ||
|
||
private static Action<IntPtr, IntPtr> _mem_free; | ||
internal static void mem_free(IntPtr rk, IntPtr ptr) | ||
=> _mem_free(rk, ptr); | ||
|
@@ -712,6 +718,11 @@ private class NativeMethods | |
[MarshalAs(UnmanagedType.LPStr)] string topic, | ||
int partition, out long low, out long high); | ||
|
||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] | ||
internal static extern ErrorCode rd_kafka_offsets_for_times(IntPtr rk, | ||
/* rd_kafka_topic_partition_list_t * */ IntPtr topics, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. offsets, not topics There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
IntPtr timeout_ms); | ||
|
||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] | ||
internal static extern void rd_kafka_mem_free(IntPtr rk, IntPtr ptr); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -258,6 +258,22 @@ internal WatermarkOffsets GetWatermarkOffsets(string topic, int partition) | |
return new WatermarkOffsets(low, high); | ||
} | ||
|
||
internal IEnumerable<TopicPartitionOffset> OffsetsForTimes(IEnumerable<TopicPartitionTimestamp> timestampsToSearch, int millisecondsTimeout) | ||
{ | ||
IEnumerable<TopicPartitionOffset> offsets = timestampsToSearch | ||
.Select(t => new TopicPartitionOffset(t.TopicPartition, t.Timestamp.UnixTimestampMs)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems so weird with the TopicPartitionOffset taking a long timestamp (though required by librdkafka) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a comment with explanation |
||
.ToList(); | ||
IntPtr cOffsets = GetCTopicPartitionList(offsets); | ||
ErrorCode err = LibRdKafka.offsets_for_times(handle, cOffsets, (IntPtr)millisecondsTimeout); | ||
if (err != ErrorCode.NoError) | ||
{ | ||
throw new KafkaException(err); | ||
} | ||
|
||
var list = GetTopicPartitionOffsetErrorList(cOffsets); | ||
return list.Select(t => t.TopicPartitionOffset).ToList(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can have error by partition - the error returned by offsets_for_times i a general error (broker not available), but each partition may return an error. In this case, the offset will still be at the timestamp. You can reproduce easily by querying to an inexistant partition. Two choices: either we return a list of TopicPartitionOffsetError (and user will have to map it before assign), either we throw exception if any member has error. I prefer first solution, eventualy with an implcit cast from TopicPartitionErrorOffset to topicPartitionOffset which would throw if an error is present There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure that I can repro the partition error when querying non-existent partition. In my case I've got a general error. But I've made checks for errors for every partition, and in case of any error Thoughts? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
yes, I agree (as noted elsewhere).
This is an interesting (good) idea. We could do implicit or explicit casting or have a It's intuitive to me that a cast would throw if there was a problem, so I like the idea of using a cast for conversion (though it could be argued the semantics aren't perfectly clear). We want an explicit cast though, as the operation can fail. See: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/types/casting-and-type-conversions I think we should include that in this PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Personally, I'm not a big fan of cast operations that can throw. Client can test However, it was easy to add an explicit cast operation and unit test for it, so I did it. Thanks! |
||
} | ||
|
||
internal void Subscribe(IEnumerable<string> topics) | ||
{ | ||
IntPtr list = LibRdKafka.topic_partition_list_new((IntPtr) topics.Count()); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1005,8 +1005,12 @@ public class Producer<TKey, TValue> : ISerializingProducer<TKey, TValue>, IDispo | |
ISerializer<TValue> valueSerializer, | ||
bool manualPoll, bool disableDeliveryReports) | ||
{ | ||
var configWithoutKeySerializerProperties = KeySerializer.Configure(config, true); | ||
var configWithoutValueSerializerProperties = ValueSerializer.Configure(config, false); | ||
var configWithoutKeySerializerProperties = keySerializer != null | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, noticed it this we, it was introduced in #205 but waiting for matt to come back as there are other things to fix on it. This will be done in separate PR I think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's ok if it will be another PR for this. But I need it to be fixed here as I'm going to use this CI build. I'll resolve conflicts if any. |
||
? keySerializer.Configure(config, true) | ||
: Enumerable.Empty<KeyValuePair<string, object>>(); | ||
var configWithoutValueSerializerProperties = valueSerializer != null | ||
? valueSerializer.Configure(config, false) | ||
: Enumerable.Empty<KeyValuePair<string, object>>(); | ||
|
||
var configWithoutSerializerProperties = config.Where(item => | ||
configWithoutKeySerializerProperties.Any(ci => ci.Key == item.Key) && | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
// Copyright 2016-2017 Confluent Inc., 2015-2016 Andreas Heider | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
// | ||
// Derived from: rdkafka-dotnet, licensed under the 2-clause BSD License. | ||
// | ||
// Refer to LICENSE for more information. | ||
|
||
namespace Confluent.Kafka | ||
{ | ||
/// <summary> | ||
/// Represents a Kafka (topic, partition, timestamp) tuple. | ||
/// </summary> | ||
public class TopicPartitionTimestamp | ||
{ | ||
/// <summary> | ||
/// Initializes a new TopicPartitionTimestamp instance. | ||
/// </summary> | ||
/// <param name="tp"> | ||
/// Kafka topic name and partition. | ||
/// </param> | ||
/// <param name="timestamp"> | ||
/// A Kafka timestamp value. | ||
/// </param> | ||
public TopicPartitionTimestamp(TopicPartition tp, Timestamp timestamp) | ||
: this (tp.Topic, tp.Partition, timestamp) | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Initializes a new TopicPartitionTimestamp instance. | ||
/// </summary> | ||
/// <param name="topic"> | ||
/// A Kafka topic name. | ||
/// </param> | ||
/// <param name="partition"> | ||
/// A Kafka partition. | ||
/// </param> | ||
/// <param name="timestamp"> | ||
/// A Kafka timestamp value. | ||
/// </param> | ||
public TopicPartitionTimestamp(string topic, int partition, Timestamp timestamp) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We may modify Timestamp back to the way where it accepted Datetime - if we want to retrieve offset at a particular date, it's kind of heavy with current api (new Timestamp(Timestamp.FromDateTime(...)), or perhaps just add a Timestamp.FromDateTime static method, or an implicit/explicit conversion from datetime to timestamp. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that we want to provide easier conversion from Of the options @treziac notes, I'm definitely against implicit conversion and Between the static method and constructor, I'm currently favoring using a constructor because 1. it's fewer characters. 2. we only ever want to return a I'm definitely open to being convinced back to using a static method if someone wants to present a good argument, but currently think we should change the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For reference, some previous conversation related to DateTime/Timestamps is in #120 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Totally agreed that a constructor much better than the static method. Done in a517357 |
||
{ | ||
Topic = topic; | ||
Partition = partition; | ||
Timestamp = timestamp; | ||
} | ||
|
||
/// <summary> | ||
/// Gets the Kafka topic name. | ||
/// </summary> | ||
public string Topic { get; } | ||
|
||
/// <summary> | ||
/// Gets the Kafka partition. | ||
/// </summary> | ||
public int Partition { get; } | ||
|
||
/// <summary> | ||
/// Gets the Kafka timestamp. | ||
/// </summary> | ||
public Timestamp Timestamp { get; } | ||
|
||
/// <summary> | ||
/// Gets the TopicPartition component of this TopicPartitionTimestamp instance. | ||
/// </summary> | ||
public TopicPartition TopicPartition | ||
=> new TopicPartition(Topic, Partition); | ||
|
||
/// <summary> | ||
/// Tests whether this TopicPartitionTimestamp instance is equal to the specified object. | ||
/// </summary> | ||
/// <param name="obj"> | ||
/// The object to test. | ||
/// </param> | ||
/// <returns> | ||
/// true if obj is a TopicPartitionTimestamp and all properties are equal. false otherwise. | ||
/// </returns> | ||
public override bool Equals(object obj) | ||
{ | ||
if (!(obj is TopicPartitionTimestamp)) | ||
{ | ||
return false; | ||
} | ||
|
||
var tp = (TopicPartitionTimestamp)obj; | ||
return tp.Partition == Partition && tp.Topic == Topic && tp.Timestamp == Timestamp; | ||
} | ||
|
||
/// <summary> | ||
/// Returns a hash code for this TopicPartitionTimestamp. | ||
/// </summary> | ||
/// <returns> | ||
/// An integer that specifies a hash value for this TopicPartitionTimestamp. | ||
/// </returns> | ||
public override int GetHashCode() | ||
// x by prime number is quick and gives decent distribution. | ||
=> (Partition.GetHashCode() * 251 + Topic.GetHashCode()) * 251 + Timestamp.GetHashCode(); | ||
|
||
/// <summary> | ||
/// Tests whether TopicPartitionTimestamp instance a is equal to TopicPartitionTimestamp instance b. | ||
/// </summary> | ||
/// <param name="a"> | ||
/// The first TopicPartitionTimestamp instance to compare. | ||
/// </param> | ||
/// <param name="b"> | ||
/// The second TopicPartitionTimestamp instance to compare. | ||
/// </param> | ||
/// <returns> | ||
/// true if TopicPartitionTimestamp instances a and b are equal. false otherwise. | ||
/// </returns> | ||
public static bool operator ==(TopicPartitionTimestamp a, TopicPartitionTimestamp b) | ||
=> a.Equals(b); | ||
|
||
/// <summary> | ||
/// Tests whether TopicPartitionTimestamp instance a is not equal to TopicPartitionTimestamp instance b. | ||
/// </summary> | ||
/// <param name="a"> | ||
/// The first TopicPartitionTimestamp instance to compare. | ||
/// </param> | ||
/// <param name="b"> | ||
/// The second TopicPartitionTimestamp instance to compare. | ||
/// </param> | ||
/// <returns> | ||
/// true if TopicPartitionTimestamp instances a and b are not equal. false otherwise. | ||
/// </returns> | ||
public static bool operator !=(TopicPartitionTimestamp a, TopicPartitionTimestamp b) | ||
=> !(a == b); | ||
|
||
/// <summary> | ||
/// Returns a string representation of the TopicPartitionTimestamp object. | ||
/// </summary> | ||
/// <returns> | ||
/// A string that represents the TopicPartitionTimestamp object. | ||
/// </returns> | ||
public override string ToString() | ||
=> $"{Topic} [{Partition}] @{Timestamp}"; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove empty line for consistency
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, as you repeat the doc, you may want to use include_docs.xml (matt used it recently, this seems a good idea to use it when doc is duplicated)
So just need a
/// <include file='include_docs.xml' path='API/Member[@name="Consumer_OffsetsForTimes"]/*' />
And add the proper doc in the file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done