Skip to content

Commit

Permalink
Support concurrent loading of Config for different namespaces (#29)
Browse files Browse the repository at this point in the history
* Support concurrent loading of Config for different namespaces.

* Supports loading the apollo module through spi
  • Loading branch information
zth9 committed Aug 2, 2023
1 parent 265fe49 commit ccc974a
Show file tree
Hide file tree
Showing 8 changed files with 391 additions and 156 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright 2022 Apollo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.ctrip.framework.apollo.internals;

import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigFile;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.google.common.collect.Maps;

import java.util.Map;

/**
* @author tian
*/
public class ConcurrentConfigManager extends DefaultConfigManager {

private final Map<String, Object> m_configLocks = Maps.newConcurrentMap();
private final Map<String, Object> m_configFileLocks = Maps.newConcurrentMap();

@Override
public Config getConfig(String namespace) {
String lockKey = buildLockKey(namespace);
Object lock = m_configLocks.get(lockKey);
if (lock == null) {
synchronized (lockKey) {
lock = m_configLocks.computeIfAbsent(lockKey, k -> new Object());
}
}
return getConfigInLock(lock, namespace);
}

@Override
public ConfigFile getConfigFile(String namespace, ConfigFileFormat configFileFormat) {
String namespaceFileName = buildNamespaceFileName(namespace, configFileFormat);
String lockKey = buildLockKey(namespaceFileName);
Object lock = m_configFileLocks.get(lockKey);
if (lock == null) {
synchronized (lockKey) {
lock = m_configFileLocks.computeIfAbsent(lockKey, k -> new Object());
}
}
return getConfigFileInLock(lock, namespaceFileName, configFileFormat);
}

private String buildLockKey(String factor) {
return (String.format("%s.%s", getClass().getName(), factor)).intern();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,13 @@ public DefaultConfigManager() {

@Override
public Config getConfig(String namespace) {
Config config = m_configs.get(namespace);
return getConfigInLock(this, namespace);
}

protected Config getConfigInLock(Object lock, String namespace) {
Config config = m_configs.get(namespace);
if (config == null) {
synchronized (this) {
synchronized (lock) {
config = m_configs.get(namespace);

if (config == null) {
Expand All @@ -55,17 +58,21 @@ public Config getConfig(String namespace) {
}
}
}

return config;
}

@Override
public ConfigFile getConfigFile(String namespace, ConfigFileFormat configFileFormat) {
String namespaceFileName = String.format("%s.%s", namespace, configFileFormat.getValue());
String namespaceFileName = buildNamespaceFileName(namespace, configFileFormat);
return getConfigFileInLock(this, namespaceFileName, configFileFormat);
}

protected ConfigFile getConfigFileInLock(Object lock, String namespaceFileName,
ConfigFileFormat configFileFormat) {
ConfigFile configFile = m_configFiles.get(namespaceFileName);

if (configFile == null) {
synchronized (this) {
synchronized (lock) {
configFile = m_configFiles.get(namespaceFileName);

if (configFile == null) {
Expand All @@ -79,4 +86,8 @@ public ConfigFile getConfigFile(String namespace, ConfigFileFormat configFileFor

return configFile;
}

protected String buildNamespaceFileName(String namespace, ConfigFileFormat configFileFormat) {
return String.format("%s.%s", namespace, configFileFormat.getValue());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Singleton;
import java.lang.annotation.Annotation;
import java.util.List;

/**
Expand Down Expand Up @@ -95,16 +96,39 @@ public <T> T getInstance(Class<T> clazz, String name) {
private static class ApolloModule extends AbstractModule {
@Override
protected void configure() {
bind(ConfigManager.class).to(DefaultConfigManager.class).in(Singleton.class);
bind(ConfigFactoryManager.class).to(DefaultConfigFactoryManager.class).in(Singleton.class);
bind(ConfigRegistry.class).to(DefaultConfigRegistry.class).in(Singleton.class);
bind(ConfigFactory.class).to(DefaultConfigFactory.class).in(Singleton.class);
bind(ConfigUtil.class).in(Singleton.class);
bind(HttpClient.class).to(DefaultHttpClient.class).in(Singleton.class);
bind(ConfigServiceLocator.class).in(Singleton.class);
bind(RemoteConfigLongPollService.class).in(Singleton.class);
bind(YamlParser.class).in(Singleton.class);
bind(PropertiesFactory.class).to(DefaultPropertiesFactory.class).in(Singleton.class);
bind(ConfigManager.class, DefaultConfigManager.class, Singleton.class);
bind(ConfigFactoryManager.class, DefaultConfigFactoryManager.class, Singleton.class);
bind(ConfigRegistry.class, DefaultConfigRegistry.class, Singleton.class);
bind(ConfigFactory.class, DefaultConfigFactory.class, Singleton.class);
bind(ConfigUtil.class, Singleton.class);
bind(HttpClient.class, DefaultHttpClient.class, Singleton.class);
bind(ConfigServiceLocator.class, Singleton.class);
bind(RemoteConfigLongPollService.class, Singleton.class);
bind(YamlParser.class, Singleton.class);
bind(PropertiesFactory.class, DefaultPropertiesFactory.class, Singleton.class);
}

/**
* bind the type's implementation from spi
* defaultImpl will be bound if the implementation cannot be found
*/
private <S> void bind(Class<S> bindClass, Class<? extends S> defaultImpl, Class<? extends Annotation> in) {
List<Class<?>> classList = ServiceBootstrap.loadClass(bindClass);
Class<? extends S> impl = defaultImpl;
if (!classList.isEmpty()) {
Class<?> spiClass = classList.get(0);

Check warning on line 119 in apollo-client/src/main/java/com/ctrip/framework/apollo/internals/DefaultInjector.java

View check run for this annotation

Codecov / codecov/patch

apollo-client/src/main/java/com/ctrip/framework/apollo/internals/DefaultInjector.java#L119

Added line #L119 was not covered by tests
if (bindClass.isAssignableFrom(spiClass)) {
impl = (Class<? extends S>) spiClass;

Check warning on line 121 in apollo-client/src/main/java/com/ctrip/framework/apollo/internals/DefaultInjector.java

View check run for this annotation

Codecov / codecov/patch

apollo-client/src/main/java/com/ctrip/framework/apollo/internals/DefaultInjector.java#L121

Added line #L121 was not covered by tests
}
}
bind(bindClass).to(impl).in(in);
}

/**
* bind the determining type
*/
private <T> void bind(Class<T> bindClass, Class<? extends Annotation> in) {
bind(bindClass).in(in);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/*
* Copyright 2022 Apollo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.ctrip.framework.apollo.internals;

import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertEquals;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.Mockito.mock;

import com.ctrip.framework.apollo.enums.ConfigSourceType;
import java.util.Properties;
import java.util.Set;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigFile;
import com.ctrip.framework.apollo.build.MockInjector;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.spi.ConfigFactory;
import com.ctrip.framework.apollo.spi.ConfigFactoryManager;
import com.ctrip.framework.apollo.util.ConfigUtil;

/**
* @author tian
*/
public abstract class AbstractConfigManagerTest {
private ConfigManager configManager;
private static String someConfigContent;

protected abstract ConfigManager getConfigManager();

@Before
public void setUp() throws Exception {
MockInjector.setInstance(ConfigFactoryManager.class, new MockConfigFactoryManager());
MockInjector.setInstance(ConfigUtil.class, new ConfigUtil());
configManager = getConfigManager();
someConfigContent = "someContent";
}

@After
public void tearDown() throws Exception {
MockInjector.reset();
}

@Test
public void testGetConfig() throws Exception {
String someNamespace = "someName";
String anotherNamespace = "anotherName";
String someKey = "someKey";
Config config = configManager.getConfig(someNamespace);
Config anotherConfig = configManager.getConfig(anotherNamespace);

assertEquals(someNamespace + ":" + someKey, config.getProperty(someKey, null));
assertEquals(anotherNamespace + ":" + someKey, anotherConfig.getProperty(someKey, null));
}

@Test
public void testGetConfigMultipleTimesWithSameNamespace() throws Exception {
String someNamespace = "someName";
Config config = configManager.getConfig(someNamespace);
Config anotherConfig = configManager.getConfig(someNamespace);

assertThat(
"Get config multiple times with the same namespace should return the same config instance",
config, equalTo(anotherConfig));
}

@Test
public void testGetConfigFile() throws Exception {
String someNamespace = "someName";
ConfigFileFormat someConfigFileFormat = ConfigFileFormat.Properties;

ConfigFile configFile =
configManager.getConfigFile(someNamespace, someConfigFileFormat);

assertEquals(someConfigFileFormat, configFile.getConfigFileFormat());
assertEquals(someConfigContent, configFile.getContent());
}

@Test
public void testGetConfigFileMultipleTimesWithSameNamespace() throws Exception {
String someNamespace = "someName";
ConfigFileFormat someConfigFileFormat = ConfigFileFormat.Properties;

ConfigFile someConfigFile =
configManager.getConfigFile(someNamespace, someConfigFileFormat);
ConfigFile anotherConfigFile =
configManager.getConfigFile(someNamespace, someConfigFileFormat);

assertThat(
"Get config file multiple times with the same namespace should return the same config file instance",
someConfigFile, equalTo(anotherConfigFile));

}

public static class MockConfigFactoryManager implements ConfigFactoryManager {

@Override
public ConfigFactory getFactory(String namespace) {
return new ConfigFactory() {
@Override
public Config create(final String namespace) {
return new AbstractConfig() {
@Override
public String getProperty(String key, String defaultValue) {
return namespace + ":" + key;
}

@Override
public Set<String> getPropertyNames() {
return null;
}

@Override
public ConfigSourceType getSourceType() {
return null;
}
};
}

@Override
public ConfigFile createConfigFile(String namespace, final ConfigFileFormat configFileFormat) {
ConfigRepository someConfigRepository = mock(ConfigRepository.class);
return new AbstractConfigFile(namespace, someConfigRepository) {

@Override
protected void update(Properties newProperties) {

}

@Override
public String getContent() {
return someConfigContent;
}

@Override
public boolean hasContent() {
return true;
}

@Override
public ConfigFileFormat getConfigFileFormat() {
return configFileFormat;
}
};
}
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2022 Apollo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.ctrip.framework.apollo.internals;

/**
* @author tian
*/
public class ConcurrentConfigManagerTest extends AbstractConfigManagerTest {

@Override
protected ConfigManager getConfigManager() {
return new ConcurrentConfigManager();
}
}
Loading

0 comments on commit ccc974a

Please sign in to comment.