This is an automated email from the ASF dual-hosted git repository.
hubcio pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git
The following commit(s) were added to refs/heads/master by this push:
new 46bb40a9a fix(sdk): delete_offset(None) for a standalone consumer
should delete current_partition_id offset (#3882)
46bb40a9a is described below
commit 46bb40a9a55024470d827b02a216919fb5ece34c
Author: haubur <[email protected]>
AuthorDate: Fri Aug 14 21:47:52 2026 +0200
fix(sdk): delete_offset(None) for a standalone consumer should delete
current_partition_id offset (#3882)
Co-authored-by: Hubert Gruszecki <[email protected]>
---
core/integration/tests/sdk/consumer_offset.rs | 139 ++++++++++++++++++++++++++
core/integration/tests/sdk/mod.rs | 1 +
core/sdk/src/clients/consumer.rs | 7 +-
3 files changed, 146 insertions(+), 1 deletion(-)
diff --git a/core/integration/tests/sdk/consumer_offset.rs
b/core/integration/tests/sdk/consumer_offset.rs
new file mode 100644
index 000000000..3bf715327
--- /dev/null
+++ b/core/integration/tests/sdk/consumer_offset.rs
@@ -0,0 +1,139 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+
+use std::str::FromStr;
+
+use futures::StreamExt;
+use iggy::prelude::*;
+use integration::iggy_harness;
+use tokio::time::{Duration, timeout};
+
+const STREAM_NAME: &str = "delete-offset-stream";
+const TOPIC_NAME: &str = "delete-offset-topic";
+const CONSUMER_NAME: &str = "delete-offset-consumer";
+const ASSIGNED_PARTITION_ID: u32 = 3;
+const POLL_TIMEOUT: Duration = Duration::from_secs(10);
+
+#[iggy_harness]
+async fn standalone_consumer_deletes_partition_zero_on_none_in_delete_offset(
+ harness: &TestHarness,
+) {
+ let client = harness
+ .root_client()
+ .await
+ .expect("Failed to get root client");
+ let stream_id = Identifier::named(STREAM_NAME).unwrap();
+ let topic_id = Identifier::named(TOPIC_NAME).unwrap();
+
+ client.create_stream(STREAM_NAME).await.unwrap();
+ client
+ .create_topic(
+ &stream_id,
+ TOPIC_NAME,
+ 4,
+ CompressionAlgorithm::default(),
+ None,
+ IggyExpiry::NeverExpire,
+ MaxTopicSize::ServerDefault,
+ )
+ .await
+ .unwrap();
+
+ // send messages to all partitions
+ for partition_id in [0, ASSIGNED_PARTITION_ID] {
+ let mut messages = vec![
+ IggyMessage::from_str("message_1").unwrap(),
+ IggyMessage::from_str("message_2").unwrap(),
+ IggyMessage::from_str("message_3").unwrap(),
+ ];
+ client
+ .send_messages(
+ &stream_id,
+ &topic_id,
+ &Partitioning::partition_id(partition_id),
+ &mut messages,
+ )
+ .await
+ .unwrap();
+ }
+
+ // get consumer assigned to last partition
+ let mut consumer = client
+ .consumer(
+ CONSUMER_NAME,
+ STREAM_NAME,
+ TOPIC_NAME,
+ ASSIGNED_PARTITION_ID,
+ )
+ .unwrap()
+ .auto_commit(AutoCommit::Disabled)
+ .batch_length(1)
+ .build();
+ consumer.init().await.unwrap();
+
+ timeout(POLL_TIMEOUT, consumer.next())
+ .await
+ .expect("Consumer should receive a message before timeout")
+ .expect("Consumer stream should remain open")
+ .expect("Consumer should poll from the assigned partition");
+ assert_eq!(consumer.partition_id(), ASSIGNED_PARTITION_ID);
+
+ // set offsets for assigned partition and partition 0 manually
+ consumer
+ .store_offset(2, Some(ASSIGNED_PARTITION_ID))
+ .await
+ .unwrap();
+ consumer.store_offset(1, Some(0)).await.unwrap();
+
+ // Check that the offset was stored on the server as intended.
+ let mut stored_offset =
+ fetch_stored_offset(&client, &stream_id, &topic_id,
ASSIGNED_PARTITION_ID).await;
+ assert_eq!(stored_offset, Some(2));
+
+ let mut stored_offset_p0 = fetch_stored_offset(&client, &stream_id,
&topic_id, 0).await;
+ assert_eq!(stored_offset_p0, Some(1));
+
+ // assigned partition was untouched
+ stored_offset =
+ fetch_stored_offset(&client, &stream_id, &topic_id,
ASSIGNED_PARTITION_ID).await;
+ assert_eq!(stored_offset, Some(2));
+
+ // delete with none and check both again
+ consumer.delete_offset(None).await.unwrap();
+ // this fails, since partition 0 got deleted
+ stored_offset_p0 = fetch_stored_offset(&client, &stream_id, &topic_id,
0).await;
+ assert_eq!(stored_offset_p0, Some(1));
+}
+
+/// Read the offset the server has stored for the consumer.
+async fn fetch_stored_offset(
+ client: &IggyClient,
+ stream_id: &Identifier,
+ topic_id: &Identifier,
+ partition_id: u32,
+) -> Option<u64> {
+ client
+ .get_consumer_offset(
+ &Consumer::new(Identifier::named(CONSUMER_NAME).unwrap()),
+ stream_id,
+ topic_id,
+ Some(partition_id),
+ )
+ .await
+ .unwrap()
+ .map(|offset| offset.stored_offset)
+}
diff --git a/core/integration/tests/sdk/mod.rs
b/core/integration/tests/sdk/mod.rs
index a59335337..cdf8dfb4e 100644
--- a/core/integration/tests/sdk/mod.rs
+++ b/core/integration/tests/sdk/mod.rs
@@ -17,6 +17,7 @@
mod consumer_group;
mod consumer_group_membership;
+mod consumer_offset;
mod hello_world;
mod http_refresh;
mod producer;
diff --git a/core/sdk/src/clients/consumer.rs b/core/sdk/src/clients/consumer.rs
index 5400d7c85..a935ca24d 100644
--- a/core/sdk/src/clients/consumer.rs
+++ b/core/sdk/src/clients/consumer.rs
@@ -280,7 +280,12 @@ impl IggyConsumer {
}
/// Deletes the consumer offset on the server either for the current
partition or the provided partition ID.
- pub async fn delete_offset(&self, partition_id: Option<u32>) -> Result<(),
IggyError> {
+ pub async fn delete_offset(&self, mut partition_id: Option<u32>) ->
Result<(), IggyError> {
+ // `None` is only resolved server-side for consumer groups. For a
standalone consumer
+ // explicitly assign the current partition_id.
+ if partition_id.is_none() && !self.is_consumer_group {
+ partition_id = Some(self.current_partition_id.load(ORDERING));
+ }
let client = self.client.read().await;
client
.delete_consumer_offset(