Skip to content
Merged
10 changes: 5 additions & 5 deletions clients/src/main/java/org/apache/kafka/common/utils/Timer.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,22 @@
* This class also ensures monotonic updates to the timer even if the underlying clock is subject
* to non-monotonic behavior. For example, the remaining time returned by {@link #remainingMs()} is
* guaranteed to decrease monotonically until it hits zero.
*
* <p>
* Note that it is up to the caller to ensure progress of the timer using one of the
* {@link #update()} methods or {@link #sleep(long)}. The timer will cache the current time and
* return it indefinitely until the timer has been updated. This allows the caller to limit
* unnecessary system calls and update the timer only when needed. For example, a timer which is
* waiting a request sent through the {@link org.apache.kafka.clients.NetworkClient} should call
* {@link #update()} following each blocking call to
* {@link org.apache.kafka.clients.NetworkClient#poll(long, long)}.
*
* <p>
* A typical usage might look something like this:
*
* <pre>
* Time time = Time.SYSTEM;
* Timer timer = time.timer(500);
*
* while (!conditionSatisfied() && timer.notExpired) {
* while (!conditionSatisfied() && timer.notExpired()) {
* client.poll(timer.remainingMs(), timer.currentTimeMs());
* timer.update();
* }
Expand Down Expand Up @@ -137,7 +137,7 @@ public void update() {
/**
* Update the cached current time to a specific value. In some contexts, the caller may already
* have an accurate time, so this avoids unnecessary calls to system time.
*
* <p>
* Note that if the updated current time is smaller than the cached time, then the update
* is ignored.
*
Expand All @@ -161,7 +161,7 @@ public long remainingMs() {
/**
* Get the current time in milliseconds. This will return the same cached value until the timer
* has been updated using one of the {@link #update()} methods or {@link #sleep(long)} is used.
*
* <p>

@C0urante C0urante Feb 1, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In the future, would you mind saving nonessential improvements like this for a dedicated PR? They tend to add noise to the diff and makes things harder to review.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Makes sense, but wouldn't it also be slightly strange to create a dedicated PR for such a trivial change? 😄

* Note that the value returned is guaranteed to increase monotonically even if the underlying
* {@link Time} implementation goes backwards. Effectively, the timer will just wait for the
* time to catch up.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1063,7 +1063,6 @@ public void putConnectorConfig(final String connName, final Map<String, String>
// Note that we use the updated connector config despite the fact that we don't have an updated
// snapshot yet. The existing task info should still be accurate.
ConnectorInfo info = new ConnectorInfo(connName, config, configState.tasks(connName),
// validateConnectorConfig have checked the existence of CONNECTOR_CLASS_CONFIG
connectorType(config));
callback.onCompletion(null, new Created<>(!exists, info));
return null;
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import org.apache.kafka.common.IsolationLevel;
import org.apache.kafka.common.KafkaException;
import org.apache.kafka.common.PartitionInfo;
Expand Down Expand Up @@ -342,12 +343,29 @@ public Future<Void> readToEnd() {
return future;
}

public void send(K key, V value) {
send(key, value, null);
/**
* Send a record asynchronously to the configured {@link #topic} without using a producer callback.
* @param key the key for the {@link ProducerRecord}
* @param value the value for the {@link ProducerRecord}
*
* @return the future from the call to {@link Producer#send}. {@link Future#get} can be called on this returned
* future if synchronous behavior is desired.
*/
public Future<RecordMetadata> send(K key, V value) {
return send(key, value, null);
}

public void send(K key, V value, org.apache.kafka.clients.producer.Callback callback) {
producer.orElseThrow(() ->
/**
* Send a record asynchronously to the configured {@link #topic}
* @param key the key for the {@link ProducerRecord}
* @param value the value for the {@link ProducerRecord}
* @param callback the callback to invoke after completion; can be null if no callback is desired
*
* @return the future from the call to {@link Producer#send}. {@link Future#get} can be called on this returned
* future if synchronous behavior is desired.
*/
public Future<RecordMetadata> send(K key, V value, org.apache.kafka.clients.producer.Callback callback) {
return producer.orElseThrow(() ->
new IllegalStateException("This KafkaBasedLog was created in read-only mode and does not support write operations")
).send(new ProducerRecord<>(topic, key, value), callback);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,32 @@ public void testConnectorInfo() {
assertEquals(ConnectorType.SOURCE, info.type());
}

@Test
public void testPauseConnector() {
AbstractHerder herder = mock(AbstractHerder.class, withSettings()
.useConstructor(worker, workerId, kafkaClusterId, statusStore, configStore, noneConnectorClientConfigOverridePolicy)
.defaultAnswer(CALLS_REAL_METHODS));

when(configStore.contains(CONN1)).thenReturn(true);

herder.pauseConnector(CONN1);

verify(configStore).putTargetState(CONN1, TargetState.PAUSED);
}

@Test
public void testResumeConnector() {
AbstractHerder herder = mock(AbstractHerder.class, withSettings()
.useConstructor(worker, workerId, kafkaClusterId, statusStore, configStore, noneConnectorClientConfigOverridePolicy)
.defaultAnswer(CALLS_REAL_METHODS));

when(configStore.contains(CONN1)).thenReturn(true);

herder.resumeConnector(CONN1);

verify(configStore).putTargetState(CONN1, TargetState.STARTED);
}

@Test
public void testConnectorInfoMissingPlugin() {
AbstractHerder herder = mock(AbstractHerder.class, withSettings()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,58 @@ public void testCreateConnector() throws Exception {
PowerMock.verifyAll();
}

@Test
public void testCreateConnectorConfigBackingStoreError() {
EasyMock.expect(member.memberId()).andStubReturn("leader");
EasyMock.expect(member.currentProtocolVersion()).andStubReturn(CONNECT_PROTOCOL_V0);
expectRebalance(1, Collections.emptyList(), Collections.emptyList(), true);
expectConfigRefreshAndSnapshot(SNAPSHOT);

member.wakeup();
PowerMock.expectLastCall();

// mock the actual validation since its asynchronous nature is difficult to test and should
// be covered sufficiently by the unit tests for the AbstractHerder class
Capture<Callback<ConfigInfos>> validateCallback = newCapture();
herder.validateConnectorConfig(EasyMock.eq(CONN2_CONFIG), capture(validateCallback));
PowerMock.expectLastCall().andAnswer(() -> {
validateCallback.getValue().onCompletion(null, CONN2_CONFIG_INFOS);
return null;
});

configBackingStore.putConnectorConfig(CONN2, CONN2_CONFIG);
PowerMock.expectLastCall().andThrow(new ConnectException("Error writing connector configuration to Kafka"));

// verify that the exception from config backing store write is propagated via the callback
putConnectorCallback.onCompletion(EasyMock.anyObject(ConnectException.class), EasyMock.isNull());
PowerMock.expectLastCall();
member.poll(EasyMock.anyInt());
PowerMock.expectLastCall();
// These will occur just before/during the second tick
member.wakeup();
PowerMock.expectLastCall();
member.ensureActive();
PowerMock.expectLastCall();
member.poll(EasyMock.anyInt());
PowerMock.expectLastCall();

PowerMock.replayAll();

herder.putConnectorConfig(CONN2, CONN2_CONFIG, false, putConnectorCallback);
// First tick runs the initial herder request, which issues an asynchronous request for
// connector validation
herder.tick();

// Once that validation is complete, another request is added to the herder request queue
// for actually performing the config write; this tick is for that request
herder.tick();

time.sleep(1000L);
assertStatistics(3, 1, 100, 1000L);

PowerMock.verifyAll();
}

@Test
public void testCreateConnectorFailedValidation() throws Exception {
EasyMock.expect(member.memberId()).andStubReturn("leader");
Expand Down Expand Up @@ -2596,6 +2648,7 @@ public void testPutConnectorConfig() throws Exception {
});
member.wakeup();
PowerMock.expectLastCall();

configBackingStore.putConnectorConfig(CONN1, CONN1_CONFIG_UPDATED);
PowerMock.expectLastCall().andAnswer(() -> {
// Simulate response to writing config + waiting until end of log to be read
Expand Down
Loading