use of org.jivesoftware.util.cache.Cache in project Openfire by igniterealtime.
the class RoutingTableImpl method restoreCacheContent.
/**
* When the local node is joining or leaving a cluster, {@link org.jivesoftware.util.cache.CacheFactory} will swap
* the implementation used to instantiate caches. This causes the cache content to be 'reset': it will no longer
* contain the data that's provided by the local node. This method restores data that's provided by the local node
* in the cache. It is expected to be invoked right after joining ({@link #joinedCluster()} or leaving
* ({@link #leftCluster()} a cluster.
*/
private void restoreCacheContent() {
Log.debug("Restoring cache content for cache '{}' by adding all outgoing server routes that are connected to the local cluster node.", serversCache.getName());
// Check if there are local s2s connections that are already in the cache for remote nodes
Set<DomainPair> localServerRoutesToRemove = new HashSet<>();
localRoutingTable.getServerRoutes().forEach(route -> route.getOutgoingDomainPairs().forEach(address -> {
final Lock lock = serversCache.getLock(address);
lock.lock();
try {
if (serversCache.containsKey(address)) {
Log.info("We have an s2s connection to {}, but this connection also exists on other nodes. They are not allowed to both exist, so this local s2s connection will be terminated.", address);
localServerRoutesToRemove.add(address);
} else {
serversCache.put(address, server.getNodeID());
}
} finally {
lock.unlock();
}
}));
for (DomainPair localServerRouteToRemove : localServerRoutesToRemove) {
final RoutableChannelHandler route = localRoutingTable.getRoute(localServerRouteToRemove);
if (route instanceof LocalOutgoingServerSession) {
// That will result in the s2s connection actually being removed from the LocalRoutingTable.
try {
LocalOutgoingServerSession.class.cast(route).close();
} catch (Exception e) {
Log.warn("Failed to terminate the local s2s connection for " + localServerRouteToRemove + ".", e);
}
} else {
Log.warn("Failed to terminate the local s2s connection for {} because it is a {} instead of a LocalOutgoingServerSession.", localServerRouteToRemove, route.getClass());
}
}
Log.debug("Restoring cache content for cache '{}' by adding all component routes that are connected to the local cluster node.", componentsCache.getName());
localRoutingTable.getComponentRoute().forEach(route -> CacheUtil.addValueToMultiValuedCache(componentsCache, route.getAddress().getDomain(), server.getNodeID(), HashSet::new));
addLocalClientRoutesToCache();
}
use of org.jivesoftware.util.cache.Cache in project Openfire by igniterealtime.
the class ClusterListener method addMapListener.
private void addMapListener(Cache cache, MapListener listener) {
if (cache instanceof CacheWrapper) {
Cache wrapped = ((CacheWrapper) cache).getWrappedCache();
if (wrapped instanceof ClusteredCache) {
((ClusteredCache) wrapped).addMapListener(listener, new MapEventFilter(MapEventFilter.E_KEYSET), false);
// Keep track of the listener that we added to the cache
mapListeners.put(cache, listener);
}
}
}
use of org.jivesoftware.util.cache.Cache in project Openfire by igniterealtime.
the class ClusterListener method addEntryListener.
private void addEntryListener(Cache cache, EntryListener listener) {
if (cache instanceof CacheWrapper) {
Cache wrapped = ((CacheWrapper) cache).getWrappedCache();
if (wrapped instanceof ClusteredCache) {
((ClusteredCache) wrapped).addEntryListener(listener, false);
// Keep track of the listener that we added to the cache
EntryListeners.put(cache, listener);
}
}
}
use of org.jivesoftware.util.cache.Cache in project Openfire by igniterealtime.
the class EntityCapabilitiesManager method userDeleting.
@Override
public void userDeleting(User user, Map<String, Object> params) {
// Delete this user's association in entityCapabilitiesUserMap.
final JID bareJid = XMPPServer.getInstance().createJID(user.getUsername(), null, true);
final Set<String> deletedUserVerHashes = new HashSet<>();
// Remember: Cache's are not regular maps. The EntrySet is immutable.
// We'll first find the keys, then remove them in a separate call.
final Lock lock = entityCapabilitiesUserMap.getLock(bareJid);
lock.lock();
try {
// Iterating over the entire set is not ideal from a performance perspective. Note that this is a
// local cache. Things would have been much worse if this would have been a clustered cache.
final Set<JID> jidsToRemove = entityCapabilitiesUserMap.keySet().stream().filter(jid -> jid.asBareJID().equals(bareJid)).collect(Collectors.toSet());
for (final JID jidToRemove : jidsToRemove) {
final String removed = entityCapabilitiesUserMap.remove(jidToRemove);
if (removed != null) {
deletedUserVerHashes.add(removed);
}
}
} finally {
lock.unlock();
}
// If there are no other references to the deleted user's 'ver' hash,
// it is safe to remove that 'ver' hash's associated entity
// capabilities from the entityCapabilitiesMap cache.
deletedUserVerHashes.forEach(this::checkObsolete);
}
use of org.jivesoftware.util.cache.Cache in project Openfire by igniterealtime.
the class RoutingTableImpl method joinedCluster.
@Override
public void joinedCluster() {
// The local node joined a cluster.
//
// Upon joining a cluster, clustered caches are reset to their clustered equivalent (by the swap from the local
// cache implementation to the clustered cache implementation that's done in the implementation of
// org.jivesoftware.util.cache.CacheFactory.joinedCluster). This means that they now hold data that's
// available on all other cluster nodes. Data that's available on the local node needs to be added again.
restoreCacheContent();
Log.debug("Add the entry listeners to the corresponding caches.");
// Register a cache entry event listeners that will collect data for entries added by all other cluster nodes,
// which is intended to be used (only) in the event of a cluster split.
final ClusteredCacheEntryListener<String, ClientRoute> userCacheEntryListener = new ReverseLookupUpdatingCacheEntryListener<>(routeOwnersByClusterNode);
final ClusteredCacheEntryListener<DomainPair, NodeID> serversCacheEntryListener = new ReverseLookupUpdatingCacheEntryListener<>(s2sDomainPairsByClusterNode);
final ClusteredCacheEntryListener<String, HashSet<NodeID>> componentsCacheEntryListener = new ReverseLookupComputingCacheEntryListener<>(componentsByClusterNode, nodeIDS -> nodeIDS.stream().filter(n -> !n.equals(XMPPServer.getInstance().getNodeID())).collect(Collectors.toSet()));
// Note that, when #joinedCluster() fired, the cache will _always_ have been replaced, meaning that it won't
// have old event listeners. When #leaveCluster() fires, the cache will be destroyed. This takes away the need
// to explicitly deregister the listener in that case.
// Ensure that event listeners have been registered with the caches, before starting to simulate 'entryAdded' events,
// to prevent the possibility of having entries that are missed by the simulation because of bad timing.
usersCache.addClusteredCacheEntryListener(userCacheEntryListener, false, false);
anonymousUsersCache.addClusteredCacheEntryListener(userCacheEntryListener, false, false);
serversCache.addClusteredCacheEntryListener(serversCacheEntryListener, false, false);
componentsCache.addClusteredCacheEntryListener(componentsCacheEntryListener, true, true);
// This is not necessary for the usersSessions cache, because its content is being managed while the content
// of users cache and anonymous users cache is being managed.
Log.debug("Simulate 'entryAdded' for all data that already exists elsewhere in the cluster.");
Stream.concat(usersCache.entrySet().stream(), anonymousUsersCache.entrySet().stream()).filter(entry -> !entry.getValue().getNodeID().equals(XMPPServer.getInstance().getNodeID())).forEach(entry -> userCacheEntryListener.entryAdded(entry.getKey(), entry.getValue(), entry.getValue().getNodeID()));
serversCache.entrySet().stream().filter(entry -> !entry.getValue().equals(XMPPServer.getInstance().getNodeID())).forEach(entry -> serversCacheEntryListener.entryAdded(entry.getKey(), entry.getValue(), entry.getValue()));
componentsCache.entrySet().forEach(entry -> {
entry.getValue().forEach(nodeIdForComponent -> {
// Iterate over all node ids on which the component is known
if (!nodeIdForComponent.equals(XMPPServer.getInstance().getNodeID())) {
// Here we pretend that the component has been added by the node id on which it is reported to
// be available. This might not have been the case, but it is probably accurate. An alternative
// approach is not easily available.
componentsCacheEntryListener.entryAdded(entry.getKey(), entry.getValue(), nodeIdForComponent);
}
});
});
// Broadcast presence of local sessions to remote sessions when subscribed to presence.
// Probe presences of remote sessions when subscribed to presence of local session.
// Send pending subscription requests to local sessions from remote sessions.
// Deliver offline messages sent to local sessions that were unavailable in other nodes.
// Send available presences of local sessions to other resources of the same user.
PresenceUpdateHandler presenceUpdateHandler = XMPPServer.getInstance().getPresenceUpdateHandler();
for (LocalClientSession session : localRoutingTable.getClientRoutes()) {
// Simulate that the local session has just become available
session.setInitialized(false);
// Simulate that current session presence has just been received
presenceUpdateHandler.process(session.getPresence());
}
// TODO OF-2067: the above also (re)generates events on the local node, where these events had already occurred. Ideally, that should not happen.
// TODO OF-2066: shouldn't a similar action be done on the other nodes, so that the node that just joined gets informed about all sessions living on other cluster nodes?
}
Aggregations