Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,19 @@ public Set<String> getCoprocessors() {
return returnValue;
}

/**
* Get the full class names of all loaded coprocessors. This method returns the complete class
* names including package information, which is useful for precise coprocessor identification and
* comparison.
*/
public Set<String> getCoprocessorClassNames() {
Set<String> returnValue = new TreeSet<>();
for (E e : coprocEnvironments) {
returnValue.add(e.getInstance().getClass().getName());
}
return returnValue;
}

/**
* Load system coprocessors once only. Read the class names from configuration. Called by
* constructor.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ public void onConfigurationChange(Configuration newConf) {
}
refreshSlowLogConfiguration(newConf);
if (
CoprocessorConfigurationUtil.checkConfigurationChange(getConf(), newConf,
CoprocessorConfigurationUtil.checkConfigurationChange(this.cpHost, newConf,
CoprocessorHost.RPC_COPROCESSOR_CONF_KEY)
) {
LOG.info("Update the RPC coprocessor(s) because the configuration has changed");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1080,7 +1080,7 @@ private void finishActiveMasterInitialization() throws IOException, InterruptedE
if (!maintenanceMode) {
startupTaskGroup.addTask("Initializing master coprocessors");
setQuotasObserver(conf);
initializeCoprocessorHost(conf);
this.cpHost = new MasterCoprocessorHost(this, conf);
} else {
// start an in process region server for carrying system regions
maintenanceRegionServer =
Expand Down Expand Up @@ -4417,11 +4417,11 @@ public void onConfigurationChange(Configuration newConf) {
setQuotasObserver(newConf);
// update region server coprocessor if the configuration has changed.
if (
CoprocessorConfigurationUtil.checkConfigurationChange(getConfiguration(), newConf,
CoprocessorConfigurationUtil.checkConfigurationChange(this.cpHost, newConf,
CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY) && !maintenanceMode
) {
LOG.info("Update the master coprocessor(s) because the configuration has changed");
initializeCoprocessorHost(newConf);
this.cpHost = new MasterCoprocessorHost(this, newConf);
}
}

Expand Down Expand Up @@ -4520,11 +4520,6 @@ private void setQuotasObserver(Configuration conf) {
}
}

private void initializeCoprocessorHost(Configuration conf) {
// initialize master side coprocessors before we start handling requests
this.cpHost = new MasterCoprocessorHost(this, conf);
}

@Override
public long flushTable(TableName tableName, List<byte[]> columnFamilies, long nonceGroup,
long nonce) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8829,7 +8829,7 @@ public void onConfigurationChange(Configuration conf) {
this.storeHotnessProtector.update(conf);
// update coprocessorHost if the configuration has changed.
if (
CoprocessorConfigurationUtil.checkConfigurationChange(getReadOnlyConfiguration(), conf,
CoprocessorConfigurationUtil.checkConfigurationChange(this.coprocessorHost, conf,
CoprocessorHost.REGION_COPROCESSOR_CONF_KEY,
CoprocessorHost.USER_REGION_COPROCESSOR_CONF_KEY)
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3475,7 +3475,7 @@ public void onConfigurationChange(Configuration newConf) {

// update region server coprocessor if the configuration has changed.
if (
CoprocessorConfigurationUtil.checkConfigurationChange(getConfiguration(), newConf,
CoprocessorConfigurationUtil.checkConfigurationChange(this.rsHost, newConf,
CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY)
) {
LOG.info("Update region server coprocessors because the configuration has changed");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@
*/
package org.apache.hadoop.hbase.util;

import org.apache.commons.lang3.StringUtils;
import java.util.HashSet;
import java.util.Set;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
import org.apache.yetus.audience.InterfaceAudience;

import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
import org.apache.hbase.thirdparty.com.google.common.base.Strings;

