/* * Licensed to the Apache Software Foundation (ASF) under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional information regarding * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.internal.cache.partitioned; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.CountDownLatch; import org.apache.logging.log4j.Logger; import org.apache.geode.distributed.internal.membership.InternalDistributedMember; import org.apache.geode.internal.net.SocketCreator; import org.apache.geode.internal.cache.BucketPersistenceAdvisor; import org.apache.geode.internal.cache.ColocationHelper; import org.apache.geode.internal.cache.DiskStoreImpl; import org.apache.geode.internal.cache.PRHARedundancyProvider; import org.apache.geode.internal.cache.PartitionedRegion; import org.apache.geode.internal.cache.ProxyBucketRegion; import org.apache.geode.internal.cache.persistence.PersistentMemberID; import org.apache.geode.internal.cache.persistence.PersistentStateListener; import org.apache.geode.internal.i18n.LocalizedStrings; import org.apache.geode.internal.logging.LogService; import org.apache.geode.internal.process.StartupStatus; import org.apache.geode.internal.util.TransformUtils; /** * Consolidates logging during the recovery of ProxyRegionBuckets that are not hosted by this * member. This logger is meant to run in its own thread and utilizes the PRHARedundancyProvider's * count down latch in order to determine when it is finished. * */ public class RedundancyLogger extends RecoveryRunnable implements PersistentStateListener { private static final Logger logger = LogService.getLogger(); /** * True when one or more buckets have reported a change in status. */ private volatile boolean membershipChanged = true; /** * Sleep period between posting log entries. */ private static final int SLEEP_PERIOD = 15000; /** * Used to determine when all proxy buckets have been recovered. */ private final CountDownLatch allBucketsRecoveredFromDisk; private final List<RegionStatus> regions; /** * Creates a new RedundancyLogger. * * @param prhaRedundancyProvider */ public RedundancyLogger(PRHARedundancyProvider prhaRedundancyProvider) { super(prhaRedundancyProvider); PartitionedRegion baseRegion = ColocationHelper.getLeaderRegion(redundancyProvider.prRegion); List<PartitionedRegion> colocatedRegions = ColocationHelper.getColocatedChildRegions(baseRegion); List<RegionStatus> allRegions = new ArrayList<RegionStatus>(colocatedRegions.size() + 1); if (baseRegion.getDataPolicy().withPersistence()) { allRegions.add(new RegionStatus(baseRegion)); } for (PartitionedRegion region : colocatedRegions) { if (region.getDataPolicy().withPersistence()) { allRegions.add(new RegionStatus(region)); } } this.regions = Collections.unmodifiableList(allRegions); this.allBucketsRecoveredFromDisk = redundancyProvider.getAllBucketsRecoveredFromDiskLatch(); this.membershipChanged = true; addListeners(); } /** * Called when a member comes online for a bucket. */ @Override public void memberOnline(InternalDistributedMember member, PersistentMemberID persistentID) { this.membershipChanged = true; } /** * Called when a member goes offline for a bucket. */ @Override public void memberOffline(InternalDistributedMember member, PersistentMemberID persistentID) { this.membershipChanged = true; } /** * Called when a member is removed for a bucket. */ @Override public void memberRemoved(PersistentMemberID persistentID, boolean revoked) { this.membershipChanged = true; } /** * Add this RedundancyLogger as a persistence listener to all the region's bucket advisors. */ private void addListeners() { for (RegionStatus region : regions) { region.addListeners(); } } /** * Removes this RedundancyLogger as a persistence listener from all the region's bucket advisors. */ private void removeListeners() { for (RegionStatus region : regions) { region.removeListeners(); } } /** * Writes a consolidated log entry every SLEEP_PERIOD that summarizes which buckets are still * waiting on persistent members for the region. */ @Override public void run2() { try { boolean warningLogged = false; while (this.allBucketsRecoveredFromDisk.getCount() > 0) { int sleepMillis = SLEEP_PERIOD; // reduce the first log time from 15secs so that higher layers can // report sooner to user if (!warningLogged) { sleepMillis = SLEEP_PERIOD / 2; } Thread.sleep(sleepMillis); if (this.membershipChanged) { this.membershipChanged = false; for (RegionStatus region : regions) { region.logWaitingForMembers(); } warningLogged = true; } } } catch (InterruptedException e) { // Log and bail logger.error(e.getMessage(), e); } finally { /* * Our job is done. Stop listening to the bucket advisors. */ removeListeners(); /* * Make sure the recovery completion message was printed to the log. */ for (RegionStatus region : regions) { if (!region.loggedDoneMessage) { region.logDoneMessage(); } } } } /** * Keeps track of logging a message for a single partitioned region and logging a separate message * when the waiting is done for the same region * */ private class RegionStatus { /** * The persistent identifier of the member running this RedundancyLogger. */ private final PersistentMemberID thisMember; /** * The region that the proxy buckets belong to. */ private final String region; /** * An array of ProxyBucketRegions that comprise this partitioned region. */ private final ProxyBucketRegion[] bucketRegions; /** * Indicates that a completion message has been logged. */ private volatile boolean loggedDoneMessage = true; public RegionStatus(PartitionedRegion region) { this.thisMember = createPersistentMemberID(region); this.region = region.getFullPath(); this.bucketRegions = region.getRegionAdvisor().getProxyBucketArray(); } public void removeListeners() { for (ProxyBucketRegion proxyBucket : this.bucketRegions) { proxyBucket.getPersistenceAdvisor().removeListener(RedundancyLogger.this); } } public void addListeners() { for (ProxyBucketRegion proxyBucket : this.bucketRegions) { proxyBucket.getPersistenceAdvisor().addListener(RedundancyLogger.this); } } /** * Creates a temporary (and somewhat fake) PersistentMemberID for this member if there is no * DiskStore available for our region (which can happen in some colocated scenarios). */ private PersistentMemberID createPersistentMemberID(PartitionedRegion region) { DiskStoreImpl diskStore = null; /* * A non-persistent colocated region will not have a disk store so check the leader region if * this region does not have one. */ if (region.getAttributes().getDataPolicy().withPersistence()) { diskStore = region.getDiskStore(); } else if (ColocationHelper.getLeaderRegion(region).getAttributes().getDataPolicy() .withPersistence()) { diskStore = ColocationHelper.getLeaderRegion(region).getDiskStore(); } /* * We have a DiskStore? Great! Simply have it generate the id. */ if (null != diskStore) { return diskStore.generatePersistentID(null); } /* * Bummer. No DiskStore. Put together a fake one (for logging only). */ { String name = "No name for this member"; String diskDir = System.getProperty("user.dir"); InetAddress localHost = null; try { localHost = SocketCreator.getLocalHost(); } catch (UnknownHostException e) { logger.error("Could not determine my own host", e); } return (new PersistentMemberID(null, localHost, diskDir, name, redundancyProvider.prRegion.getCache().cacheTimeMillis(), (short) 0)); } } /** * Returns a unique Set of persistent members that all the ProxyBucketRegions are waiting for. * * @param offlineOnly true if only the members which are not currently try running should be * returned, false to return all members that this member is waiting for, including * members which are running but not fully initialized. */ private Map<PersistentMemberID, Set<Integer>> getMembersToWaitFor(boolean offlineOnly) { Map<PersistentMemberID, Set<Integer>> waitingForMembers = new HashMap<PersistentMemberID, Set<Integer>>(); for (ProxyBucketRegion proxyBucket : this.bucketRegions) { Integer bucketId = proxyBucket.getBucketId(); // Get the set of missing members from the persistence advisor Set<PersistentMemberID> missingMembers; BucketPersistenceAdvisor persistenceAdvisor = proxyBucket.getPersistenceAdvisor(); if (offlineOnly) { missingMembers = persistenceAdvisor.getMissingMembers(); } else { missingMembers = persistenceAdvisor.getAllMembersToWaitFor(); } if (missingMembers != null) { for (PersistentMemberID missingMember : missingMembers) { Set<Integer> buckets = waitingForMembers.get(missingMember); if (buckets == null) { buckets = new TreeSet<Integer>(); waitingForMembers.put(missingMember, buckets); } buckets.add(bucketId); } } } return waitingForMembers; } /** * Prints a recovery completion message to the log. */ private void logDoneMessage() { this.loggedDoneMessage = true; StartupStatus.startup( LocalizedStrings.CreatePersistentRegionProcessor_DONE_WAITING_FOR_BUCKET_MEMBERS, new Object[] {this.region, TransformUtils.persistentMemberIdToLogEntryTransformer.transform(this.thisMember)}); } /** * Logs a consolidated log entry for all ProxyBucketRegions waiting for persistent members. */ private void logWaitingForMembers() { Map<PersistentMemberID, Set<Integer>> offlineMembers = getMembersToWaitFor(true); Map<PersistentMemberID, Set<Integer>> allMembersToWaitFor = getMembersToWaitFor(false); boolean thereAreBucketsToBeRecovered = (RedundancyLogger.this.allBucketsRecoveredFromDisk.getCount() > 0); /* * Log any offline members the region is waiting for. */ if (thereAreBucketsToBeRecovered && !offlineMembers.isEmpty()) { Set<String> membersToWaitForLogEntries = new HashSet<String>(); TransformUtils.transform(offlineMembers.entrySet(), membersToWaitForLogEntries, TransformUtils.persistentMemberEntryToLogEntryTransformer); Set<Integer> missingBuckets = getAllWaitingBuckets(offlineMembers); StartupStatus.startup( LocalizedStrings.CreatePersistentRegionProcessor_WAITING_FOR_OFFLINE_BUCKET_MEMBERS, new Object[] {this.region, missingBuckets, TransformUtils.persistentMemberIdToLogEntryTransformer.transform(this.thisMember), membersToWaitForLogEntries}); this.loggedDoneMessage = false; } /* * No offline? Then log any online members the region is waiting for. */ else if (thereAreBucketsToBeRecovered && !allMembersToWaitFor.isEmpty()) { Set<String> membersToWaitForLogEntries = new HashSet<String>(); Set<Integer> missingBuckets = getAllWaitingBuckets(allMembersToWaitFor); TransformUtils.transform(allMembersToWaitFor.entrySet(), membersToWaitForLogEntries, TransformUtils.persistentMemberEntryToLogEntryTransformer); StartupStatus.startup( LocalizedStrings.CreatePersistentRegionProcessor_WAITING_FOR_ONLINE_BUCKET_MEMBERS, new Object[] {this.region, missingBuckets, TransformUtils.persistentMemberIdToLogEntryTransformer.transform(this.thisMember), membersToWaitForLogEntries}); this.loggedDoneMessage = false; } /* * No online? Then log that we are done. */ else if (!this.loggedDoneMessage) { logDoneMessage(); } } /** * Get a consolodated set of all buckets that are waiting. */ private Set<Integer> getAllWaitingBuckets( Map<PersistentMemberID, Set<Integer>> offlineMembers) { Set<Integer> allWaitingBuckets = new TreeSet<Integer>(); for (Set<Integer> missingPerMember : offlineMembers.values()) { allWaitingBuckets.addAll(missingPerMember); } return allWaitingBuckets; } } }