Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dfc3a6ba authored by Lee Shombert's avatar Lee Shombert Committed by Android (Google) Code Review
Browse files

Merge "Clean up PropertyInvalidatedCache for SystemApi"

parents a0b8e664 80f4322b
Loading
Loading
Loading
Loading
+2 −4
Original line number Diff line number Diff line
@@ -1646,7 +1646,7 @@ public final class ActivityThread extends ClientTransactionHandler

        @Override
        public void dumpCacheInfo(ParcelFileDescriptor pfd, String[] args) {
            PropertyInvalidatedCache.dumpCacheInfo(pfd.getFileDescriptor(), args);
            PropertyInvalidatedCache.dumpCacheInfo(pfd, args);
            IoUtils.closeQuietly(pfd);
        }

@@ -6391,9 +6391,7 @@ public final class ActivityThread extends ClientTransactionHandler
        if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level);

        if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
            for (PropertyInvalidatedCache pic : PropertyInvalidatedCache.getActiveCaches()) {
                pic.clear();
            }
            PropertyInvalidatedCache.onTrimMemory();
        }

        final ArrayList<ComponentCallbacks2> callbacks =
+73 −35
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.text.TextUtils;
@@ -29,7 +30,6 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FastPrintWriter;

import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
@@ -355,11 +355,12 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
    private final int mMaxEntries;

    /**
     * Make a new property invalidated cache.
     * Make a new property invalidated cache.  This constructor names the cache after the
     * property name.  New clients should prefer the constructor that takes an explicit
     * cache name.
     *
     * @param maxEntries Maximum number of entries to cache; LRU discard
     * @param propertyName Name of the system property holding the cache invalidation nonce
     * Defaults the cache name to the property name.
     * @param propertyName Name of the system property holding the cache invalidation nonce.
     */
    public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName) {
        this(maxEntries, propertyName, propertyName);
@@ -418,7 +419,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
     * Enable or disable testing.  The testing property map is cleared every time this
     * method is called.
     */
    @VisibleForTesting
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public static void setTestMode(boolean mode) {
        sTesting = mode;
        synchronized (sTestingPropertyMap) {
@@ -430,8 +431,8 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
     * Enable testing the specific cache key.  Only keys in the map are subject to testing.
     * There is no method to stop testing a property name.  Just disable the test mode.
     */
    @VisibleForTesting
    public static void testPropertyName(String name) {
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public static void testPropertyName(@NonNull String name) {
        synchronized (sTestingPropertyMap) {
            sTestingPropertyMap.put(name, (long) NONCE_UNSET);
        }
@@ -505,21 +506,23 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
     * block. If this function returns null, the result of the cache query is null. There is no
     * "negative cache" in the query: we don't cache null results at all.
     */
    public abstract Result recompute(Query query);
    public abstract @NonNull Result recompute(@NonNull Query query);

    /**
     * Return true if the query should bypass the cache.  The default behavior is to
     * always use the cache but the method can be overridden for a specific class.
     */
    public boolean bypass(Query query) {
    public boolean bypass(@NonNull Query query) {
        return false;
    }

    /**
     * Determines if a pair of responses are considered equal. Used to determine whether
     * a cache is inadvertently returning stale results when VERIFY is set to true.
     * Determines if a pair of responses are considered equal. Used to determine whether a
     * cache is inadvertently returning stale results when VERIFY is set to true.  Some
     * existing clients override this method, but it is now deprecated in favor of a valid
     * equals() method on the Result class.
     */
    protected boolean resultEquals(Result cachedResult, Result fetchedResult) {
    public boolean resultEquals(Result cachedResult, Result fetchedResult) {
        // If a service crashes and returns a null result, the cached value remains valid.
        if (fetchedResult != null) {
            return Objects.equals(cachedResult, fetchedResult);
@@ -544,8 +547,11 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
    }

    /**
     * Disable the use of this cache in this process.
     * Disable the use of this cache in this process.  This method is using during
     * testing.  To disable a cache in normal code, use disableLocal().
     * @hide
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public final void disableInstance() {
        synchronized (mLock) {
            mDisabled = true;
@@ -558,7 +564,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
     * using the key will be disabled now, and all future cache instances that use the key will be
     * disabled in their constructor.
     */
    public static final void disableLocal(@NonNull String name) {
    private static final void disableLocal(@NonNull String name) {
        synchronized (sCorkLock) {
            sDisabledKeys.add(name);
            for (PropertyInvalidatedCache cache : sCaches.keySet()) {
@@ -569,8 +575,22 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
        }
    }

    /**
     * Stop disabling local caches with a particular name.  Any caches that are currently
     * disabled remain disabled (the "disabled" setting is sticky).  However, new caches
     * with this name will not be disabled.  It is not an error if the cache name is not
     * found in the list of disabled caches.
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public final void clearDisableLocal() {
        synchronized (sCorkLock) {
            sDisabledKeys.remove(mCacheName);
        }
    }

    /**
     * Disable this cache in the current process, and all other caches that use the same
     * name.  This does not affect caches that have a different name but use the same
     * property.
     */
    public final void disableLocal() {
@@ -580,6 +600,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
    /**
     * Return whether the cache is disabled in this process.
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public final boolean isDisabledLocal() {
        return mDisabled || !sEnabled;
    }
@@ -587,7 +608,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
    /**
     * Get a value from the cache or recompute it.
     */
    public Result query(Query query) {
    public @NonNull Result query(@NonNull Query query) {
        // Let access to mDisabled race: it's atomic anyway.
        long currentNonce = (!isDisabledLocal()) ? getCurrentNonce() : NONCE_DISABLED;
        if (bypass(query)) {
@@ -704,6 +725,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
     * the cache objects to invalidate all of the cache objects becomes confusing and you should
     * just use the static version of this function.
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public final void disableSystemWide() {
        disableSystemWide(mPropertyName);
    }
@@ -714,7 +736,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
     *
     * @param name Name of the cache-key property to invalidate
     */
    public static void disableSystemWide(@NonNull String name) {
    private static void disableSystemWide(@NonNull String name) {
        if (!sEnabled) {
            return;
        }
@@ -784,8 +806,8 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
                    "invalidating cache [%s]: [%s] -> [%s]",
                    name, nonce, Long.toString(newValue)));
        }
        // TODO(dancol): add an atomic compare and exchange property set operation to avoid a
        // small race with concurrent disable here.
        // There is a small race with concurrent disables here.  A compare-and-exchange
        // property operation would be required to eliminate the race condition.
        setNonce(name, newValue);
        long invalidateCount = sInvalidates.getOrDefault(name, (long) 0);
        sInvalidates.put(name, ++invalidateCount);
@@ -990,6 +1012,10 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
        }
    }

    /**
     * Return the result generated by a given query to the cache, performing debugging checks when
     * enabled.
     */
    private Result maybeCheckConsistency(Query query, Result proposedResult) {
        if (VERIFY) {
            Result resultToCompare = recompute(query);
@@ -1007,24 +1033,27 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
    }

    /**
     * Return the name of the cache, to be used in debug messages.  The
     * method is public so clients can use it.
     * Return the name of the cache, to be used in debug messages.
     */
    public String cacheName() {
    private final @NonNull String cacheName() {
        return mCacheName;
    }

    /**
     * Return the query as a string, to be used in debug messages.  The
     * method is public so clients can use it in external debug messages.
     * Return the query as a string, to be used in debug messages.  New clients should not
     * override this, but should instead add the necessary toString() method to the Query
     * class.
     */
    public String queryToString(Query query) {
    protected @NonNull String queryToString(@NonNull Query query) {
        return Objects.toString(query);
    }

    /**
     * Disable all caches in the local process.  Once disabled it is not
     * possible to re-enable caching in the current process.
     * Disable all caches in the local process.  This is primarily useful for testing when
     * the test needs to bypass the cache or when the test is for a server, and the test
     * process does not have privileges to write SystemProperties. Once disabled it is not
     * possible to re-enable caching in the current process.  If a client wants to
     * temporarily disable caching, use the corking mechanism.
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public static void disableForTestMode() {
@@ -1044,7 +1073,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
    /**
     * Returns a list of caches alive at the current time.
     */
    public static ArrayList<PropertyInvalidatedCache> getActiveCaches() {
    private static @NonNull ArrayList<PropertyInvalidatedCache> getActiveCaches() {
        synchronized (sCorkLock) {
            return new ArrayList<PropertyInvalidatedCache>(sCaches.keySet());
        }
@@ -1053,7 +1082,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
    /**
     * Returns a list of the active corks in a process.
     */
    public static ArrayList<Map.Entry<String, Integer>> getActiveCorks() {
    private static @NonNull ArrayList<Map.Entry<String, Integer>> getActiveCorks() {
        synchronized (sCorkLock) {
            return new ArrayList<Map.Entry<String, Integer>>(sCorks.entrySet());
        }
@@ -1104,14 +1133,14 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
    }

    /**
     * Dumps contents of every cache in the process to the provided FileDescriptor.
     * Dumps the contents of every cache in the process to the provided ParcelFileDescriptor.
     */
    public static void dumpCacheInfo(FileDescriptor fd, String[] args) {
    public static void dumpCacheInfo(@NonNull ParcelFileDescriptor pfd, @NonNull String[] args) {
        ArrayList<PropertyInvalidatedCache> activeCaches;
        ArrayList<Map.Entry<String, Integer>> activeCorks;

        try  (
            FileOutputStream fout = new FileOutputStream(fd);
            FileOutputStream fout = new FileOutputStream(pfd.getFileDescriptor());
            PrintWriter pw = new FastPrintWriter(fout);
        ) {
            if (!sEnabled) {
@@ -1142,4 +1171,13 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
            Log.e(TAG, "Failed to dump PropertyInvalidatedCache instances");
        }
    }

    /**
     * Trim memory by clearing all the caches.
     */
    public static void onTrimMemory() {
        for (PropertyInvalidatedCache pic : getActiveCaches()) {
            pic.clear();
        }
    }
}
+165 −2
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@
package android.app;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;

import androidx.test.filters.SmallTest;

@@ -25,7 +27,9 @@ import org.junit.Test;

/**
 * Test for verifying the behavior of {@link PropertyInvalidatedCache}.  This test does
 * not use any actual binder calls - it is entirely self-contained.
 * not use any actual binder calls - it is entirely self-contained.  This test also relies
 * on the test mode of {@link PropertyInvalidatedCache} because Android SELinux rules do
 * not grant test processes the permission to set system properties.
 * <p>
 * Build/Install/Run:
 *  atest FrameworksCoreTests:PropertyInvalidatedCacheTests
@@ -33,6 +37,8 @@ import org.junit.Test;
@SmallTest
public class PropertyInvalidatedCacheTests {

    // This property is never set.  The test process does not have permission to set any
    // properties.
    static final String CACHE_PROPERTY = "cache_key.cache_test_a";

    // This class is a proxy for binder calls.  It contains a counter that increments
@@ -58,7 +64,8 @@ public class PropertyInvalidatedCacheTests {
        }
    }

    // Clear the test mode after every test, in case this process is used for other tests.
    // Clear the test mode after every test, in case this process is used for other
    // tests. This also resets the test property map.
    @After
    public void tearDown() throws Exception {
        PropertyInvalidatedCache.setTestMode(false);
@@ -176,5 +183,161 @@ public class PropertyInvalidatedCacheTests {
                    }
                };
        assertEquals(true, cache1.getDisabledState());

        // Remove the record of caches being locally disabled.  This is a clean-up step.
        cache1.clearDisableLocal();
        assertEquals(true, cache1.getDisabledState());
        assertEquals(true, cache2.getDisabledState());
        assertEquals(false, cache3.getDisabledState());

        // Create a new cache1.  Verify that the new instance is not disabled.
        cache1 = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) {
                    @Override
                    public Boolean recompute(Integer x) {
                        return tester.query(x);
                    }
                };
        assertEquals(false, cache1.getDisabledState());
    }

    private static final String UNSET_KEY = "Aiw7woh6ie4toh7W";

    private static class TestCache extends PropertyInvalidatedCache<Integer, String> {
        TestCache() {
            this(CACHE_PROPERTY);
        }

        TestCache(String key) {
            super(4, key);
            setTestMode(true);
            testPropertyName(key);
        }

        @Override
        public String recompute(Integer qv) {
            mRecomputeCount += 1;
            return "foo" + qv.toString();
        }

        int getRecomputeCount() {
            return mRecomputeCount;
        }

        private int mRecomputeCount = 0;
    }

    @Test
    public void testCacheRecompute() {
        TestCache cache = new TestCache();
        cache.invalidateCache();
        assertEquals(cache.isDisabledLocal(), false);
        assertEquals("foo5", cache.query(5));
        assertEquals(1, cache.getRecomputeCount());
        assertEquals("foo5", cache.query(5));
        assertEquals(1, cache.getRecomputeCount());
        assertEquals("foo6", cache.query(6));
        assertEquals(2, cache.getRecomputeCount());
        cache.invalidateCache();
        assertEquals("foo5", cache.query(5));
        assertEquals("foo5", cache.query(5));
        assertEquals(3, cache.getRecomputeCount());
    }

    @Test
    public void testCacheInitialState() {
        TestCache cache = new TestCache();
        assertEquals("foo5", cache.query(5));
        assertEquals("foo5", cache.query(5));
        assertEquals(2, cache.getRecomputeCount());
        cache.invalidateCache();
        assertEquals("foo5", cache.query(5));
        assertEquals("foo5", cache.query(5));
        assertEquals(3, cache.getRecomputeCount());
    }

    @Test
    public void testCachePropertyUnset() {
        TestCache cache = new TestCache(UNSET_KEY);
        assertEquals("foo5", cache.query(5));
        assertEquals("foo5", cache.query(5));
        assertEquals(2, cache.getRecomputeCount());
    }

    @Test
    public void testCacheDisableState() {
        TestCache cache = new TestCache();
        assertEquals("foo5", cache.query(5));
        assertEquals("foo5", cache.query(5));
        assertEquals(2, cache.getRecomputeCount());
        cache.invalidateCache();
        assertEquals("foo5", cache.query(5));
        assertEquals("foo5", cache.query(5));
        assertEquals(3, cache.getRecomputeCount());
        cache.disableSystemWide();
        assertEquals("foo5", cache.query(5));
        assertEquals("foo5", cache.query(5));
        assertEquals(5, cache.getRecomputeCount());
        cache.invalidateCache();  // Should not reenable
        assertEquals("foo5", cache.query(5));
        assertEquals("foo5", cache.query(5));
        assertEquals(7, cache.getRecomputeCount());
    }

    @Test
    public void testRefreshSameObject() {
        int[] refreshCount = new int[1];
        TestCache cache = new TestCache() {
            @Override
            public String refresh(String oldResult, Integer query) {
                refreshCount[0] += 1;
                return oldResult;
            }
        };
        cache.invalidateCache();
        String result1 = cache.query(5);
        assertEquals("foo5", result1);
        String result2 = cache.query(5);
        assertSame(result1, result2);
        assertEquals(1, cache.getRecomputeCount());
        assertEquals(1, refreshCount[0]);
        assertEquals("foo5", cache.query(5));
        assertEquals(2, refreshCount[0]);
    }

    @Test
    public void testRefreshInvalidateRace() {
        int[] refreshCount = new int[1];
        TestCache cache = new TestCache() {
            @Override
            public String refresh(String oldResult, Integer query) {
                refreshCount[0] += 1;
                invalidateCache();
                return new String(oldResult);
            }
        };
        cache.invalidateCache();
        String result1 = cache.query(5);
        assertEquals("foo5", result1);
        String result2 = cache.query(5);
        assertEquals(result1, result2);
        assertNotSame(result1, result2);
        assertEquals(2, cache.getRecomputeCount());
    }

    @Test
    public void testLocalProcessDisable() {
        TestCache cache = new TestCache();
        assertEquals(cache.isDisabledLocal(), false);
        cache.invalidateCache();
        assertEquals("foo5", cache.query(5));
        assertEquals(1, cache.getRecomputeCount());
        assertEquals("foo5", cache.query(5));
        assertEquals(1, cache.getRecomputeCount());
        assertEquals(cache.isDisabledLocal(), false);
        cache.disableLocal();
        assertEquals(cache.isDisabledLocal(), true);
        assertEquals("foo5", cache.query(5));
        assertEquals("foo5", cache.query(5));
        assertEquals(3, cache.getRecomputeCount());
    }
}
+0 −168
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * 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 android.os;

import android.app.PropertyInvalidatedCache;
import android.test.suitebuilder.annotation.SmallTest;

import junit.framework.TestCase;

public class PropertyInvalidatedCacheTest extends TestCase {
    private static final String KEY = "sys.testkey";
    private static final String UNSET_KEY = "Aiw7woh6ie4toh7W";

    private static class TestCache extends PropertyInvalidatedCache<Integer, String> {
        TestCache() {
            this(KEY);
        }

        TestCache(String key) {
            super(4, key);
        }

        @Override
        public String recompute(Integer qv) {
            mRecomputeCount += 1;
            return "foo" + qv.toString();
        }

        int getRecomputeCount() {
            return mRecomputeCount;
        }

        private int mRecomputeCount = 0;
    }

    @Override
    protected void setUp() {
        SystemProperties.set(KEY, "");
    }

    @SmallTest
    public void testCacheRecompute() throws Exception {
        TestCache cache = new TestCache();
        cache.invalidateCache();
        assertEquals("foo5", cache.query(5));
        assertEquals(1, cache.getRecomputeCount());
        assertEquals("foo5", cache.query(5));
        assertEquals(1, cache.getRecomputeCount());
        assertEquals("foo6", cache.query(6));
        assertEquals(2, cache.getRecomputeCount());
        cache.invalidateCache();
        assertEquals("foo5", cache.query(5));
        assertEquals("foo5", cache.query(5));
        assertEquals(3, cache.getRecomputeCount());
    }

    @SmallTest
    public void testCacheInitialState() throws Exception {
        TestCache cache = new TestCache();
        assertEquals("foo5", cache.query(5));
        assertEquals("foo5", cache.query(5));
        assertEquals(2, cache.getRecomputeCount());
        cache.invalidateCache();
        assertEquals("foo5", cache.query(5));
        assertEquals("foo5", cache.query(5));
        assertEquals(3, cache.getRecomputeCount());
    }

    @SmallTest
    public void testCachePropertyUnset() throws Exception {
        TestCache cache = new TestCache(UNSET_KEY);
        assertEquals("foo5", cache.query(5));
        assertEquals("foo5", cache.query(5));
        assertEquals(2, cache.getRecomputeCount());
    }

    @SmallTest
    public void testCacheDisableState() throws Exception {
        TestCache cache = new TestCache();
        assertEquals("foo5", cache.query(5));
        assertEquals("foo5", cache.query(5));
        assertEquals(2, cache.getRecomputeCount());
        cache.invalidateCache();
        assertEquals("foo5", cache.query(5));
        assertEquals("foo5", cache.query(5));
        assertEquals(3, cache.getRecomputeCount());
        cache.disableSystemWide();
        assertEquals("foo5", cache.query(5));
        assertEquals("foo5", cache.query(5));
        assertEquals(5, cache.getRecomputeCount());
        cache.invalidateCache();  // Should not reenable
        assertEquals("foo5", cache.query(5));
        assertEquals("foo5", cache.query(5));
        assertEquals(7, cache.getRecomputeCount());
    }

    @SmallTest
    public void testRefreshSameObject() throws Exception {
        int[] refreshCount = new int[1];
        TestCache cache = new TestCache() {
            @Override
            protected String refresh(String oldResult, Integer query) {
                refreshCount[0] += 1;
                return oldResult;
            }
        };
        cache.invalidateCache();
        String result1 = cache.query(5);
        assertEquals("foo5", result1);
        String result2 = cache.query(5);
        assertSame(result1, result2);
        assertEquals(1, cache.getRecomputeCount());
        assertEquals(1, refreshCount[0]);
        assertEquals("foo5", cache.query(5));
        assertEquals(2, refreshCount[0]);
    }

    @SmallTest
    public void testRefreshInvalidateRace() throws Exception {
        int[] refreshCount = new int[1];
        TestCache cache = new TestCache() {
            @Override
            protected String refresh(String oldResult, Integer query) {
                refreshCount[0] += 1;
                invalidateCache();
                return new String(oldResult);
            }
        };
        cache.invalidateCache();
        String result1 = cache.query(5);
        assertEquals("foo5", result1);
        String result2 = cache.query(5);
        assertEquals(result1, result2);
        assertNotSame(result1, result2);
        assertEquals(2, cache.getRecomputeCount());
    }

    @SmallTest
    public void testLocalProcessDisable() throws Exception {
        TestCache cache = new TestCache();
        cache.invalidateCache();
        assertEquals("foo5", cache.query(5));
        assertEquals(1, cache.getRecomputeCount());
        assertEquals("foo5", cache.query(5));
        assertEquals(1, cache.getRecomputeCount());
        assertEquals(cache.isDisabledLocal(), false);
        cache.disableLocal();
        assertEquals(cache.isDisabledLocal(), true);
        assertEquals("foo5", cache.query(5));
        assertEquals("foo5", cache.query(5));
        assertEquals(3, cache.getRecomputeCount());
    }

}
+0 −4
Original line number Diff line number Diff line
@@ -10360,10 +10360,6 @@ public class ActivityManagerService extends IActivityManager.Stub
            if (thread != null) {
                pw.println("\n\n** Cache info for pid " + pid + " [" + r.processName + "] **");
                pw.flush();
                if (pid == MY_PID) {
                    PropertyInvalidatedCache.dumpCacheInfo(fd, args);
                    continue;
                }
                try {
                    TransferPipe tp = new TransferPipe();
                    try {