From b2776cefa4c43d0757bc4f851c12aef6b0a4e5e1 Mon Sep 17 00:00:00 2001 From: Eugene Kudelevsky Date: Tue, 15 Mar 2011 17:18:40 +0300 Subject: [PATCH] IDEA-51833 adb operations should be done in non-ui thread with timeout --- .../jetbrains/android/facet/AndroidFacet.java | 1 + .../run/AndroidRunConfigurationBase.java | 92 +++++------ .../android/run/AndroidRunningState.java | 5 +- .../org/jetbrains/android/sdk/AndroidSdk.java | 146 +++++++++++++++++- 4 files changed, 195 insertions(+), 49 deletions(-) diff --git a/plugins/android/src/org/jetbrains/android/facet/AndroidFacet.java b/plugins/android/src/org/jetbrains/android/facet/AndroidFacet.java index 2cece465bbb1..0ab1c1cdefc9 100644 --- a/plugins/android/src/org/jetbrains/android/facet/AndroidFacet.java +++ b/plugins/android/src/org/jetbrains/android/facet/AndroidFacet.java @@ -122,6 +122,7 @@ public class AndroidFacet extends Facet { mySystemResourceManager = null; } + // can be invoked only from dispatch thread! @Nullable public AndroidDebugBridge getDebugBridge() { AndroidPlatform platform = getConfiguration().getAndroidPlatform(); diff --git a/plugins/android/src/org/jetbrains/android/run/AndroidRunConfigurationBase.java b/plugins/android/src/org/jetbrains/android/run/AndroidRunConfigurationBase.java index 3065f0c3c2bc..2154d9f8b7a5 100644 --- a/plugins/android/src/org/jetbrains/android/run/AndroidRunConfigurationBase.java +++ b/plugins/android/src/org/jetbrains/android/run/AndroidRunConfigurationBase.java @@ -192,6 +192,16 @@ public abstract class AndroidRunConfigurationBase extends ModuleBasedConfigurati Project project = env.getProject(); + AndroidFacetConfiguration configuration = facet.getConfiguration(); + AndroidPlatform platform = configuration.getAndroidPlatform(); + if (platform == null) { + Messages.showErrorDialog(project, AndroidBundle.message("specify.platform.error"), CommonBundle.getErrorTitle()); + ModulesConfigurator.showFacetSettingsDialog(facet, null); + return null; + } + + if (platform.getSdk().getDebugBridge(getProject()) == null) return null; + boolean debug = DefaultDebugExecutor.EXECUTOR_ID.equals(executor.getId()); if (debug) { if (!activateDdmsIfNeccessary(facet)) { @@ -204,47 +214,37 @@ public abstract class AndroidRunConfigurationBase extends ModuleBasedConfigurati } } - AndroidFacetConfiguration configuration = facet.getConfiguration(); - AndroidPlatform platform = configuration.getAndroidPlatform(); - if (platform == null) { - Messages.showErrorDialog(project, AndroidBundle.message("specify.platform.error"), CommonBundle.getErrorTitle()); - ModulesConfigurator.showFacetSettingsDialog(facet, null); - return null; - } - else { - String aPackage = getPackageName(facet); - if (aPackage == null) return null; + String aPackage = getPackageName(facet); + if (aPackage == null) return null; - Map depModule2PackageName = new HashMap(); - if (!fillRuntimeAndTestDependencies(module, depModule2PackageName)) return null; + Map depModule2PackageName = new HashMap(); + if (!fillRuntimeAndTestDependencies(module, depModule2PackageName)) return null; - if (platform.getSdk().getDebugBridge(project) == null) return null; - IDevice[] targetDevices = new IDevice[0]; - if (CHOOSE_DEVICE_MANUALLY) { - IDevice[] devices = chooseDevicesManually(facet); - if (devices.length > 0) { - if (debug && containsRealDevice(devices)) { - if (!checkDebuggableOption(facet)) { - return null; - } + IDevice[] targetDevices = new IDevice[0]; + if (CHOOSE_DEVICE_MANUALLY) { + IDevice[] devices = chooseDevicesManually(facet); + if (devices.length > 0) { + if (debug && containsRealDevice(devices)) { + if (!checkDebuggableOption(facet)) { + return null; } - targetDevices = devices; - PropertiesComponent.getInstance(getProject()).setValue(ANDROID_TARGET_DEVICES_PROPERTY, toString(targetDevices)); } - if (targetDevices.length == 0) return null; + targetDevices = devices; + PropertiesComponent.getInstance(getProject()).setValue(ANDROID_TARGET_DEVICES_PROPERTY, toString(targetDevices)); } - AndroidApplicationLauncher applicationLauncher = getApplicationLauncher(facet); - if (applicationLauncher != null) { - return new AndroidRunningState(env, facet, targetDevices, PREFERRED_AVD.length() > 0 ? PREFERRED_AVD : null, - computeCommandLine(), aPackage, applicationLauncher, depModule2PackageName) { + if (targetDevices.length == 0) return null; + } + AndroidApplicationLauncher applicationLauncher = getApplicationLauncher(facet); + if (applicationLauncher != null) { + return new AndroidRunningState(env, facet, targetDevices, PREFERRED_AVD.length() > 0 ? PREFERRED_AVD : null, + computeCommandLine(), aPackage, applicationLauncher, depModule2PackageName) { - @NotNull - @Override - protected ConsoleView attachConsole() throws ExecutionException { - return AndroidRunConfigurationBase.this.attachConsole(this, executor); - } - }; - } + @NotNull + @Override + protected ConsoleView attachConsole() throws ExecutionException { + return AndroidRunConfigurationBase.this.attachConsole(this, executor); + } + }; } return null; } @@ -296,16 +296,22 @@ public abstract class AndroidRunConfigurationBase extends ModuleBasedConfigurati return true; } - static boolean isDdmsCorrupted(@NotNull AndroidFacet facet) { + // can be invoked only from dispatch thread! + private static boolean isDdmsCorrupted(@NotNull AndroidFacet facet) { AndroidDebugBridge bridge = facet.getDebugBridge(); if (bridge != null) { - IDevice[] devices = bridge.getDevices(); - if (devices.length > 0) { - Client[] clients = devices[0].getClients(); - if (clients.length > 0) { - ClientData clientData = clients[0].getClientData(); - return clientData == null || clientData.getVmIdentifier() == null; - } + return isDdmsCorrupted(bridge); + } + return false; + } + + static boolean isDdmsCorrupted(@NotNull AndroidDebugBridge bridge) { + IDevice[] devices = bridge.getDevices(); + if (devices.length > 0) { + Client[] clients = devices[0].getClients(); + if (clients.length > 0) { + ClientData clientData = clients[0].getClientData(); + return clientData == null || clientData.getVmIdentifier() == null; } } return false; diff --git a/plugins/android/src/org/jetbrains/android/run/AndroidRunningState.java b/plugins/android/src/org/jetbrains/android/run/AndroidRunningState.java index a233a23d82b7..822ee80d70e9 100644 --- a/plugins/android/src/org/jetbrains/android/run/AndroidRunningState.java +++ b/plugins/android/src/org/jetbrains/android/run/AndroidRunningState.java @@ -254,7 +254,7 @@ public abstract class AndroidRunningState implements RunProfileState, AndroidDeb @Nullable private IDevice chooseDeviceAutomaticaly() { - final AndroidDebugBridge bridge = myFacet.getDebugBridge(); + final AndroidDebugBridge bridge = AndroidDebugBridge.getBridge(); if (bridge == null) { return null; } @@ -551,7 +551,8 @@ public abstract class AndroidRunningState implements RunProfileState, AndroidDeb } private boolean checkDdms() { - if (myDebugMode && AndroidRunConfigurationBase.isDdmsCorrupted(myFacet)) { + AndroidDebugBridge bridge = AndroidDebugBridge.getBridge(); + if (myDebugMode && bridge != null && AndroidRunConfigurationBase.isDdmsCorrupted(bridge)) { message("Debug info is not available. Please close other application using ADB: DDMS, Eclipse", STDERR); return false; } diff --git a/plugins/android/src/org/jetbrains/android/sdk/AndroidSdk.java b/plugins/android/src/org/jetbrains/android/sdk/AndroidSdk.java index 4203bd2f04dd..fdcff6ea4476 100644 --- a/plugins/android/src/org/jetbrains/android/sdk/AndroidSdk.java +++ b/plugins/android/src/org/jetbrains/android/sdk/AndroidSdk.java @@ -23,6 +23,10 @@ import com.android.sdklib.ISdkLog; import com.android.sdklib.SdkConstants; import com.android.sdklib.SdkManager; import com.intellij.CommonBundle; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.io.FileUtil; @@ -48,6 +52,8 @@ import static org.jetbrains.android.util.AndroidUtils.ADB; public abstract class AndroidSdk { private static volatile boolean myDdmLibInitialized = false; + private static volatile boolean myAdbCrashed = false; + private static final Object myDdmsLock = new Object(); @NotNull @@ -138,7 +144,77 @@ public abstract class AndroidSdk { return getLocation().hashCode(); } - public void initializeDdmlib() { + private boolean initializeDdmlib(@NotNull Project project) { + ApplicationManager.getApplication().assertIsDispatchThread(); + + while (true) { + final MyInitializeDdmlibTask task = new MyInitializeDdmlibTask(project); + + Thread t = new Thread(new Runnable() { + @Override + public void run() { + doInitializeDdmlib(); + task.finish(); + } + }); + + t.start(); + + boolean retryWas = false; + + while (!task.isFinished()) { + ProgressManager.getInstance().run(task); + + boolean finished = task.isFinished(); + + myAdbCrashed = !finished; + + if (task.isCanceled()) { + forceInterrupt(t); + return false; + } + + if (!finished) { + int result = Messages + .showDialog(project, "ADB not responding. Please, kill \"" + SdkConstants.FN_ADB + "\" process manually and click 'Retry'", + CommonBundle.getErrorTitle(), new String[]{"&Retry", "&Cancel"}, 0, Messages.getErrorIcon()); + + if (result == 1) { + forceInterrupt(t); + return false; + } + retryWas = true; + } + } + + // task finished, but if we had problems, ddmlib can be still initialized incorrectly, so we invoke initialize once again + if (!retryWas) { + break; + } + } + + return true; + } + + @SuppressWarnings({"BusyWait"}) + private static void forceInterrupt(Thread thread) { + /* + ddmlib has incorrect handling of InterruptedException, so we need to invoke it several times, + because there are three blocking invokation in succession + */ + + for (int i = 0; i < 6 && thread.isAlive(); i++) { + thread.interrupt(); + try { + Thread.sleep(200); + } + catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + + private void doInitializeDdmlib() { synchronized (myDdmsLock) { String adbPath = getAdbPath(); if (!myDdmLibInitialized) { @@ -148,7 +224,7 @@ public abstract class AndroidSdk { AndroidDebugBridge.createBridge(adbPath, true); } else { - AndroidDebugBridge.createBridge(adbPath, false); + AndroidDebugBridge.createBridge(adbPath, myAdbCrashed); } } } @@ -170,8 +246,10 @@ public abstract class AndroidSdk { } @Nullable - public AndroidDebugBridge getDebugBridge(Project project) { - initializeDdmlib(); + public AndroidDebugBridge getDebugBridge(@NotNull Project project) { + if (!initializeDdmlib(project)) { + return null; + } return AndroidDebugBridge.getBridge(); } @@ -188,4 +266,64 @@ public abstract class AndroidSdk { } return result; } + + private static class MyInitializeDdmlibTask extends Task.Modal { + private final Object myLock = new Object(); + private volatile boolean myFinished; + private volatile boolean myCanceled; + + public MyInitializeDdmlibTask(Project project) { + super(project, "Connecting to ADB process", true); + } + + public boolean isFinished() { + synchronized (myLock) { + return myFinished; + } + } + + public boolean isCanceled() { + synchronized (myLock) { + return myCanceled; + } + } + + public void finish() { + synchronized (myLock) { + myFinished = true; + myLock.notifyAll(); + } + } + + @Override + public void onCancel() { + synchronized (myLock) { + myCanceled = true; + myLock.notifyAll(); + } + } + + @Override + public void run(@NotNull ProgressIndicator indicator) { + indicator.setIndeterminate(true); + synchronized (myLock) { + final long startTime = System.currentTimeMillis(); + + final long timeout = 7000; + + while (!myFinished && !myCanceled) { + long wastedTime = System.currentTimeMillis() - startTime; + if (wastedTime >= timeout) { + break; + } + try { + myLock.wait(timeout - wastedTime); + } + catch (InterruptedException e) { + break; + } + } + } + } + } }