[fix][broker] Execute per-topic entry filters with the same classloader#19364
[fix][broker] Execute per-topic entry filters with the same classloader#19364nicoloboschi merged 3 commits intoapache:masterfrom
Conversation
lhotari
left a comment
There was a problem hiding this comment.
Good work @nicoloboschi . I have a comment about EntryFilterWithClassLoader.
| } | ||
| } | ||
|
|
||
| public List<EntryFilterWithClassLoader> loadEntryFiltersForPolicy(EntryFilters policy) |
There was a problem hiding this comment.
In the original implementation, EntryFilterWithClassLoader is unnecessarily used as a top level concept. I think that this is a bad approach. It's sufficient to use EntryFilter.
Please refactor the solution to hide EntryFilterWithClassLoader. It's possible that it could be removed completely.
There was a problem hiding this comment.
Unfortunately we need to keep a reference to the NarClassLoader, this is a pattern we have in other plugins that can be loaded from a Nar file
| EntryFilterWithClassLoader mockFilterReject = mock(EntryFilterWithClassLoader.class); | ||
| when(mockFilterReject.filterEntry(any(Entry.class), any(FilterContext.class))).thenReturn( | ||
| EntryFilter.FilterResult.REJECT); | ||
| EntryFilterWithClassLoader mockFilterAccept = mock(EntryFilterWithClassLoader.class); | ||
| when(mockFilterAccept.filterEntry(any(Entry.class), any(FilterContext.class))).thenReturn( | ||
| EntryFilter.FilterResult.ACCEPT); | ||
| if (overrideBrokerEntryFilters) { | ||
| setMockFilterToTopic((PersistentTopic) pulsar.getBrokerService() | ||
| .getTopicReference(topic).get(), List.of(mockFilterReject, mockFilterAccept)); | ||
| } else { | ||
| setMockFilterToTopic((PersistentTopic) pulsar.getBrokerService() | ||
| .getTopicReference(topic).get(), List.of()); | ||
| setMockBrokerFilter(List.of(mockFilterReject, mockFilterAccept)); | ||
| } |
There was a problem hiding this comment.
I think that we need to get rid of this type of mocking. It's not that this PR has introduced this approach.
It would be useful it the EntryFilterProvider could be overridden in tests. It becomes easier if EntryFilterProvider is a simple interface and the existing EntryFilterProvider is renamed to EntryFilterProviderImpl or DefaultEntryFilterProvider.
Instead of injecting fields and doing other hacks, filters could simply be tested by passing an EntryFilterProvider in tests.
One clarification to this: this applies to all entry filters in the master branch. The master branch has conflicting changes, but it will also load per-broker filters using the same logic as per-topic filters. The filters just won't get used at all unless allowOverrideEntryFilters=true. The broker config level value gets set to the policies here: Since #19361 applies to all entry filter usage, I'd suggest renaming the title of this PR. |
| private void setMockFilterToTopic(PersistentTopic topicRef, List<EntryFilterWithClassLoader> mockFilter) throws NoSuchFieldException, IllegalAccessException { | ||
| Field field = topicRef.getClass().getSuperclass().getDeclaredField("entryFilters"); | ||
| field.setAccessible(true); | ||
| field.set(topicRef, mockFilter); | ||
| } | ||
|
|
||
| private void setMockBrokerFilter(List<EntryFilterWithClassLoader> mockFilter) throws NoSuchFieldException, IllegalAccessException { | ||
| Field field2 = pulsar.getBrokerService().getEntryFilterProvider() | ||
| .getClass().getDeclaredField("brokerEntryFilters"); | ||
| field2.setAccessible(true); | ||
| field2.set(pulsar.getBrokerService().getEntryFilterProvider(), mockFilter); | ||
| } |
There was a problem hiding this comment.
I hope that there's a way to get rid of these as explained in my other comment.
eolivelli
left a comment
There was a problem hiding this comment.
LGTM
@hangc0276 please take a look
| } | ||
| } | ||
|
|
||
| public List<EntryFilterWithClassLoader> loadEntryFiltersForPolicy(EntryFilters policy) |
There was a problem hiding this comment.
Unfortunately we need to keep a reference to the NarClassLoader, this is a pattern we have in other plugins that can be loaded from a Nar file
@eolivelli That can be achieved without making |
|
@lhotari @eolivelli I've updated the pull:
For the test part, I'm planning to improve the mocking part after #19376 gets merged, does it make sense to you @lhotari ? |
632b474 to
b1ed5f8
Compare
@nicoloboschi sounds good! |
|
/pulsarbot rerun-failure-checks |
eolivelli
left a comment
There was a problem hiding this comment.
Nice work
I left some additional comments
| }); | ||
| } catch (PulsarServerException e) { | ||
| log.warn("Failed to create topic {}-{}", topic, e.getMessage()); | ||
| } catch (Throwable e) { |
There was a problem hiding this comment.
Catching Throwable is usually a code smell (and you should always deal with InterruptedException unfortunately.
Maybe we can move to RuntimeException here
|
|
||
| this.updateResourceGroupLimiter(Optional.of(data)); | ||
| final Optional<Policies> optionalData = Optional.of(data); | ||
| this.updateResourceGroupLimiter(optionalData); |
There was a problem hiding this comment.
this change seems unrelated
| builder.put(filterName, filter); | ||
| } | ||
| log.info("Successfully loaded entry filter for name `{}`", filterName); | ||
| final EntryFilter filterWithClassLoader = load(metaData); |
There was a problem hiding this comment.
nit: rename to "entryFilter"
| } | ||
|
|
||
| private static String classLoaderKey(Path archivePath) { | ||
| return archivePath.toFile().getAbsolutePath(); |
There was a problem hiding this comment.
is archivePath.toFile().getAbsolutePath() potentially performing some IO operation ? (metadata access?)
probably archivePath.toString() is enough in this context
|
|
||
| @Slf4j | ||
| @ToString | ||
| public class EntryFilterWithClassLoader implements EntryFilter { |
There was a problem hiding this comment.
do we still need this class ?
There was a problem hiding this comment.
yes, it's the implementation of EntryFilter
| @VisibleForTesting | ||
| protected Map<String, EntryFilterMetaData> definitions; | ||
| @VisibleForTesting | ||
| protected Map<String, NarClassLoader> cachedClassLoaders; |
There was a problem hiding this comment.
is it required that we refer to NarClassLoader here ? can it be simply a "ClassLoader" ?
There was a problem hiding this comment.
no because we need to get the ServiceDefinition file from the classloader, so a NarClassLoader is required
| meta.setDefinition(def); | ||
| meta.setArchivePath(Path.of(name)); | ||
| definitions.put(name, meta); | ||
| final NarClassLoader ncl = mock(NarClassLoader.class); |
There was a problem hiding this comment.
if cachedClassLoaders is not required to old " NarClassLoader" entries that maybe this code can be simplified a lot.
and you can create a new URLClassloader with Thread.currentThread().getContextClassLoader() as parent
and so we continue to remove mocks
There was a problem hiding this comment.
see my previous comment
600b4a8 to
3b6153c
Compare
### Modifications - Upgrade the Pulsar dependency to 3.0.0.1-SNAPSHOT - Use `List<EntryFilter>` from apache/pulsar#19364 as the filters - Remove the removed APIs in `AuthorizationService` - Use `TopicType` instead of String.
### Modifications - Upgrade the Pulsar dependency to 3.0.0.1-SNAPSHOT - Use `List<EntryFilter>` from apache/pulsar#19364 as the filters - Remove the removed APIs in `AuthorizationService` - Use `TopicType` instead of String.
### Modifications - Upgrade the Pulsar dependency to 3.0.0.1-SNAPSHOT - Use `List<EntryFilter>` from apache/pulsar#19364 as the filters - Remove the removed APIs in `AuthorizationService` - Use `TopicType` instead of String. - Fix authenticator failure caused by apache/pulsar#19295 Co-authored-by: Demogorgon314 <kwang@streamnative.io>
Fixes #19361
Motivation
Currently the entry filters per-topic/per-namespace are recreated from scratch (search in fs, unzip nar, classloader creation) every time a topic with entry filters enabled is created/updated.
This leads to multiple issues:
NarClassLoaderinstance that has a relevant memory footprint impactModifications
EntryFilterProvidera stateful class. In the constructor all the entry filter definitions and NarClassLoader are loaded, together with the broker entry filtersOne different thing from before is that now the same entry filter class is loaded once. This means if a entry filter relies on
staticvariables, those point to the same memory address for each EntryFilter invocation, even for different topics.I think it's acceptable because:
Other things in the pull request:
AbstractTopicto theinitializemethodallowOverrideEntryFiltersis falseDocumentation
docdoc-requireddoc-not-neededdoc-completeMatching PR in forked repository