X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=blobdiff_plain;f=opendaylight%2Fconfig%2Fconfig-manager%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fcontroller%2Fconfig%2Fmanager%2Fimpl%2FConfigRegistryImpl.java;h=369e7ed2a5c1b26bb6b0152cdfe35f5ab47ff1ec;hp=39ea73be55a63788c17c0edcd5a9215e8550a1be;hb=cbcc2b61265e903959f11d44c292771e76b3926e;hpb=7c36786fa7e491d952c82cb13f28bbe87bc26015 diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ConfigRegistryImpl.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ConfigRegistryImpl.java index 39ea73be55..369e7ed2a5 100644 --- a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ConfigRegistryImpl.java +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ConfigRegistryImpl.java @@ -7,6 +7,7 @@ */ package org.opendaylight.controller.config.manager.impl; +import com.google.common.base.Throwables; import com.google.common.collect.Maps; import java.util.ArrayList; import java.util.Collection; @@ -18,8 +19,13 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import javax.annotation.concurrent.GuardedBy; -import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.ThreadSafe; import javax.management.InstanceAlreadyExistsException; import javax.management.InstanceNotFoundException; @@ -60,22 +66,20 @@ import org.slf4j.LoggerFactory; @ThreadSafe public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBean { private static final Logger LOG = LoggerFactory.getLogger(ConfigRegistryImpl.class); + private static final ObjectName NOOP_TX_NAME = ObjectNameUtil.createTransactionControllerON("noop"); private final ModuleFactoriesResolver resolver; private final MBeanServer configMBeanServer; private final BindingContextProvider bindingContextProvider; - @GuardedBy("this") - private long version = 0; - @GuardedBy("this") - private long versionCounter = 0; + private volatile long version = 0; + private volatile long versionCounter = 0; /** * Contains current configuration in form of {moduleName:{instanceName,read * only module}} for copying state to new transaction. Each running module * is present just once, no matter how many interfaces it exposes. */ - @GuardedBy("this") private final ConfigHolder currentConfig = new ConfigHolder(); /** @@ -83,14 +87,12 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe * validation but failed in second phase of commit. In this case the server * is unstable and its state is undefined. */ - @GuardedBy("this") - private boolean isHealthy = true; + private volatile boolean isHealthy = true; /** * Holds Map and purges it each time * its content is requested. */ - @GuardedBy("this") private final TransactionsHolder transactionsHolder = new TransactionsHolder(); private final BaseJMXRegistrator baseJMXRegistrator; @@ -102,24 +104,32 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe // internal jmx server shared by all transactions private final MBeanServer transactionsMBeanServer; + private final AtomicBoolean closed = new AtomicBoolean(); + + private final Object readableSRRegistryLock = new Object(); + + private final Lock configTransactionLock = new ReentrantLock(); + // Used for finding new factory instances for default module functionality - @GuardedBy("this") + @GuardedBy("configTransactionLock") private List lastListOfFactories = Collections.emptyList(); - @GuardedBy("this") // switched in every 2ndPC - private CloseableServiceReferenceReadableRegistry readableSRRegistry = ServiceReferenceRegistryImpl.createInitialSRLookupRegistry(); + // switched in every 2ndPC + @GuardedBy("readableSRRegistryLock") + private CloseableServiceReferenceReadableRegistry readableSRRegistry = + ServiceReferenceRegistryImpl.createInitialSRLookupRegistry(); // constructor - public ConfigRegistryImpl(ModuleFactoriesResolver resolver, - MBeanServer configMBeanServer, BindingContextProvider bindingContextProvider) { + public ConfigRegistryImpl(final ModuleFactoriesResolver resolver, + final MBeanServer configMBeanServer, final BindingContextProvider bindingContextProvider) { this(resolver, configMBeanServer, new BaseJMXRegistrator(configMBeanServer), bindingContextProvider); } // constructor - public ConfigRegistryImpl(ModuleFactoriesResolver resolver, - MBeanServer configMBeanServer, - BaseJMXRegistrator baseJMXRegistrator, BindingContextProvider bindingContextProvider) { + public ConfigRegistryImpl(final ModuleFactoriesResolver resolver, + final MBeanServer configMBeanServer, + final BaseJMXRegistrator baseJMXRegistrator, final BindingContextProvider bindingContextProvider) { this.resolver = resolver; this.beanToOsgiServiceManager = new BeanToOsgiServiceManager(); this.configMBeanServer = configMBeanServer; @@ -135,7 +145,7 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe * Create new {@link ConfigTransactionControllerImpl } */ @Override - public synchronized ObjectName beginConfig() { + public ObjectName beginConfig() { return beginConfig(false); } @@ -143,11 +153,46 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe * @param blankTransaction true if this transaction is created automatically by * org.opendaylight.controller.config.manager.impl.osgi.BlankTransactionServiceTracker */ - public synchronized ObjectName beginConfig(boolean blankTransaction) { - return beginConfigInternal(blankTransaction).getControllerObjectName(); + public ObjectName beginConfig(final boolean blankTransaction) { + // If we're closed or in the process of closing then all modules are destroyed or being destroyed + // so there's no point in trying to acquire the lock and beginning an actual transaction. Also we want + // to avoid trying to lock as it may block the shutdown process if there is an outstanding transaction + // attempting to be committed. + // + // We could throw an exception here to indicate this but that's not part of the original API contract + // and callers may not be expecting an unchecked exception. Therefore we return a special transaction + // handle that signifies a "no-op". + if(closed.get()) { + return NOOP_TX_NAME; + } + + if(blankTransaction) { + try { + // For a "blank" transaction, we'll try to obtain the config lock but "blank" transactions + // are initiated via OSGi events so we don't want to block indefinitely or for a long period + // of time. + if(!configTransactionLock.tryLock(5, TimeUnit.SECONDS)) { + LOG.debug("Timed out trying to obtain configTransactionLock"); + return NOOP_TX_NAME; + } + } catch(final InterruptedException e) { + LOG.debug("Interrupted trying to obtain configTransactionLock", e); + Thread.currentThread().interrupt(); + return NOOP_TX_NAME; + } + } else { + configTransactionLock.lock(); + } + + try { + return beginConfigSafe(blankTransaction).getControllerObjectName(); + } finally { + configTransactionLock.unlock(); + } } - private synchronized ConfigTransactionControllerInternal beginConfigInternal(boolean blankTransaction) { + @GuardedBy("configTransactionLock") + private ConfigTransactionControllerInternal beginConfigSafe(final boolean blankTransaction) { versionCounter++; final String transactionName = "ConfigTransaction-" + version + "-" + versionCounter; @@ -164,7 +209,7 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe // add all factories that disappeared from SR but are still committed for (ModuleInternalInfo moduleInternalInfo : currentConfig.getEntries()) { String name = moduleInternalInfo.getModuleFactory().getImplementationName(); - if (allCurrentFactories.containsKey(name) == false) { + if (!allCurrentFactories.containsKey(name)) { LOG.trace("Factory {} not found in SR, using reference from previous commit", name); allCurrentFactories.put(name, Maps.immutableEntry(moduleInternalInfo.getModuleFactory(), moduleInternalInfo.getBundleContext())); @@ -184,7 +229,7 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe configMBeanServer, blankTransaction, writableRegistry); try { txLookupRegistry.registerMBean(transactionController, transactionController.getControllerObjectName()); - } catch (InstanceAlreadyExistsException e) { + } catch (final InstanceAlreadyExistsException e) { throw new IllegalStateException(e); } transactionController.copyExistingModulesAndProcessFactoryDiff(currentConfig.getEntries(), lastListOfFactories); @@ -194,9 +239,25 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe /** * {@inheritDoc} + * @throws ConflictingVersionException */ @Override - public synchronized CommitStatus commitConfig(ObjectName transactionControllerON) + public CommitStatus commitConfig(final ObjectName transactionControllerON) + throws ValidationException, ConflictingVersionException { + if(NOOP_TX_NAME.equals(transactionControllerON) || closed.get()) { + return new CommitStatus(Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + } + + configTransactionLock.lock(); + try { + return commitConfigSafe(transactionControllerON); + } finally { + configTransactionLock.unlock(); + } + } + + @GuardedBy("configTransactionLock") + private CommitStatus commitConfigSafe(final ObjectName transactionControllerON) throws ConflictingVersionException, ValidationException { final String transactionName = ObjectNameUtil .getTransactionName(transactionControllerON); @@ -225,55 +286,61 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe // non recoverable from here: try { return secondPhaseCommit(configTransactionController, commitInfo, configTransactionControllerEntry.getValue()); - } catch (Error | RuntimeException t) { // some libs throw Errors: e.g. + // some libs throw Errors: e.g. // javax.xml.ws.spi.FactoryFinder$ConfigurationError + } catch (final Throwable t) { isHealthy = false; LOG.error("Configuration Transaction failed on 2PC, server is unhealthy", t); - if (t instanceof RuntimeException) { - throw (RuntimeException) t; - } else { - throw (Error) t; - } + throw Throwables.propagate(t); } } - private CommitStatus secondPhaseCommit(ConfigTransactionControllerInternal configTransactionController, - CommitInfo commitInfo, ConfigTransactionLookupRegistry txLookupRegistry) { + @GuardedBy("configTransactionLock") + private CommitStatus secondPhaseCommit(final ConfigTransactionControllerInternal configTransactionController, + final CommitInfo commitInfo, final ConfigTransactionLookupRegistry txLookupRegistry) { // close instances which were destroyed by the user, including // (hopefully) runtime beans for (DestroyedModule toBeDestroyed : commitInfo .getDestroyedFromPreviousTransactions()) { - toBeDestroyed.close(); // closes instance (which should close + // closes instance (which should close // runtime jmx registrator), // also closes osgi registration and ModuleJMXRegistrator // registration + toBeDestroyed.close(); currentConfig.remove(toBeDestroyed.getIdentifier()); } // set RuntimeBeanRegistrators on beans implementing - // RuntimeBeanRegistratorAwareModule, each module - // should have exactly one runtime jmx registrator. + // RuntimeBeanRegistratorAwareModule Map runtimeRegistrators = new HashMap<>(); for (ModuleInternalTransactionalInfo entry : commitInfo.getCommitted() .values()) { - RootRuntimeBeanRegistratorImpl runtimeBeanRegistrator; - if (entry.hasOldModule() == false) { - runtimeBeanRegistrator = baseJMXRegistrator - .createRuntimeBeanRegistrator(entry.getIdentifier()); - } else { - // reuse old JMX registrator - runtimeBeanRegistrator = entry.getOldInternalInfo() - .getRuntimeBeanRegistrator(); - } // set runtime jmx registrator if required Module module = entry.getProxiedModule(); + RootRuntimeBeanRegistratorImpl runtimeBeanRegistrator = null; + if (module instanceof RuntimeBeanRegistratorAwareModule) { - ((RuntimeBeanRegistratorAwareModule) module) - .setRuntimeBeanRegistrator(runtimeBeanRegistrator); + + if(entry.hasOldModule()) { + + if(module.canReuse(entry.getOldInternalInfo().getReadableModule().getModule())) { + runtimeBeanRegistrator = entry.getOldInternalInfo().getRuntimeBeanRegistrator(); + ((RuntimeBeanRegistratorAwareModule) module).setRuntimeBeanRegistrator(runtimeBeanRegistrator); + } else { + runtimeBeanRegistrator = baseJMXRegistrator.createRuntimeBeanRegistrator(entry.getIdentifier()); + entry.getOldInternalInfo().getRuntimeBeanRegistrator().close(); + ((RuntimeBeanRegistratorAwareModule) module).setRuntimeBeanRegistrator(runtimeBeanRegistrator); + } + } else { + runtimeBeanRegistrator = baseJMXRegistrator.createRuntimeBeanRegistrator(entry.getIdentifier()); + ((RuntimeBeanRegistratorAwareModule) module).setRuntimeBeanRegistrator(runtimeBeanRegistrator); + } } // save it to info so it is accessible afterwards - runtimeRegistrators.put(entry.getIdentifier(), runtimeBeanRegistrator); + if(runtimeBeanRegistrator != null) { + runtimeRegistrators.put(entry.getIdentifier(), runtimeBeanRegistrator); + } } // can register runtime beans @@ -338,6 +405,11 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe // close old module jmx registrator oldInternalInfo.getModuleJMXRegistrator().close(); + + // We no longer need old internal info. Clear it out, so we do not create a serial leak evidenced + // by BUG-4514. The reason is that modules retain their resolver, which retains modules. If we retain + // the old module, we would have the complete reconfiguration history held in heap for no good reason. + entry.clearOldInternalInfo(); } else { // new instance: // wrap in readable dynamic mbean @@ -352,7 +424,7 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe // register to JMX try { newModuleJMXRegistrator.registerMBean(newReadableConfigBean, primaryReadOnlyON); - } catch (InstanceAlreadyExistsException e) { + } catch (final InstanceAlreadyExistsException e) { throw new IllegalStateException("Possible code error, already registered:" + primaryReadOnlyON,e); } @@ -383,9 +455,11 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe version = configTransactionController.getVersion(); // switch readable Service Reference Registry - this.readableSRRegistry.close(); - this.readableSRRegistry = ServiceReferenceRegistryImpl.createSRReadableRegistry( - configTransactionController.getWritableRegistry(), this, baseJMXRegistrator); + synchronized(readableSRRegistryLock) { + readableSRRegistry.close(); + readableSRRegistry = ServiceReferenceRegistryImpl.createSRReadableRegistry( + configTransactionController.getWritableRegistry(), this, baseJMXRegistrator); + } return new CommitStatus(newInstances, reusedInstances, recreatedInstances); @@ -395,7 +469,7 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe * {@inheritDoc} */ @Override - public synchronized List getOpenConfigs() { + public List getOpenConfigs() { Map> transactions = transactionsHolder .getCurrentTransactions(); List result = new ArrayList<>(transactions.size()); @@ -413,7 +487,11 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe * here. */ @Override - public synchronized void close() { + public void close() { + if(!closed.compareAndSet(false, true)) { + return; + } + // abort transactions Map> transactions = transactionsHolder .getCurrentTransactions(); @@ -424,15 +502,16 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe try { configTransactionControllerEntry.getValue().close(); configTransactionController.abortConfig(); - } catch (RuntimeException e) { - LOG.warn("Ignoring exception while aborting {}", - configTransactionController, e); + } catch (final RuntimeException e) { + LOG.debug("Ignoring exception while aborting {}", configTransactionController, e); } } // destroy all live objects one after another in order of the dependency hierarchy, from top to bottom - List destroyedModules = currentConfig - .getModulesToBeDestroyed(); + List destroyedModules = currentConfig.getModulesToBeDestroyed(); + + LOG.info("ConfigRegistry closing - destroying {} modules", destroyedModules.size()); + for (DestroyedModule destroyedModule : destroyedModules) { destroyedModule.close(); } @@ -442,6 +521,7 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe MBeanServerFactory.releaseMBeanServer(registryMBeanServer); MBeanServerFactory.releaseMBeanServer(transactionsMBeanServer); + LOG.info("ConfigRegistry closed"); } /** @@ -483,7 +563,7 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe * {@inheritDoc} */ @Override - public Set lookupConfigBeans(String moduleName) { + public Set lookupConfigBeans(final String moduleName) { return lookupConfigBeans(moduleName, "*"); } @@ -491,7 +571,7 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe * {@inheritDoc} */ @Override - public ObjectName lookupConfigBean(String moduleName, String instanceName) + public ObjectName lookupConfigBean(final String moduleName, final String instanceName) throws InstanceNotFoundException { return LookupBeansUtil.lookupConfigBean(this, moduleName, instanceName); } @@ -500,8 +580,8 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe * {@inheritDoc} */ @Override - public Set lookupConfigBeans(String moduleName, - String instanceName) { + public Set lookupConfigBeans(final String moduleName, + final String instanceName) { ObjectName namePattern = ObjectNameUtil.createModulePattern(moduleName, instanceName); return baseJMXRegistrator.queryNames(namePattern, null); @@ -519,8 +599,8 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe * {@inheritDoc} */ @Override - public Set lookupRuntimeBeans(String moduleName, - String instanceName) { + public Set lookupRuntimeBeans(final String moduleName, + final String instanceName) { String finalModuleName = moduleName == null ? "*" : moduleName; String finalInstanceName = instanceName == null ? "*" : instanceName; ObjectName namePattern = ObjectNameUtil.createRuntimeBeanPattern( @@ -529,7 +609,7 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe } @Override - public void checkConfigBeanExists(ObjectName objectName) throws InstanceNotFoundException { + public void checkConfigBeanExists(final ObjectName objectName) throws InstanceNotFoundException { ObjectNameUtil.checkDomain(objectName); ObjectNameUtil.checkType(objectName, ObjectNameUtil.TYPE_MODULE); String transactionName = ObjectNameUtil.getTransactionName(objectName); @@ -542,38 +622,52 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe // service reference functionality: @Override - public synchronized ObjectName lookupConfigBeanByServiceInterfaceName(String serviceInterfaceQName, String refName) { - return readableSRRegistry.lookupConfigBeanByServiceInterfaceName(serviceInterfaceQName, refName); + public ObjectName lookupConfigBeanByServiceInterfaceName(final String serviceInterfaceQName, final String refName) { + synchronized(readableSRRegistryLock) { + return readableSRRegistry.lookupConfigBeanByServiceInterfaceName(serviceInterfaceQName, refName); + } } @Override - public synchronized Map> getServiceMapping() { - return readableSRRegistry.getServiceMapping(); + public Map> getServiceMapping() { + synchronized(readableSRRegistryLock) { + return readableSRRegistry.getServiceMapping(); + } } @Override - public synchronized Map lookupServiceReferencesByServiceInterfaceName(String serviceInterfaceQName) { - return readableSRRegistry.lookupServiceReferencesByServiceInterfaceName(serviceInterfaceQName); + public Map lookupServiceReferencesByServiceInterfaceName(final String serviceInterfaceQName) { + synchronized(readableSRRegistryLock) { + return readableSRRegistry.lookupServiceReferencesByServiceInterfaceName(serviceInterfaceQName); + } } @Override - public synchronized Set lookupServiceInterfaceNames(ObjectName objectName) throws InstanceNotFoundException { - return readableSRRegistry.lookupServiceInterfaceNames(objectName); + public Set lookupServiceInterfaceNames(final ObjectName objectName) throws InstanceNotFoundException { + synchronized(readableSRRegistryLock) { + return readableSRRegistry.lookupServiceInterfaceNames(objectName); + } } @Override - public synchronized String getServiceInterfaceName(String namespace, String localName) { - return readableSRRegistry.getServiceInterfaceName(namespace, localName); + public String getServiceInterfaceName(final String namespace, final String localName) { + synchronized(readableSRRegistryLock) { + return readableSRRegistry.getServiceInterfaceName(namespace, localName); + } } @Override - public void checkServiceReferenceExists(ObjectName objectName) throws InstanceNotFoundException { - readableSRRegistry.checkServiceReferenceExists(objectName); + public void checkServiceReferenceExists(final ObjectName objectName) throws InstanceNotFoundException { + synchronized(readableSRRegistryLock) { + readableSRRegistry.checkServiceReferenceExists(objectName); + } } @Override - public ObjectName getServiceReference(String serviceInterfaceQName, String refName) throws InstanceNotFoundException { - return readableSRRegistry.getServiceReference(serviceInterfaceQName, refName); + public ObjectName getServiceReference(final String serviceInterfaceQName, final String refName) throws InstanceNotFoundException { + synchronized(readableSRRegistryLock) { + return readableSRRegistry.getServiceReference(serviceInterfaceQName, refName); + } } @Override @@ -593,15 +687,14 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe /** * Holds currently running modules */ -@NotThreadSafe class ConfigHolder { - private final Map currentConfig = new HashMap<>(); + private final ConcurrentMap currentConfig = new ConcurrentHashMap<>(); /** * Add all modules to the internal map. Also add service instance to OSGi * Service Registry. */ - public void addAll(Collection configInfos) { + public void addAll(final Collection configInfos) { if (!currentConfig.isEmpty()) { throw new IllegalStateException( "Error - some config entries were not removed: " @@ -612,9 +705,8 @@ class ConfigHolder { } } - private void add(ModuleInternalInfo configInfo) { - ModuleInternalInfo oldValue = currentConfig.put(configInfo.getIdentifier(), - configInfo); + private void add(final ModuleInternalInfo configInfo) { + ModuleInternalInfo oldValue = currentConfig.putIfAbsent(configInfo.getIdentifier(), configInfo); if (oldValue != null) { throw new IllegalStateException( "Cannot overwrite module with same name:" @@ -625,7 +717,7 @@ class ConfigHolder { /** * Remove entry from current config. */ - public void remove(ModuleIdentifier name) { + public void remove(final ModuleIdentifier name) { ModuleInternalInfo removed = currentConfig.remove(name); if (removed == null) { throw new IllegalStateException( @@ -653,7 +745,6 @@ class ConfigHolder { * Holds Map and purges it each time its * content is requested. */ -@NotThreadSafe class TransactionsHolder { /** * This map keeps transaction names and @@ -661,16 +752,12 @@ class TransactionsHolder { * MBeanServer transforms mbeans into another representation. Map is cleaned * every time current transactions are requested. */ - @GuardedBy("ConfigRegistryImpl.this") - private final Map> transactions = new HashMap<>(); + private final ConcurrentMap> transactions = new ConcurrentHashMap<>(); - /** - * Can only be called from within synchronized method. - */ - public void add(String transactionName, - ConfigTransactionControllerInternal transactionController, ConfigTransactionLookupRegistry txLookupRegistry) { - Object oldValue = transactions.put(transactionName, + public void add(final String transactionName, + final ConfigTransactionControllerInternal transactionController, final ConfigTransactionLookupRegistry txLookupRegistry) { + Object oldValue = transactions.putIfAbsent(transactionName, Maps.immutableEntry(transactionController, txLookupRegistry)); if (oldValue != null) { throw new IllegalStateException( @@ -679,8 +766,7 @@ class TransactionsHolder { } /** - * Purges closed transactions from transactions map. Can only be called from - * within synchronized method. Calling this method more than once within the + * Purges closed transactions from transactions map. Calling this method more than once within the * method can modify the resulting map that was obtained in previous calls. * * @return current view on transactions map.