/* * Copyright (c) 2016 Couchbase, 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.couchbase.client.core; import com.couchbase.client.core.env.CoreEnvironment; import com.couchbase.client.core.env.DefaultCoreEnvironment; import com.couchbase.client.core.logging.CouchbaseLogger; import com.couchbase.client.core.logging.CouchbaseLoggerFactory; import com.couchbase.client.core.message.cluster.DisconnectRequest; import com.couchbase.client.core.message.cluster.OpenBucketRequest; import com.couchbase.client.core.message.cluster.OpenBucketResponse; import com.couchbase.client.core.message.cluster.SeedNodesRequest; import com.couchbase.client.core.message.cluster.SeedNodesResponse; import com.couchbase.client.core.util.TestProperties; import org.junit.Test; import rx.Observable; import rx.functions.Func1; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import static org.junit.Assert.assertTrue; /** * A test case that validates shutdown behavior of the SDK under "real use" conditions. * Aim is to detect potential problems with threading cleanup in containers like Tomcat. * * This test case doesn't extend ClusterDependentTest so that the connection and shutdown of the cluster * can be triggered at the right time for the tests. * * @author Simon Baslé * @since 1.2 */ public class ThreadCleanupTest { private static final CouchbaseLogger LOGGER = CouchbaseLoggerFactory.getInstance(ThreadCleanupTest.class); private static final String seedNode = TestProperties.seedNode(); private static final String bucket = TestProperties.bucket(); private static final String username = TestProperties.username(); private static final String password = TestProperties.password(); private CoreEnvironment env; private ClusterFacade cluster; public void connect() { env = DefaultCoreEnvironment .builder() .dcpEnabled(true) .mutationTokensEnabled(true) .build(); cluster = new CouchbaseCore(env); cluster.<SeedNodesResponse>send(new SeedNodesRequest(seedNode)).flatMap( new Func1<SeedNodesResponse, Observable<OpenBucketResponse>>() { @Override public Observable<OpenBucketResponse> call(SeedNodesResponse response) { return cluster.send(new OpenBucketRequest(bucket, username, password)); } } ).toBlocking().single(); } public void disconnect() throws InterruptedException { cluster.send(new DisconnectRequest()).toBlocking().first(); } @Test public void testSdkNettyRxJavaThreadsShutdownProperly() throws InterruptedException { //FIXME differently improve the tolerance of this test to threads spawned by others Thread.sleep(500); ThreadMXBean mx = ManagementFactory.getThreadMXBean(); LOGGER.info("Threads at start"); Set<String> ignore = dump(threads(mx)); connect(); LOGGER.info("Threads before shutdown:"); Set<String> beforeShutdown = dump(threads(mx, ignore, false)); LOGGER.info(""); LOGGER.info("Shutting Down Couchbase Cluster"); disconnect(); LOGGER.info("Shutting Down Couchbase Env"); boolean hasShutdown = env.shutdownAsync().toBlocking().single(); //TODO also test RxJava Schedulers.shutdown here once we depend on 1.0.15+ LOGGER.info("Shutting Down RxJava should be implemented here"); LOGGER.info(""); LOGGER.info("Threads after shutdown:"); Set<String> afterShutdown = dump(threads(mx, ignore, true)); boolean hasCleanedUpThreads = Collections.disjoint(afterShutdown, beforeShutdown); if (hasCleanedUpThreads) LOGGER.info("All relevant threads properly cleaned up after shutdown"); //TODO when shutdown RxJava is implemented, RxJava should also be restarted here //TODO move this test case to the rx.schedulers package to do that assertTrue("Some threads created by the SDK remained after shutdown", hasCleanedUpThreads); assertTrue("env.shutdown() returned false", hasShutdown); } private Set<String> dump(Set<String> threads) { for (String thread : threads) { LOGGER.info(thread); } return threads; } private Set<String> threads(ThreadMXBean mx, Set<String> ignore, boolean shouldIgnoreRxJavaThreads) { Set<String> all = threads(mx); all.removeAll(ignore); if (shouldIgnoreRxJavaThreads) { for (Iterator<String> iterator = all.iterator(); iterator.hasNext(); ) { String next = iterator.next(); if (next.startsWith("Rx")) iterator.remove(); } } return all; } private Set<String> threads(ThreadMXBean mx) { ThreadInfo[] dump = mx.getThreadInfo(mx.getAllThreadIds()); Set<String> names = new HashSet<String>(dump.length); for (ThreadInfo threadInfo : dump) { if (threadInfo == null || threadInfo.getThreadName() == null) { continue; } names.add(threadInfo.getThreadName()); } return names; } }