Skip to content

Commit 42e502c

Browse files
committed
Fix onClose, remove TransactionState monitoring attempt - not needed
1 parent fc114ec commit 42e502c

File tree

6 files changed

+88
-66
lines changed

6 files changed

+88
-66
lines changed

parallel-consumer-core/src/main/java/io/confluent/TransactionState.java

Lines changed: 0 additions & 30 deletions
This file was deleted.

parallel-consumer-core/src/main/java/io/confluent/parallelconsumer/AbstractOffsetCommitter.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ public void commit() {
3131
ConsumerGroupMetadata groupMetadata = consumer.groupMetadata();
3232

3333
commitOffsets(offsetsToSend, groupMetadata);
34-
35-
afterCommit();
3634
}
3735
}
3836

@@ -42,7 +40,4 @@ protected void onOffsetCommitSuccess(final Map<TopicPartition, OffsetAndMetadata
4240

4341
protected abstract void commitOffsets(final Map<TopicPartition, OffsetAndMetadata> offsetsToSend, final ConsumerGroupMetadata groupMetadata);
4442

45-
protected void afterCommit() {
46-
// noop
47-
}
4843
}

parallel-consumer-core/src/main/java/io/confluent/parallelconsumer/ConsumerOffsetCommitter.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,13 @@ public ConsumerOffsetCommitter(final Consumer<K, V> newConsumer, final WorkManag
3535
protected void commitOffsets(final Map<TopicPartition, OffsetAndMetadata> offsetsToSend, final ConsumerGroupMetadata groupMetadata) {
3636
if (isSync) {
3737
consumer.commitSync(offsetsToSend);
38+
commitBarrier.countDown(); // todo use Condition instead?
3839
} else {
3940
consumer.commitAsync(offsetsToSend, (offsets, exception) -> {
40-
if (exception == null) {
41+
if (exception != null) {
4142
log.error("Error committing offsets", exception);
43+
// todo keep work in limbo until async response is received?
4244
}
43-
commitBarrier.countDown();
4445
});
4546
}
4647
}

parallel-consumer-core/src/main/java/io/confluent/parallelconsumer/ParallelConsumerOptions.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public enum ProcessingOrder {
4545
* // TODO we could just auto detect this from the Producers state. However, forcing it to be specified makes the choice more verbose?
4646
*/
4747
@Builder.Default
48-
private final boolean usingTransactionalProducer = false;
48+
private final boolean usingTransactionalProducer = true;
4949

5050
/**
5151
* Don't have more than this many uncommitted messages in process

parallel-consumer-core/src/main/java/io/confluent/parallelconsumer/ProducerManager.java

Lines changed: 61 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.confluent.parallelconsumer;
22

3-
import io.confluent.TransactionState;
43
import lombok.SneakyThrows;
54
import lombok.extern.slf4j.Slf4j;
65
import org.apache.kafka.clients.consumer.Consumer;
@@ -12,10 +11,10 @@
1211
import org.apache.kafka.common.TopicPartition;
1312

1413
import java.lang.reflect.Field;
14+
import java.lang.reflect.Method;
1515
import java.time.Duration;
1616
import java.util.Map;
1717
import java.util.concurrent.Future;
18-
import java.util.concurrent.locks.Lock;
1918
import java.util.concurrent.locks.ReentrantReadWriteLock;
2019

2120
import static io.confluent.csid.utils.StringUtils.msg;
@@ -26,8 +25,6 @@ public class ProducerManager<K, V> extends AbstractOffsetCommitter<K, V> impleme
2625
protected final Producer<K, V> producer;
2726
private final ParallelConsumerOptions options;
2827

29-
protected final TransactionState ts = new TransactionState();
30-
3128
/**
3229
* The {@link KafkaProducer) isn't actually completely thread safe, at least when using it transactionally. We must
3330
* be careful not to send messages to the producer, while we are committing a transaction - "Cannot call send in
@@ -57,7 +54,6 @@ private void initProducer(final Producer<K, V> newProducer) {
5754
log.debug("Initialising producer transaction session...");
5855
producer.initTransactions();
5956
producer.beginTransaction();
60-
ts.setInTransaction();
6157
} catch (KafkaException e) {
6258
log.error("Make sure your producer is setup for transactions - specifically make sure it's {} is set.", ProducerConfig.TRANSACTIONAL_ID_CONFIG, e);
6359
throw e;
@@ -76,16 +72,21 @@ private void initProducer(final Producer<K, V> newProducer) {
7672
*/
7773
@SneakyThrows
7874
private boolean getProducerIsTransactional(final Producer<K, V> newProducer) {
79-
Field coordinatorField = newProducer.getClass().getDeclaredField("transactionManager");
80-
coordinatorField.setAccessible(true);
81-
TransactionManager transactionManager = (TransactionManager) coordinatorField.get(newProducer);
75+
TransactionManager transactionManager = getTransactionManager(newProducer);
8276
if (transactionManager == null) {
8377
return false;
8478
} else {
8579
return transactionManager.isTransactional();
8680
}
8781
}
8882

83+
private TransactionManager getTransactionManager(final Producer<K, V> newProducer) throws NoSuchFieldException, IllegalAccessException {
84+
Field coordinatorField = newProducer.getClass().getDeclaredField("transactionManager");
85+
coordinatorField.setAccessible(true);
86+
TransactionManager transactionManager = (TransactionManager) coordinatorField.get(newProducer);
87+
return transactionManager;
88+
}
89+
8990
/**
9091
* Produce a message back to the broker.
9192
* <p>
@@ -152,15 +153,31 @@ protected void commitOffsets(final Map<TopicPartition, OffsetAndMetadata> offset
152153
ReentrantReadWriteLock.WriteLock writeLock = producerCommitLock.writeLock();
153154
writeLock.lock();
154155
try {
155-
producer.commitTransaction();
156+
boolean retrying = retryCount > 0;
157+
if (retrying) {
158+
if (isTransactionCompleting()) {
159+
// try wait again
160+
producer.commitTransaction();
161+
}
162+
if (isTransactionReady()) {
163+
// tx has completed since we last tried, start a new one
164+
producer.beginTransaction();
165+
}
166+
boolean ready = (lastErrorSavedForRethrow != null) ? !lastErrorSavedForRethrow.getMessage().contains("Invalid transition attempted from state READY to state COMMITTING_TRANSACTION") : true;
167+
if (ready) {
168+
// try again
169+
log.error("Was already ready - tx completed between interrupt and retry");
170+
}
171+
} else {
172+
// happy path
173+
producer.commitTransaction();
174+
producer.beginTransaction();
175+
}
156176
} finally {
157177
writeLock.unlock();
158178
}
159-
160179
}
161180

162-
ts.setNotInTransaction();
163-
164181
onOffsetCommitSuccess(offsetsToSend);
165182

166183
committed = true;
@@ -175,18 +192,42 @@ protected void commitOffsets(final Map<TopicPartition, OffsetAndMetadata> offset
175192
}
176193
}
177194

178-
@Override
179-
protected void afterCommit() {
180-
// begin tx for next cycle
181-
producer.beginTransaction();
182-
ts.setInTransaction();
195+
/**
196+
* TODO talk about alternatives to this brute force approach for retrying committing transactions
197+
*/
198+
@SneakyThrows
199+
private boolean isTransactionCompleting() {
200+
TransactionManager transactionManager = getTransactionManager(producer);
201+
Method method = transactionManager.getClass().getDeclaredMethod("isCompleting");
202+
method.setAccessible(true);
203+
return (boolean) method.invoke(transactionManager);
204+
}
205+
206+
/**
207+
* TODO talk about alternatives to this brute force approach for retrying committing transactions
208+
*/
209+
@SneakyThrows
210+
private boolean isTransactionReady() {
211+
TransactionManager transactionManager = getTransactionManager(producer);
212+
Method method = transactionManager.getClass().getDeclaredMethod("isReady");
213+
method.setAccessible(true);
214+
return (boolean) method.invoke(transactionManager);
183215
}
184216

217+
/**
218+
* Assumes the system is drained at this point, or draining is not desired.
219+
*/
185220
public void close(final Duration timeout) {
186221
log.debug("Closing producer, assuming no more in flight...");
187-
if (options.isUsingTransactionalProducer() && ts.isInTransaction()) {
188-
// close started after tx began, but before work was done, otherwise a tx wouldn't have been started
189-
producer.abortTransaction();
222+
if (options.isUsingTransactionalProducer() && !isTransactionReady()) {
223+
ReentrantReadWriteLock.WriteLock writeLock = producerCommitLock.writeLock();
224+
writeLock.lock();
225+
try {
226+
// close started after tx began, but before work was done, otherwise a tx wouldn't have been started
227+
producer.abortTransaction();
228+
} finally {
229+
writeLock.unlock();
230+
}
190231
}
191232
producer.close(timeout);
192233
}

parallel-consumer-examples/parallel-consumer-example-core/src/test/java/io/confluent/parallelconsumer/examples/core/Bug25AppTest.java

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import io.confluent.parallelconsumer.ParallelConsumerOptions;
88
import io.confluent.parallelconsumer.integrationTests.KafkaTest;
9+
import lombok.AllArgsConstructor;
910
import lombok.RequiredArgsConstructor;
1011
import lombok.SneakyThrows;
1112
import lombok.extern.slf4j.Slf4j;
@@ -28,22 +29,35 @@
2829
@Slf4j
2930
public class Bug25AppTest extends KafkaTest<String, String> {
3031

31-
@SneakyThrows
32+
int DEAFULT_MAX_POLL_RECORDS_CONFIG = 500;
33+
34+
@Test
35+
public void testTransactionalDefaultMaxPoll() {
36+
boolean tx = true;
37+
runTest(tx, DEAFULT_MAX_POLL_RECORDS_CONFIG);
38+
}
39+
40+
@Test
41+
public void testNonTransactionalDefaultMaxPoll() {
42+
boolean tx = false;
43+
runTest(tx, DEAFULT_MAX_POLL_RECORDS_CONFIG);
44+
}
45+
3246
@Test
3347
public void testTransactional() {
3448
boolean tx = true;
35-
runTest(tx);
49+
runTest(tx, 1); // Sometimes causes test to fail (default 500)
3650
}
3751

38-
@SneakyThrows
3952
@Test
4053
public void testNonTransactional() {
4154
boolean tx = false;
42-
runTest(tx);
55+
runTest(tx, 1); // Sometimes causes test to fail (default 500)
4356
}
4457

45-
private void runTest(final boolean tx) {
46-
AppUnderTest coreApp = new AppUnderTest(tx, ParallelConsumerOptions.builder().ordering(KEY).usingTransactionalProducer(tx).build());
58+
@SneakyThrows
59+
private void runTest(boolean tx, int maxPoll) {
60+
AppUnderTest coreApp = new AppUnderTest(tx, ParallelConsumerOptions.builder().ordering(KEY).usingTransactionalProducer(tx).build(), maxPoll);
4761

4862
ensureTopic(coreApp.inputTopic, 1);
4963
ensureTopic(coreApp.outputTopic, 1);
@@ -68,16 +82,17 @@ private void runTest(final boolean tx) {
6882
coreApp.close();
6983
}
7084

71-
@RequiredArgsConstructor
85+
@AllArgsConstructor
7286
class AppUnderTest extends CoreApp {
7387

7488
final boolean tx;
7589
final ParallelConsumerOptions options;
90+
int MAX_POLL_RECORDS_CONFIG;
7691

7792
@Override
7893
Consumer<String, String> getKafkaConsumer() {
7994
Properties props = kcu.props;
80-
props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 1); // Sometimes causes test to fail (default 500)
95+
props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, MAX_POLL_RECORDS_CONFIG);
8196
props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 10000);
8297
return new KafkaConsumer<>(props);
8398
}

0 commit comments

Comments
 (0)