diff options
author | Thomas Watson | 2017-09-13 20:59:12 +0000 |
---|---|---|
committer | Andrey Loskutov | 2019-01-09 15:35:49 +0000 |
commit | bbccd4175ebd9df8c3ae41086c51fd8b0a219650 (patch) | |
tree | e955b4b25622a9ca744d17b5519c2d351979eaa6 /bundles | |
parent | 23a1ba9b7bd355860c000fa16e4f8359054e35b6 (diff) | |
download | rt.equinox.framework-bbccd4175ebd9df8c3ae41086c51fd8b0a219650.tar.gz rt.equinox.framework-bbccd4175ebd9df8c3ae41086c51fd8b0a219650.tar.xz rt.equinox.framework-bbccd4175ebd9df8c3ae41086c51fd8b0a219650.zip |
Bug 543305 -Add thread info report when locks cannot be acquired.I20190111-0850
Change-Id: I4f4ef3621837359cd2227347f0b778adf7c41097
Signed-off-by: Thomas Watson <tjwatson@us.ibm.com>
Signed-off-by: Andrey Loskutov <loskutov@gmx.de>
Diffstat (limited to 'bundles')
8 files changed, 238 insertions, 11 deletions
diff --git a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/TestModuleContainer.java b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/TestModuleContainer.java index 24af7a28f..623689639 100644 --- a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/TestModuleContainer.java +++ b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/TestModuleContainer.java @@ -38,8 +38,10 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionHandler; @@ -49,6 +51,7 @@ import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.jar.Attributes; @@ -67,6 +70,7 @@ import org.eclipse.osgi.container.ModuleWire; import org.eclipse.osgi.container.ModuleWiring; import org.eclipse.osgi.container.builders.OSGiManifestBuilderFactory; import org.eclipse.osgi.container.namespaces.EclipsePlatformNamespace; +import org.eclipse.osgi.framework.util.ThreadInfoReport; import org.eclipse.osgi.internal.debug.Debug; import org.eclipse.osgi.internal.framework.EquinoxConfiguration; import org.eclipse.osgi.report.resolution.ResolutionReport; @@ -2188,7 +2192,7 @@ public class TestModuleContainer extends AbstractTest { public void testUTF8LineContinuation() throws BundleException, IOException { DummyContainerAdaptor adaptor = createDummyAdaptor(); ModuleContainer container = adaptor.getContainer(); - String utfString = "a.with.é.multibyte"; + String utfString = "a.with.�.multibyte"; while (utfString.getBytes("UTF8").length < 500) { Map<String, String> manifest = getUTFManifest(utfString); Module testModule = installDummyModule(manifest, manifest.get(Constants.BUNDLE_SYMBOLICNAME), container); @@ -3512,6 +3516,76 @@ public class TestModuleContainer extends AbstractTest { Assert.assertEquals("Wrong bundle-symbolic-name attribute", "org.eclipse.osgi", packages.get(0).getAttributes().get(PackageNamespace.CAPABILITY_BUNDLE_SYMBOLICNAME_ATTRIBUTE)); } + @Test + public void testStartDeadLock() throws BundleException, InterruptedException, IOException { + CountDownLatch startLatch = new CountDownLatch(1); + CountDownLatch stopLatch = new CountDownLatch(1); + + DummyContainerAdaptor adaptor = new DummyContainerAdaptor(new DummyCollisionHook(false), Collections.singletonMap(EquinoxConfiguration.PROP_MODULE_LOCK_TIMEOUT, "1")); + adaptor.setStartLatch(startLatch); + adaptor.setStopLatch(stopLatch); + + ModuleContainer container = adaptor.getContainer(); + + // install the system.bundle + Module systemBundle = installDummyModule("system.bundle.MF", Constants.SYSTEM_BUNDLE_LOCATION, Constants.SYSTEM_BUNDLE_SYMBOLICNAME, null, null, container); + ResolutionReport report = container.resolve(Arrays.asList(systemBundle), true); + Assert.assertNull("Failed to resolve system.bundle.", report.getResolutionException()); + systemBundle.start(); + + // install a module + Map<String, String> manifest = new HashMap<String, String>(); + manifest.put(Constants.BUNDLE_MANIFESTVERSION, "2"); + manifest.put(Constants.BUNDLE_SYMBOLICNAME, "lock.test"); + final Module module = installDummyModule(manifest, manifest.get(Constants.BUNDLE_SYMBOLICNAME), container); + + final ArrayBlockingQueue<BundleException> startExceptions = new ArrayBlockingQueue<BundleException>(2); + Runnable start = new Runnable() { + @Override + public void run() { + try { + module.start(); + } catch (BundleException e) { + startExceptions.offer(e); + } + } + }; + Thread t1 = new Thread(start); + Thread t2 = new Thread(start); + t1.start(); + t2.start(); + + BundleException startError = startExceptions.poll(10, TimeUnit.SECONDS); + startLatch.countDown(); + + Assert.assertEquals("Wrong cause.", TimeoutException.class, startError.getCause().getClass()); + Assert.assertEquals("Wrong cause.", ThreadInfoReport.class, startError.getCause().getCause().getClass()); + startError.printStackTrace(); + + final ArrayBlockingQueue<BundleException> stopExceptions = new ArrayBlockingQueue<BundleException>(2); + Runnable stop = new Runnable() { + @Override + public void run() { + try { + module.stop(); + } catch (BundleException e) { + stopExceptions.offer(e); + } + } + }; + Thread tStop1 = new Thread(stop); + Thread tStop2 = new Thread(stop); + tStop1.start(); + tStop2.start(); + + BundleException stopError = stopExceptions.poll(10, TimeUnit.SECONDS); + startLatch.countDown(); + + Assert.assertEquals("Wrong cause.", TimeoutException.class, stopError.getCause().getClass()); + Assert.assertEquals("Wrong cause.", ThreadInfoReport.class, stopError.getCause().getCause().getClass()); + stopError.printStackTrace(); + } + private static void assertWires(List<ModuleWire> required, List<ModuleWire>... provided) { for (ModuleWire requiredWire : required) { for (List<ModuleWire> providedList : provided) { diff --git a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/dummys/DummyContainerAdaptor.java b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/dummys/DummyContainerAdaptor.java index 5b14a5469..0d4874503 100644 --- a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/dummys/DummyContainerAdaptor.java +++ b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/dummys/DummyContainerAdaptor.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012, 2017 IBM Corporation and others. + * Copyright (c) 2012, 2018 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -15,9 +15,11 @@ package org.eclipse.osgi.tests.container.dummys; import java.util.EnumSet; import java.util.Map; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import org.eclipse.osgi.container.Module; import org.eclipse.osgi.container.Module.Settings; import org.eclipse.osgi.container.ModuleCollisionHook; @@ -31,7 +33,7 @@ import org.osgi.framework.FrameworkListener; import org.osgi.framework.hooks.resolver.ResolverHookFactory; public class DummyContainerAdaptor extends ModuleContainerAdaptor { - private AtomicBoolean slowdownEvents = new AtomicBoolean(false); + private final AtomicBoolean slowdownEvents = new AtomicBoolean(false); private Runnable runForEvents = null; private final ModuleCollisionHook collisionHook; private final Map<String, String> configuration; @@ -39,6 +41,8 @@ public class DummyContainerAdaptor extends ModuleContainerAdaptor { private final ModuleContainer container; private final ResolverHookFactory resolverHookFactory; private final DebugOptions debugOptions; + private final AtomicReference<CountDownLatch> startLatch = new AtomicReference<CountDownLatch>(); + private final AtomicReference<CountDownLatch> stopLatch = new AtomicReference<CountDownLatch>(); private volatile Executor resolverExecutor; private volatile ScheduledExecutorService timeoutExecutor; @@ -59,6 +63,10 @@ public class DummyContainerAdaptor extends ModuleContainerAdaptor { this.container = new ModuleContainer(this, moduleDatabase); } + public void setConfiguration(String key, String value) { + this.configuration.put(key, value); + } + @Override public ModuleCollisionHook getModuleCollisionHook() { return collisionHook; @@ -82,7 +90,7 @@ public class DummyContainerAdaptor extends ModuleContainerAdaptor { @Override public Module createModule(String location, long id, EnumSet<Settings> settings, int startlevel) { - return new DummyModule(id, location, container, settings, startlevel); + return new DummyModule(id, location, container, settings, startlevel, startLatch.get(), stopLatch.get()); } @Override @@ -150,4 +158,12 @@ public class DummyContainerAdaptor extends ModuleContainerAdaptor { return this.timeoutExecutor; } + public void setStartLatch(CountDownLatch startLatch) { + this.startLatch.set(startLatch); + } + + public void setStopLatch(CountDownLatch stopLatch) { + this.stopLatch.set(stopLatch); + } + } diff --git a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/dummys/DummyModule.java b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/dummys/DummyModule.java index 08fec38e0..c63cebd22 100644 --- a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/dummys/DummyModule.java +++ b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/dummys/DummyModule.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012, 2017 IBM Corporation and others. + * Copyright (c) 2012, 2018 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -14,15 +14,25 @@ package org.eclipse.osgi.tests.container.dummys; import java.util.EnumSet; +import java.util.concurrent.CountDownLatch; import org.eclipse.osgi.container.Module; import org.eclipse.osgi.container.ModuleContainer; import org.eclipse.osgi.container.ModuleRevision; import org.osgi.framework.Bundle; +import org.osgi.framework.BundleException; public class DummyModule extends Module { + private final CountDownLatch startLatch; + private final CountDownLatch stopLatch; public DummyModule(Long id, String location, ModuleContainer container, EnumSet<Settings> settings, int startlevel) { + this(id, location, container, settings, startlevel, null, null); + } + + public DummyModule(Long id, String location, ModuleContainer container, EnumSet<Settings> settings, int startlevel, CountDownLatch startLatch, CountDownLatch stopLatch) { super(id, location, container, settings, startlevel); + this.startLatch = startLatch == null ? new CountDownLatch(0) : startLatch; + this.stopLatch = stopLatch == null ? new CountDownLatch(0) : stopLatch; } @Override @@ -35,4 +45,25 @@ public class DummyModule extends Module { // Do nothing } + @Override + protected void startWorker() throws BundleException { + try { + startLatch.await(); + } catch (InterruptedException e) { + Thread.interrupted(); + throw new BundleException("Interrupted.", e); + } + super.startWorker(); + } + + @Override + protected void stopWorker() throws BundleException { + try { + stopLatch.await(); + } catch (InterruptedException e) { + Thread.interrupted(); + throw new BundleException("Interrupted.", e); + } + super.stopWorker(); + } } diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/Module.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/Module.java index 480e24155..86d902e07 100644 --- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/Module.java +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/Module.java @@ -20,6 +20,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.osgi.container.ModuleContainerAdaptor.ModuleEvent; +import org.eclipse.osgi.framework.util.ThreadInfoReport; import org.eclipse.osgi.internal.container.EquinoxReentrantLock; import org.eclipse.osgi.internal.debug.Debug; import org.eclipse.osgi.internal.messages.Msg; @@ -338,7 +339,7 @@ public abstract class Module implements BundleReference, BundleStartLevel, Compa if (invalid) { cause = new IllegalStateException(NLS.bind(Msg.Module_LockStateError, transitionEvent, currentTransition)); } else { - cause = new TimeoutException(NLS.bind(Msg.Module_LockTimeout, revisions.getContainer().getModuleLockTimeout())); + cause = new TimeoutException(NLS.bind(Msg.Module_LockTimeout, revisions.getContainer().getModuleLockTimeout())).initCause(new ThreadInfoReport(stateChangeLock.toString())); } String exceptonInfo = toString() + ' ' + transitionEvent + ' ' + currentTransition; throw new BundleException(Msg.Module_LockError + exceptonInfo, BundleException.STATECHANGE_ERROR, cause); diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleContainer.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleContainer.java index 89420d8ba..4d84638af 100644 --- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleContainer.java +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleContainer.java @@ -46,6 +46,7 @@ import org.eclipse.osgi.framework.eventmgr.EventDispatcher; import org.eclipse.osgi.framework.eventmgr.EventManager; import org.eclipse.osgi.framework.eventmgr.ListenerQueue; import org.eclipse.osgi.framework.util.SecureAction; +import org.eclipse.osgi.framework.util.ThreadInfoReport; import org.eclipse.osgi.internal.container.InternalUtils; import org.eclipse.osgi.internal.container.LockSet; import org.eclipse.osgi.internal.debug.Debug; @@ -248,10 +249,10 @@ public final class ModuleContainer implements DebugOptionsListener { locationLocked = locationLocks.tryLock(location, 5, TimeUnit.SECONDS); nameLocked = name != null && nameLocks.tryLock(name, 5, TimeUnit.SECONDS); if (!locationLocked) { - throw new BundleException("Failed to obtain location lock for installation: " + location, BundleException.STATECHANGE_ERROR); //$NON-NLS-1$ + throw new BundleException("Failed to obtain location lock for installation: " + location, BundleException.STATECHANGE_ERROR, new ThreadInfoReport(locationLocks.getLockInfo(location))); //$NON-NLS-1$ } if (name != null && !nameLocked) { - throw new BundleException("Failed to obtain symbolic name lock for installation: " + name, BundleException.STATECHANGE_ERROR); //$NON-NLS-1$ + throw new BundleException("Failed to obtain symbolic name lock for installation: " + name, BundleException.STATECHANGE_ERROR, new ThreadInfoReport(nameLocks.getLockInfo(name))); //$NON-NLS-1$ } } catch (InterruptedException e) { Thread.currentThread().interrupt(); @@ -341,7 +342,7 @@ public final class ModuleContainer implements DebugOptionsListener { // Attempt to lock the name try { if (name != null && !(nameLocked = nameLocks.tryLock(name, 5, TimeUnit.SECONDS))) { - throw new BundleException("Failed to obtain id locks for installation.", BundleException.STATECHANGE_ERROR); //$NON-NLS-1$ + throw new BundleException("Failed to obtain id locks for installation.", BundleException.STATECHANGE_ERROR, new ThreadInfoReport(nameLocks.getLockInfo(name))); //$NON-NLS-1$ } } catch (InterruptedException e) { Thread.currentThread().interrupt(); diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/util/ThreadInfoReport.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/util/ThreadInfoReport.java new file mode 100644 index 000000000..89e8e5d59 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/util/ThreadInfoReport.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2019 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.osgi.framework.util; + +import java.lang.management.LockInfo; +import java.lang.management.ManagementFactory; +import java.lang.management.MonitorInfo; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; + +public class ThreadInfoReport extends RuntimeException { + private static final long serialVersionUID = 1L; + + public ThreadInfoReport(String failedMonitor) { + super(getThreadDump(failedMonitor)); + } + + public static String getThreadDump(String failedMonitor) { + long currentId = Thread.currentThread().getId(); + ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); + StringBuilder dump = new StringBuilder("Thread dump"); //$NON-NLS-1$ + ThreadInfo[] infos = threadMXBean.dumpAllThreads(threadMXBean.isObjectMonitorUsageSupported(), threadMXBean.isSynchronizerUsageSupported()); + for (ThreadInfo info : infos) { + dumpThreadIDNameState(info, dump); + dumpLockInfo(currentId, failedMonitor, info, dump); + dumpStackTrace(info, dump); + } + return dump.toString(); + } + + private static void dumpThreadIDNameState(ThreadInfo info, StringBuilder dump) { + dump.append('\n').append('\n'); + dump.append("ThreadId: ").append(info.getThreadId()); //$NON-NLS-1$ + dump.append(" ThreadName: ").append(info.getThreadName()); //$NON-NLS-1$ + dump.append(" ThreadState: ").append(info.getThreadState()); //$NON-NLS-1$ + } + + private static void dumpLockInfo(long currentId, String failedMonitor, ThreadInfo info, StringBuilder dump) { + dump.append('\n'); + dump.append(" Blocked On: "); //$NON-NLS-1$ + LockInfo blockedOn = info.getLockInfo(); + if (blockedOn == null) { + if (currentId == info.getThreadId() && failedMonitor != null) { + dump.append(failedMonitor); + } else { + dump.append("none"); //$NON-NLS-1$ + } + } else { + dump.append(blockedOn.toString()); + dump.append(" LockOwnerId: ").append(info.getLockOwnerId()); //$NON-NLS-1$ + dump.append(" LockOwnerName: ").append(info.getLockOwnerName()); //$NON-NLS-1$ + } + dump.append('\n'); + + dump.append(" Synchronizers Locked: "); //$NON-NLS-1$ + LockInfo[] synchronizers = info.getLockedSynchronizers(); + if (synchronizers.length == 0) { + dump.append("none"); //$NON-NLS-1$ + } else { + for (LockInfo sync : synchronizers) { + dump.append('\n'); + dump.append(" ").append(sync.toString()); //$NON-NLS-1$ + } + } + dump.append('\n'); + + dump.append(" Monitors Locked: "); //$NON-NLS-1$ + MonitorInfo[] monitors = info.getLockedMonitors(); + if (monitors.length == 0) { + dump.append("none"); //$NON-NLS-1$ + } + for (MonitorInfo monitor : monitors) { + dump.append('\n'); + dump.append(" ").append(monitor.toString()); //$NON-NLS-1$ + } + dump.append('\n'); + } + + private static void dumpStackTrace(ThreadInfo info, StringBuilder dump) { + dump.append(" Stack Trace: "); //$NON-NLS-1$ + for (StackTraceElement e : info.getStackTrace()) { + dump.append('\n').append(" ").append(e); //$NON-NLS-1$ + } + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/LockSet.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/LockSet.java index 79617b1a3..5b751e34a 100644 --- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/LockSet.java +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/LockSet.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012, 2016 IBM Corporation and others. + * Copyright (c) 2012, 2018 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -51,6 +51,10 @@ public class LockSet<T> { void unlock() { lock.unlock(); } + + public String toString() { + return lock.toString(); + } } private final Map<T, LockHolder> locks = new HashMap<>(); @@ -101,4 +105,10 @@ public class LockSet<T> { } } } + + public String getLockInfo(T t) { + synchronized (locks) { + return String.valueOf(locks.get(t)); + } + } } diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/BundleInfo.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/BundleInfo.java index 840a33a8f..fd100cd18 100644 --- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/BundleInfo.java +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/BundleInfo.java @@ -37,6 +37,7 @@ import org.eclipse.osgi.container.ModuleRevision; import org.eclipse.osgi.container.ModuleRevisionBuilder; import org.eclipse.osgi.framework.log.FrameworkLogEntry; import org.eclipse.osgi.framework.util.CaseInsensitiveDictionaryMap; +import org.eclipse.osgi.framework.util.ThreadInfoReport; import org.eclipse.osgi.internal.container.LockSet; import org.eclipse.osgi.internal.debug.Debug; import org.eclipse.osgi.internal.framework.EquinoxConfiguration; @@ -471,7 +472,7 @@ public final class BundleInfo { throw new BundleException("Failed to obtain id locks for generation.", BundleException.STATECHANGE_ERROR, e); //$NON-NLS-1$ } if (!lockedID) { - throw new BundleException("Failed to obtain id locks for generation.", BundleException.STATECHANGE_ERROR); //$NON-NLS-1$ + throw new BundleException("Failed to obtain id locks for generation.", BundleException.STATECHANGE_ERROR, new ThreadInfoReport(generationLocks.getLockInfo(nextGenerationId))); //$NON-NLS-1$ } Generation newGeneration = new Generation(nextGenerationId++); return newGeneration; |