/**
* Helper class for coprocessor host when configuration changes.
Expand All @@ -32,19 +35,65 @@ public final class CoprocessorConfigurationUtil {
private CoprocessorConfigurationUtil() {
}

public static boolean checkConfigurationChange(Configuration oldConfig, Configuration newConfig,
String... configurationKey) {
/**
* Check configuration change by comparing current loaded coprocessors with configuration values.
* This method is useful when the configuration object has been updated but we need to determine
* if coprocessor configuration has actually changed compared to what's currently loaded.
* @param coprocessorHost the coprocessor host to check current loaded coprocessors (can be null)
* @param conf the configuration to check
* @param configurationKey the configuration keys to check
* @return true if configuration has changed, false otherwise
*/
public static boolean checkConfigurationChange(CoprocessorHost<?, ?> coprocessorHost,
Configuration conf, String... configurationKey) {
Preconditions.checkArgument(configurationKey != null, "Configuration Key(s) must be provided");
boolean isConfigurationChange = false;
Preconditions.checkArgument(conf != null, "Configuration must be provided");

if (coprocessorHost == null) {
// If no coprocessor host exists, check if any coprocessors are now configured
return hasCoprocessorsConfigured(conf, configurationKey);
}

// Get currently loaded coprocessor class names
Set<String> currentlyLoaded = coprocessorHost.getCoprocessorClassNames();

// Get coprocessor class names from configuration
Set<String> configuredClasses = new HashSet<>();
for (String key : configurationKey) {
String[] classes = conf.getStrings(key);
if (classes != null) {
for (String className : classes) {
// Handle the className|priority|path format
String[] classNameToken = className.split("\\|");
String actualClassName = classNameToken[0].trim();
if (!Strings.isNullOrEmpty(actualClassName)) {
configuredClasses.add(actualClassName);
}
}
}
}
Comment on lines +70 to +87

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.

this made me rethink about the configuration change detection cannot handle path reload, and even if we can detect changes for classname and priority.

So, can you update a note/comment in the function checkConfigurationChange, that priority and path changes are not supported with the same classname once it's loaded?


// Compare the two sets
return !currentlyLoaded.equals(configuredClasses);
Comment on lines +64 to +90

Copilot AI Dec 11, 2025

Copy link

Choose a reason for hiding this comment

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

The checkConfigurationChange method doesn't check if coprocessors have been disabled via the COPROCESSORS_ENABLED_CONF_KEY when coprocessorHost is not null. This could lead to a scenario where coprocessors remain loaded even after being disabled in the configuration. When coprocessors are enabled and coprocessorHost exists, if a user sets hbase.coprocessor.enabled to false, the method will still compare currently loaded coprocessors with the configured classes and may incorrectly return false (no change) if the class names match, even though coprocessors should be unloaded. Consider checking the enabled flag in the main logic path as well.

Copilot uses AI. Check for mistakes.
}

/**
* Helper method to check if there are any coprocessors configured.
*/
private static boolean hasCoprocessorsConfigured(Configuration conf, String... configurationKey) {
if (
!conf.getBoolean(CoprocessorHost.COPROCESSORS_ENABLED_CONF_KEY,
CoprocessorHost.DEFAULT_COPROCESSORS_ENABLED)

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.

maybe we should move this COPROCESSORS_ENABLED_CONF_KEY check to the top of checkConfigurationChange

) {
return false;
}

for (String key : configurationKey) {
String oldValue = oldConfig.get(key);
String newValue = newConfig.get(key);
// check if the coprocessor key has any difference
if (!StringUtils.equalsIgnoreCase(oldValue, newValue)) {
isConfigurationChange = true;
break;
String[] coprocessors = conf.getStrings(key);
if (coprocessors != null && coprocessors.length > 0) {
return true;
}
}
return isConfigurationChange;
return false;
}
Comment on lines +96 to 104

Copilot AI Dec 11, 2025

Copy link

Choose a reason for hiding this comment

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

The hasCoprocessorsConfigured method doesn't handle empty or whitespace-only strings in the configuration values. When conf.getStrings(key) returns an array containing empty strings, the method returns true even though no actual coprocessors are configured. This is inconsistent with how the main checkConfigurationChange method handles empty strings (using Strings.isNullOrEmpty check on line 69). Consider filtering out empty/whitespace-only strings before checking the array length.

Copilot uses AI. Check for mistakes.
}
Original file line number Diff line number Diff line change
Expand Up @@ -267,18 +267,22 @@ public void testStoreConfigurationOnlineChange() {
@Test
public void testCoprocessorConfigurationOnlineChange() {
assertNull(rs1.getRegionServerCoprocessorHost().findCoprocessor(JMXListener.class.getName()));
conf.set(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY, JMXListener.class.getName());
rs1.getConfigurationManager().notifyAllObservers(conf);
// Update configuration directly to simulate dynamic configuration reload
Configuration rsConf = rs1.getConfiguration();
rsConf.set(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY, JMXListener.class.getName());
rs1.getConfigurationManager().notifyAllObservers(rsConf);
assertNotNull(
rs1.getRegionServerCoprocessorHost().findCoprocessor(JMXListener.class.getName()));
}

@Test
public void testCoprocessorConfigurationOnlineChangeOnMaster() {
assertNull(hMaster.getMasterCoprocessorHost().findCoprocessor(JMXListener.class.getName()));
conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, JMXListener.class.getName());
// Update configuration directly to simulate dynamic configuration reload
Configuration masterConf = hMaster.getConfiguration();
masterConf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, JMXListener.class.getName());
assertFalse(hMaster.isInMaintenanceMode());
hMaster.getConfigurationManager().notifyAllObservers(conf);
hMaster.getConfigurationManager().notifyAllObservers(masterConf);
assertNotNull(hMaster.getMasterCoprocessorHost().findCoprocessor(JMXListener.class.getName()));
}

Expand Down