/**
* Copyright 2016 Yahoo Inc.
*
* 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.yahoo.pulsar.broker.namespace;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.yahoo.pulsar.common.naming.NamespaceBundle;
import com.yahoo.pulsar.broker.PulsarService;
public class OwnedBundle {
private static final Logger LOG = LoggerFactory.getLogger(OwnedBundle.class);
private final NamespaceBundle bundle;
/**
* {@link #nsLock} is used to protect read/write access to {@link #active} flag and the corresponding code section
* based on {@link #active} flag
*/
private final ReentrantReadWriteLock nsLock = new ReentrantReadWriteLock();
private static final int FALSE = 0;
private static final int TRUE = 1;
private static final AtomicIntegerFieldUpdater<OwnedBundle> IS_ACTIVE_UPDATER =
AtomicIntegerFieldUpdater.newUpdater(OwnedBundle.class, "isActive");
private volatile int isActive = TRUE;
/**
* constructor
*
* @param nsname
*/
public OwnedBundle(NamespaceBundle suName) {
this.bundle = suName;
IS_ACTIVE_UPDATER.set(this, TRUE);
};
/**
* Constructor to allow set initial active flag
*
* @param nsname
* @param nssvc
* @param active
*/
public OwnedBundle(NamespaceBundle suName, boolean active) {
this.bundle = suName;
IS_ACTIVE_UPDATER.set(this, active ? TRUE : FALSE);
}
/**
* Access to the namespace name
*
* @return NamespaceName
*/
public NamespaceBundle getNamespaceBundle() {
return this.bundle;
}
/**
* This method initiates the unload namespace process. It is invoked by Admin API
* <code>Namespaces.unloadNamespace()</code>.
*
* @param pulsar
* @param adminView
* @param ownershipCache
*
* @throws Exception
*/
public void handleUnloadRequest(PulsarService pulsar) throws Exception {
long unloadBundleStartTime = System.nanoTime();
// Need a per namespace RenetrantReadWriteLock
// Here to do a writeLock to set the flag and proceed to check and close connections
while (!this.nsLock.writeLock().tryLock(1, TimeUnit.SECONDS)) {
// Using tryLock to avoid deadlocks caused by 2 threads trying to acquire 2 readlocks (eg: JMS replicators)
// while a handleUnloadRequest happens in the middle
LOG.warn("Contention on OwnedBundle rw lock. Retrying to acquire lock write lock");
}
try {
// set the flag locally s.t. no more producer/consumer to this namespace is allowed
if (!IS_ACTIVE_UPDATER.compareAndSet(this, TRUE, FALSE)) {
// An exception is thrown when the namespace is not in active state (i.e. another thread is
// removing/have removed it)
throw new IllegalStateException(
"Namespace is not active. ns:" + this.bundle + "; state:" + IS_ACTIVE_UPDATER.get(this));
}
} finally {
// no matter success or not, unlock
this.nsLock.writeLock().unlock();
}
int unloadedTopics = 0;
try {
LOG.info("Disabling ownership: {}", this.bundle);
pulsar.getNamespaceService().getOwnershipCache().updateBundleState(this.bundle, false);
// close topics forcefully
try {
unloadedTopics = pulsar.getBrokerService().unloadServiceUnit(bundle).get();
} catch (Exception e) {
// ignore topic-close failure to unload bundle
LOG.error("Failed to close topics under namespace {}", bundle.toString(), e);
}
// delete ownership node on zk
try {
pulsar.getNamespaceService().getOwnershipCache().removeOwnership(bundle).get();
} catch (Exception e) {
// Failed to remove ownership node: enable namespace-bundle again so, it can serve new topics
pulsar.getNamespaceService().getOwnershipCache().updateBundleState(this.bundle, true);
throw new RuntimeException(String.format("Failed to delete ownership node %s", bundle.toString()),
e.getCause());
}
} catch (Exception e) {
LOG.error("Failed to unload a namespace {}", bundle.toString(), e);
throw new RuntimeException(e);
}
double unloadBundleTime = TimeUnit.NANOSECONDS.toMillis((System.nanoTime() - unloadBundleStartTime));
LOG.info("Unloading {} namespace-bundle with {} topics completed in {} ms", this.bundle, unloadedTopics, unloadBundleTime);
}
/**
* Access method to the namespace state to check whether the namespace is active or not
*
* @return boolean value indicate that the namespace is active or not.
*/
public boolean isActive() {
return IS_ACTIVE_UPDATER.get(this) == TRUE;
}
public void setActive(boolean active) {
IS_ACTIVE_UPDATER.set(this, active ? TRUE : FALSE);
}
}