-
Notifications
You must be signed in to change notification settings - Fork 217
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
Fixed bug leading to semaphore leak when filtering messages #1891
Changes from all commits
ffcc75b
d2a4130
675c06b
67ebd39
9bf86d6
5d0f48b
f8dad25
5db6818
1250931
55226bd
497351f
e0d04c7
76e68ea
9190d4a
888fd7e
87b4d96
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 |
---|---|---|
@@ -1,5 +1,5 @@ | ||
package pl.allegro.tech.hermes.consumers.consumer.profiling; | ||
|
||
public enum ConsumerRun { | ||
EMPTY, DELIVERED, DISCARDED, RETRIED | ||
EMPTY, DELIVERED, DISCARDED, RETRIED, FILTERED | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,21 @@ | |
|
||
public interface MessageReceiver { | ||
|
||
/** | ||
* Retrieves the next available message from the queue. | ||
* | ||
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. ❤️ |
||
* <p>Depending on the context, the returned {@link Optional} can contain: | ||
* <ul> | ||
* <li>A {@link Message} that contains a valid message ready to be sent.</li> | ||
* <li>A {@link Message} with the `isFiltered` flag set, indicating that the message | ||
* has been filtered and should be skipped during processing or sending.</li> | ||
* <li>{@code null}, indicating that there are no messages currently available in the queue.</li> | ||
* </ul> | ||
* | ||
* @return an {@link Optional} containing the next {@link Message} if available; | ||
* an {@link Optional} containing a filtered message if it should be skipped; | ||
* or an empty {@link Optional} if there are no messages in the queue. | ||
*/ | ||
Optional<Message> next(); | ||
|
||
default void stop() {} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ | |
import org.junit.jupiter.api.extension.RegisterExtension; | ||
import pl.allegro.tech.hermes.api.BatchSubscriptionPolicy; | ||
import pl.allegro.tech.hermes.api.ContentType; | ||
import pl.allegro.tech.hermes.api.MessageFilterSpecification; | ||
import pl.allegro.tech.hermes.api.Subscription; | ||
import pl.allegro.tech.hermes.api.Topic; | ||
import pl.allegro.tech.hermes.integrationtests.setup.HermesExtension; | ||
|
@@ -23,10 +24,14 @@ | |
import static com.github.tomakehurst.wiremock.client.WireMock.post; | ||
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; | ||
import static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED; | ||
import static com.google.common.collect.ImmutableMap.of; | ||
import static java.util.Arrays.stream; | ||
import static org.awaitility.Awaitility.waitAtMost; | ||
import static pl.allegro.tech.hermes.api.BatchSubscriptionPolicy.Builder.batchSubscriptionPolicy; | ||
import static pl.allegro.tech.hermes.api.TopicWithSchema.topicWithSchema; | ||
import static pl.allegro.tech.hermes.integrationtests.assertions.HermesAssertions.assertThatMetrics; | ||
import static pl.allegro.tech.hermes.test.helper.builder.SubscriptionBuilder.subscription; | ||
import static pl.allegro.tech.hermes.test.helper.builder.SubscriptionBuilder.subscriptionWithRandomName; | ||
import static pl.allegro.tech.hermes.test.helper.builder.TopicBuilder.topicWithRandomName; | ||
|
||
public class BatchDeliveryTest { | ||
|
@@ -39,10 +44,19 @@ public class BatchDeliveryTest { | |
@RegisterExtension | ||
public static final TestSubscribersExtension subscribers = new TestSubscribersExtension(); | ||
|
||
static final AvroUser BOB = new AvroUser("Bob", 50, "blue"); | ||
|
||
static final AvroUser ALICE = new AvroUser("Alice", 20, "magenta"); | ||
|
||
private static final TestMessage[] SMALL_BATCH = TestMessage.simpleMessages(2); | ||
|
||
private static final TestMessage SINGLE_MESSAGE = TestMessage.simple(); | ||
|
||
private static final TestMessage SINGLE_MESSAGE_FILTERED = BOB.asTestMessage(); | ||
|
||
private static final MessageFilterSpecification MESSAGE_NAME_FILTER = | ||
new MessageFilterSpecification(of("type", "jsonpath", "path", ".name", "matcher", "^Bob.*")); | ||
|
||
@Test | ||
public void shouldDeliverMessagesInBatch() { | ||
// given | ||
|
@@ -67,6 +81,43 @@ public void shouldDeliverMessagesInBatch() { | |
expectSingleBatch(subscriber, SMALL_BATCH); | ||
} | ||
|
||
@Test | ||
public void shouldFilterIncomingEventsForBatch() { | ||
// given | ||
TestSubscriber subscriber = subscribers.createSubscriber(); | ||
Topic topic = hermes.initHelper().createTopic(topicWithRandomName().build()); | ||
final Subscription subscription = hermes.initHelper().createSubscription(subscriptionWithRandomName(topic.getName(), subscriber.getEndpoint()) | ||
.withSubscriptionPolicy(buildBatchPolicy() | ||
.withBatchSize(2) | ||
.withBatchTime(3) | ||
.withBatchVolume(1024) | ||
.build()) | ||
.withFilter(MESSAGE_NAME_FILTER) | ||
.build()); | ||
|
||
// when | ||
hermes.api().publishUntilSuccess(topic.getQualifiedName(), BOB.asJson()); | ||
hermes.api().publishUntilSuccess(topic.getQualifiedName(), ALICE.asJson()); | ||
|
||
// then | ||
expectSingleBatch(subscriber, SINGLE_MESSAGE_FILTERED); | ||
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. suggestions:
|
||
waitAtMost(Duration.ofSeconds(10)).untilAsserted(() -> | ||
hermes.api().getConsumersMetrics() | ||
.expectStatus() | ||
.isOk() | ||
.expectBody(String.class) | ||
.value((body) -> assertThatMetrics(body) | ||
.contains("hermes_consumers_subscription_filtered_out_total") | ||
.withLabels( | ||
"group", topic.getName().getGroupName(), | ||
"subscription", subscription.getName(), | ||
"topic", topic.getName().getName() | ||
) | ||
.withValue(1.0) | ||
) | ||
); | ||
} | ||
|
||
@Test | ||
public void shouldDeliverBatchInGivenTimePeriod() { | ||
// given | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -257,6 +257,69 @@ public void shouldReportMetricForFilteredSubscription() { | |
); | ||
} | ||
|
||
@Test | ||
public void shouldNotIncreaseInflightForFilteredSubscription() { | ||
// given | ||
TestSubscriber subscriber = subscribers.createSubscriber(503); | ||
Topic topic = hermes.initHelper().createTopic(topicWithRandomName().build()); | ||
final Subscription subscription = hermes.initHelper().createSubscription( | ||
subscription(topic, "subscription") | ||
.withEndpoint(subscriber.getEndpoint()) | ||
.withSubscriptionPolicy( | ||
subscriptionPolicy() | ||
.withMessageTtl(3600) | ||
.withInflightSize(1) | ||
.build() | ||
) | ||
.withFilter(filterMatchingHeaderByPattern("Trace-Id", "^vte.*")) | ||
.build() | ||
); | ||
TestMessage unfiltered = TestMessage.of("msg", "unfiltered"); | ||
TestMessage filtered = TestMessage.of("msg", "filtered"); | ||
|
||
// when | ||
hermes.api().publishUntilSuccess(topic.getQualifiedName(), filtered.body(), header("Trace-Id", "otherTraceId")); | ||
hermes.api().publishUntilSuccess(topic.getQualifiedName(), filtered.body(), header("Trace-Id", "otherTraceId")); | ||
|
||
// then | ||
waitAtMost(Duration.ofSeconds(10)).untilAsserted(() -> | ||
hermes.api().getConsumersMetrics() | ||
.expectStatus() | ||
.isOk() | ||
.expectBody(String.class) | ||
.value((body) -> assertThatMetrics(body) | ||
.contains("hermes_consumers_subscription_inflight") | ||
.withLabels( | ||
"group", topic.getName().getGroupName(), | ||
"subscription", subscription.getName(), | ||
"topic", topic.getName().getName() | ||
) | ||
.withValue(0.0) | ||
) | ||
); | ||
|
||
// when | ||
hermes.api().publishUntilSuccess(topic.getQualifiedName(), unfiltered.body(), header("Trace-Id", "vte12")); | ||
hermes.api().publishUntilSuccess(topic.getQualifiedName(), unfiltered.body(), header("Trace-Id", "vte12")); | ||
|
||
// then | ||
waitAtMost(Duration.ofSeconds(10)).untilAsserted(() -> | ||
hermes.api().getConsumersMetrics() | ||
.expectStatus() | ||
.isOk() | ||
.expectBody(String.class) | ||
.value((body) -> assertThatMetrics(body) | ||
.contains("hermes_consumers_subscription_inflight") | ||
.withLabels( | ||
"group", topic.getName().getGroupName(), | ||
"subscription", subscription.getName(), | ||
"topic", topic.getName().getName() | ||
) | ||
.withValue(1.0) | ||
) | ||
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. why 1.0 instead of 2.0? 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. Because previously we set |
||
); | ||
} | ||
|
||
@Test | ||
public void shouldReportMetricsForSuccessfulBatchDelivery() { | ||
// given | ||
|
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.
we must ensure that the this value is visible to all threads (see the comment on top of this class)
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.
I have checked it and this is always used within a single thread.