IDEA-51833 adb operations should be done in non-ui thread with timeout

This commit is contained in:
Eugene Kudelevsky
2011-03-17 21:26:40 +03:00
parent 3dcdd384bf
commit b2776cefa4
4 changed files with 195 additions and 49 deletions
@@ -122,6 +122,7 @@ public class AndroidFacet extends Facet<AndroidFacetConfiguration> {
mySystemResourceManager = null;
}
// can be invoked only from dispatch thread!
@Nullable
public AndroidDebugBridge getDebugBridge() {
AndroidPlatform platform = getConfiguration().getAndroidPlatform();
@@ -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<AndroidFacet, String> depModule2PackageName = new HashMap<AndroidFacet, String>();
if (!fillRuntimeAndTestDependencies(module, depModule2PackageName)) return null;
Map<AndroidFacet, String> depModule2PackageName = new HashMap<AndroidFacet, String>();
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;
@@ -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;
}
@@ -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;
}
}
}
}
}
}