Revamp exceptions
Various changes, based on the comments on aosp/2192077 and the API
guidelines:
- Always rethrow RemoteException as a runtime exception.
- Map the various other exceptions that VirtualizationService can
return to VirtualMachineException for consistency - except for
SecurityException (missing permissions) which will still be directly
thrown.
- Document the exceptions that can be thrown and when for each API
function.
- Always create the console & log pipes on demand, mostly so I don't
have to document exactly when they might be null.
- Use an exception rather than an Optional for getCid().
- Check for null return from nativeConnectToVsockServer() and throw.
Bug: 243512115
Test: atest MicrodroidTests MicrodroidHostTests
Change-Id: Ib5cb421bc9137db4d9dddada71f0098212b7d0da
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index 81f97f3..e2fc33e 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -53,6 +53,7 @@
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
import android.system.virtualizationcommon.ErrorCode;
import android.system.virtualizationservice.DeathReason;
import android.system.virtualizationservice.IVirtualMachine;
@@ -82,7 +83,6 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
import java.util.WeakHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -286,6 +286,8 @@
} catch (FileNotFoundException e) {
throw new VirtualMachineException("instance image missing", e);
} catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } catch (ServiceSpecificException | IllegalArgumentException e) {
throw new VirtualMachineException("failed to create instance partition", e);
}
@@ -379,7 +381,7 @@
* @hide
*/
@Status
- public int getStatus() throws VirtualMachineException {
+ public int getStatus() {
try {
if (mVirtualMachine != null) {
switch (mVirtualMachine.getState()) {
@@ -395,7 +397,7 @@
}
}
} catch (RemoteException e) {
- throw new VirtualMachineException(e);
+ throw e.rethrowAsRuntimeException();
}
if (!mConfigFilePath.exists()) {
return STATUS_DELETED;
@@ -451,8 +453,11 @@
/**
* Runs this virtual machine. The returning of this method however doesn't mean that the VM has
* actually started running or the OS has booted there. Such events can be notified by
- * registering a callback using {@link #setCallback(Executor, VirtualMachineCallback)}.
+ * registering a callback using {@link #setCallback(Executor, VirtualMachineCallback)} before
+ * calling {@code run()}.
*
+ * @throws VirtualMachineException if the virtual machine is not stopped or could not be
+ * started.
* @hide
*/
@RequiresPermission(MANAGE_VIRTUAL_MACHINE_PERMISSION)
@@ -476,17 +481,7 @@
ServiceManager.waitForService(SERVICE_NAME));
try {
- if (mConsoleReader == null && mConsoleWriter == null) {
- ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
- mConsoleReader = pipe[0];
- mConsoleWriter = pipe[1];
- }
-
- if (mLogReader == null && mLogWriter == null) {
- ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
- mLogReader = pipe[0];
- mLogWriter = pipe[1];
- }
+ createVmPipes();
VirtualMachineAppConfig appConfig = getConfig().toParcel();
appConfig.name = mName;
@@ -571,7 +566,27 @@
);
service.asBinder().linkToDeath(deathRecipient, 0);
mVirtualMachine.start();
- } catch (IOException | RemoteException e) {
+ } catch (IOException | IllegalStateException | ServiceSpecificException e) {
+ throw new VirtualMachineException(e);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ private void createVmPipes() throws VirtualMachineException {
+ try {
+ if (mConsoleReader == null || mConsoleWriter == null) {
+ ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+ mConsoleReader = pipe[0];
+ mConsoleWriter = pipe[1];
+ }
+
+ if (mLogReader == null || mLogWriter == null) {
+ ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+ mLogReader = pipe[0];
+ mLogWriter = pipe[1];
+ }
+ } catch (IOException e) {
throw new VirtualMachineException(e);
}
}
@@ -633,26 +648,24 @@
/**
* Returns the stream object representing the console output from the virtual machine.
*
+ * @throws VirtualMachineException if the stream could not be created.
* @hide
*/
@NonNull
public InputStream getConsoleOutputStream() throws VirtualMachineException {
- if (mConsoleReader == null) {
- throw new VirtualMachineException("Console output not available");
- }
+ createVmPipes();
return new FileInputStream(mConsoleReader.getFileDescriptor());
}
/**
* Returns the stream object representing the log output from the virtual machine.
*
+ * @throws VirtualMachineException if the stream could not be created.
* @hide
*/
@NonNull
public InputStream getLogOutputStream() throws VirtualMachineException {
- if (mLogReader == null) {
- throw new VirtualMachineException("Log output not available");
- }
+ createVmPipes();
return new FileInputStream(mLogReader.getFileDescriptor());
}
@@ -662,6 +675,7 @@
* notified of the event. A stopped virtual machine can be re-started by calling {@link
* #run()}.
*
+ * @throws VirtualMachineException if the virtual machine could not be stopped.
* @hide
*/
public void stop() throws VirtualMachineException {
@@ -670,6 +684,8 @@
mVirtualMachine.stop();
mVirtualMachine = null;
} catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } catch (ServiceSpecificException e) {
throw new VirtualMachineException(e);
}
}
@@ -677,6 +693,7 @@
/**
* Stops this virtual machine. See {@link #stop()}.
*
+ * @throws VirtualMachineException if the virtual machine could not be stopped.
* @hide
*/
@Override
@@ -690,6 +707,7 @@
* machine once deleted can never be restored. A new virtual machine created with the same name
* and the same config is different from an already deleted virtual machine.
*
+ * @throws VirtualMachineException if the virtual machine is not stopped.
* @hide
*/
public void delete() throws VirtualMachineException {
@@ -714,31 +732,32 @@
/**
* Returns the CID of this virtual machine, if it is running.
*
+ * @throws VirtualMachineException if the virtual machine is not running.
* @hide
*/
@NonNull
- public Optional<Integer> getCid() throws VirtualMachineException {
+ public int getCid() throws VirtualMachineException {
if (getStatus() != STATUS_RUNNING) {
- return Optional.empty();
+ throw new VirtualMachineException("VM is not running");
}
try {
- return Optional.of(mVirtualMachine.getCid());
+ return mVirtualMachine.getCid();
} catch (RemoteException e) {
- throw new VirtualMachineException(e);
+ throw e.rethrowAsRuntimeException();
}
}
/**
* Changes the config of this virtual machine to a new one. This can be used to adjust things
* like the number of CPU and size of the RAM, depending on the situation (e.g. the size of the
- * application to run on the virtual machine, etc.) However, changing a config might make the
- * virtual machine un-bootable if the new config is not compatible with the existing one. For
- * example, if the signer of the app payload in the new config is different from that of the old
- * config, the virtual machine won't boot. To prevent such cases, this method returns exception
- * when an incompatible config is attempted.
+ * application to run on the virtual machine, etc.)
+ *
+ * The new config must be {@link VirtualMachineConfig#isCompatibleWith compatible with} the
+ * existing config.
*
* @return the old config
- *
+ * @throws VirtualMachineException if the virtual machine is not stopped, or the new config is
+ * incompatible.
* @hide
*/
@NonNull
@@ -765,6 +784,7 @@
return oldConfig;
}
+ @Nullable
private static native IBinder nativeConnectToVsockServer(IBinder vmBinder, int port);
/**
@@ -773,6 +793,8 @@
* VirtualMachineCallback#onPayloadReady(VirtualMachine)}, it can use this method to
* establish a connection to the guest VM.
*
+ * @throws VirtualMachineException if the virtual machine is not running or the connection
+ * failed.
* @hide
*/
@NonNull
@@ -780,12 +802,17 @@
if (getStatus() != STATUS_RUNNING) {
throw new VirtualMachineException("VM is not running");
}
- return nativeConnectToVsockServer(mVirtualMachine.asBinder(), port);
+ IBinder iBinder = nativeConnectToVsockServer(mVirtualMachine.asBinder(), port);
+ if (iBinder == null) {
+ throw new VirtualMachineException("Failed to connect to vsock server");
+ }
+ return iBinder;
}
/**
* Opens a vsock connection to the VM on the given port.
*
+ * @throws VirtualMachineException if connecting fails.
* @hide
*/
@NonNull
@@ -793,7 +820,9 @@
try {
return mVirtualMachine.connectVsock(port);
} catch (RemoteException e) {
- throw new VirtualMachineException("failed to connect vsock", e);
+ throw e.rethrowAsRuntimeException();
+ } catch (ServiceSpecificException e) {
+ throw new VirtualMachineException(e);
}
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index 9555d1e..2dff9bb 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -39,7 +39,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
-
/**
* Represents a configuration of a virtual machine. A configuration consists of hardware
* configurations like the number of CPUs and the size of RAM, and software configurations like the
@@ -371,7 +370,7 @@
}
if (!mProtectedVmSet) {
- throw new IllegalStateException("setProtectedVm(t/f) must be called explicitly");
+ throw new IllegalStateException("setProtectedVm must be called explicitly");
}
if (mProtectedVm
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index 86fd91f..3f904be 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -120,7 +120,8 @@
* name and the config are the same as a deleted one. The new virtual machine will initially
* be stopped.
*
- * @throws VirtualMachineException If there is an existing virtual machine with the given name
+ * @throws VirtualMachineException if the VM cannot be created, or there is an existing VM with
+ * the given name.
* @hide
*/
@NonNull
@@ -137,6 +138,7 @@
* Returns an existing {@link VirtualMachine} with the given name. Returns null if there is no
* such virtual machine.
*
+ * @throws VirtualMachineException if the virtual machine could not be successfully retrieved.
* @hide
*/
@Nullable
@@ -148,6 +150,7 @@
* Returns an existing {@link VirtualMachine} if it exists, or create a new one. The config
* parameter is used only when a new virtual machine is created.
*
+ * @throws VirtualMachineException if the virtual machine could not be created or retrieved.
* @hide
*/
@NonNull