Fixed timeout problems with list, copy, move and delete commands.
Check new data for list commands; use indefinitely wait for copy, move and delete commands.
Tested with 5000 and 30000 files.
Change-Id: I33cd6c9b7422966cdc4bc0c9cb265f74533ef161
JIRA: https://jira.cyanogenmod.org/browse/CYAN-533
Bugfix: CYAN-533
Signed-off-by: Jorge Ruesga <jorge@ruesga.com>
<!-- Process control and info -->
<command commandId="pid_shell" commandPath="/system/xbin/echo" commandArgs="$$" />
+ <command commandId="pid_shell_cmds" commandPath="/system/bin/ps" commandArgs="| /system/xbin/grep -w %1$s | /system/xbin/awk '{print $2}' | /system/xbin/grep -v -w %1$s" />
<command commandId="pid_cmd" commandPath="/system/bin/ps" commandArgs="| /system/xbin/grep %1$s | /system/xbin/grep -w %2$s | /system/xbin/awk '{print $2}'" />
<command commandId="sendsignal" commandPath="/system/bin/kill" commandArgs="-%1$s %2$s" />
<command commandId="terminate" commandPath="/system/bin/kill" commandArgs="%1$s" />
NoSuchFileOrDirectory, InsufficientPermissionsException;
/**
+ * Method that creates an executable for retrieve operating system process identifiers of a
+ * shell.
+ *
+ * @param pid The shell process id where the process is running
+ * @param processName The process name
+ * @return ProcessIdExecutable A {@link ProcessIdExecutable} executable implementation
+ * reference
+ * @throws CommandNotFoundException If the executable can't be created
+ * @throws NoSuchFileOrDirectory If the file or directory was not found
+ * @throws InsufficientPermissionsException If an operation requires elevated permissions
+ */
+ ProcessIdExecutable createProcessIdExecutable(int pid) throws CommandNotFoundException,
+ NoSuchFileOrDirectory, InsufficientPermissionsException;
+
+ /**
* Method that creates an executable for retrieve operating system process identifier of a
* process.
*
package com.cyanogenmod.filemanager.commands;
+import java.util.List;
+
/**
* An interface that represents an executable for retrieve the process identifier
* of a program.
* {@inheritDoc}
*/
@Override
- Integer getResult();
+ List<Integer> getResult();
}
* {@inheritDoc}
*/
@Override
+ public ProcessIdExecutable createProcessIdExecutable(int pid)
+ throws CommandNotFoundException {
+ throw new CommandNotFoundException("Not implemented"); //$NON-NLS-1$
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public ProcessIdExecutable createProcessIdExecutable(int pid, String processName)
throws CommandNotFoundException {
throw new CommandNotFoundException("Not implemented"); //$NON-NLS-1$
* {@inheritDoc}
*/
@Override
+ public final boolean isIndefinitelyWait() {
+ // Asynchronous programs should wait indefinitely for its nature
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public boolean isCancellable() {
//By defect an asynchronous command is cancellable
return true;
public MountPoint getDstWritableMountPoint() {
return MountPointHelper.getMountPointFromDirectory(this.mDst);
}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isIndefinitelyWait() {
+ return true;
+ }
}
public MountPoint getDstWritableMountPoint() {
return MountPointHelper.getMountPointFromDirectory(this.mFileName);
}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isIndefinitelyWait() {
+ return true;
+ }
}
public MountPoint getDstWritableMountPoint() {
return MountPointHelper.getMountPointFromDirectory(this.mFileName);
}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isIndefinitelyWait() {
+ return true;
+ }
}
throw new ExecutionException("exitcode != 0 && != 1 && != 123"); //$NON-NLS-1$
}
}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isWaitOnNewDataReceipt() {
+ return true;
+ }
}
public MountPoint getDstWritableMountPoint() {
return MountPointHelper.getMountPointFromDirectory(this.mDst);
}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isIndefinitelyWait() {
+ return true;
+ }
}
import java.io.IOException;
import java.io.StringReader;
import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.List;
/**
public class ProcessIdCommand extends SyncResultProgram implements ProcessIdExecutable {
private static final String ID_SHELL = "pid_shell"; //$NON-NLS-1$
+ private static final String ID_SHELL_CMDS = "pid_shell_cmds"; //$NON-NLS-1$
private static final String ID_CMD = "pid_cmd"; //$NON-NLS-1$
- private Integer mPID;
+ private List<Integer> mPIDs;
/**
* Constructor of <code>ProcessIdCommand</code>.<br/>
/**
* Constructor of <code>ProcessIdCommand</code>.<br/>
+ * Use this to retrieve all PIDs running on a shell.
+ *
+ * @param pid The process identifier of the shell when the process is running
+ * @throws InvalidCommandDefinitionException If the command has an invalid definition
+ */
+ public ProcessIdCommand(int pid) throws InvalidCommandDefinitionException {
+ super(ID_SHELL_CMDS, new String[]{String.valueOf(pid)});
+ }
+
+ /**
+ * Constructor of <code>ProcessIdCommand</code>.<br/>
* Use this to retrieve the PID of a command running on a shell.
*
* @param pid The process identifier of the shell when the process is running
@Override
public void parse(String in, String err) throws ParseException {
//Release the return object
- this.mPID = null;
+ this.mPIDs = new ArrayList<Integer>();
// Check the in buffer to extract information
BufferedReader br = null;
if (szLine == null) {
throw new ParseException("no information", 0); //$NON-NLS-1$
}
+ do {
+ // Add every PID
+ this.mPIDs.add(Integer.valueOf(szLine.trim()));
- //Get the PID
- this.mPID = Integer.valueOf(szLine.trim());
+ // Next line
+ szLine = br.readLine();
+ } while (szLine != null);
} catch (IOException ioEx) {
throw new ParseException(ioEx.getMessage(), 0);
* {@inheritDoc}
*/
@Override
- public Integer getResult() {
- return this.mPID;
+ public List<Integer> getResult() {
+ return this.mPIDs;
}
/**
import com.cyanogenmod.filemanager.console.ExecutionException;
import com.cyanogenmod.filemanager.console.InsufficientPermissionsException;
import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
+import com.cyanogenmod.filemanager.console.OperationTimeoutException;
import java.io.OutputStream;
}
/**
+ * Returns whether the shell should wait indefinitely for the end of the command.
+ *
+ * @return boolean If shell should wait indefinitely for the end of the command
+ * @hide
+ */
+ @SuppressWarnings("static-method")
+ public boolean isIndefinitelyWait() {
+ return false;
+ }
+
+ /**
+ * Returns whether the shell shouldn't raise a {@link OperationTimeoutException} when
+ * the program didn't exited but new data was received.
+ *
+ * @return boolean If shell shouldn't raise a {@link OperationTimeoutException} if new
+ * data was received
+ * @hide
+ */
+ @SuppressWarnings("static-method")
+ public boolean isWaitOnNewDataReceipt() {
+ return false;
+ }
+
+ /**
* Method that returns if the standard error must be
* ignored safely by the shell, and don't check for errors
* like <code>NoSuchFileOrDirectory</code> or
return true;
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isWaitOnNewDataReceipt() {
+ return true;
+ }
+
}
* {@inheritDoc}
*/
@Override
+ public ProcessIdExecutable createProcessIdExecutable(int pid)
+ throws CommandNotFoundException {
+ try {
+ return new ProcessIdCommand(pid);
+ } catch (InvalidCommandDefinitionException icdEx) {
+ throw new CommandNotFoundException("ProcessIdCommand", icdEx); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public ProcessIdExecutable createProcessIdExecutable(int pid, String processName)
throws CommandNotFoundException {
try {
private static final String TAG = "ShellConsole"; //$NON-NLS-1$
- // A timeout of 5 seconds should be enough for no-debugging environments
+ // A timeout of 3 seconds should be enough for no-debugging environments
private static final long DEFAULT_TIMEOUT =
FileManagerApplication.isDebuggable() ? 20000L : 3000L;
+ // A maximum operation timeout independently of the isWaitOnNewDataReceipt
+ // of the program. A synchronous operation must not be more longer than
+ // MAX_OPERATION_TIMEOUT + DEFAULT_TIMEOUT
+ private static final long MAX_OPERATION_TIMEOUT = 30000L;
+
private static final int DEFAULT_BUFFER = 512;
//Shell References
*/
boolean mActive = false;
private boolean mFinished = true;
+ private boolean mNewData = false;
private Process mProc = null;
/**
* @hide
// wait to user response to SuperUser or SuperSu prompt (or whatever it is)
// The rest of sync operations will run with a timeout.
execute(processIdCmd, this.isPrivileged());
- Integer pid = processIdCmd.getResult();
+ Integer pid = null;
+ try {
+ pid = processIdCmd.getResult().get(0);
+ } catch (Exception e) {
+ // Ignore
+ }
if (pid == null) {
throw new ConsoleAllocException(
"can't retrieve the PID of the shell."); //$NON-NLS-1$
sb.append(FileHelper.NEWLINE);
synchronized (this.mSync) {
this.mFinished = false;
+ this.mNewData = false;
this.mOut.write(sb.toString().getBytes());
}
} catch (InvalidCommandDefinitionException icdEx) {
//Now, wait for buffers to be filled
synchronized (this.mSync) {
if (!this.mFinished) {
- if (waitForSu || program instanceof AsyncResultProgram) {
+ if (waitForSu || program.isIndefinitelyWait()) {
this.mSync.wait();
} else {
- this.mSync.wait(DEFAULT_TIMEOUT);
- if (!this.mFinished) {
- throw new OperationTimeoutException(DEFAULT_TIMEOUT, cmd);
+ final long start = System.currentTimeMillis();
+ while (true) {
+ this.mSync.wait(DEFAULT_TIMEOUT);
+ if (!this.mFinished) {
+ final long end = System.currentTimeMillis();
+ if (!program.isWaitOnNewDataReceipt() ||
+ !this.mNewData ||
+ (end - start >= MAX_OPERATION_TIMEOUT)) {
+ throw new OperationTimeoutException(end - start, cmd);
+ }
+
+ // Still waiting for program ending
+ this.mNewData = false;
+ continue;
+ }
+ break;
}
}
}
//Invocation finished. Now program.getResult() has the result of
//the operation, if any exists
+ } catch (OperationTimeoutException otEx) {
+ try {
+ killCurrentCommand();
+ } catch (Exception e) { /**NON BLOCK **/}
+ throw otEx;
+
} catch (IOException ioEx) {
if (reallocate) {
realloc();
sb.append((char)r);
}
+ // New data received
+ onNewData();
+
//Check if the command has finished (and extract the control)
boolean finished = isCommandFinished(shell.mSbIn, sb);
sb.append(s);
}
+ // New data received
+ onNewData();
+
//Check if the command has finished (and extract the control)
boolean finished = isCommandFinished(shell.mSbIn, sb);
toStdErr(sb.toString());
}
+ // New data received
+ onNewData();
+
//Has more data? Read with available as more as exists
//or maximum loop count is rebased
int count = 0;
break;
}
+ // New data received
+ onNewData();
+
//Wait for buffer to be filled
try {
Thread.sleep(1L);
}
/**
+ * New data was received
+ * @hide
+ */
+ void onNewData() {
+ synchronized (this.mSync) {
+ this.mNewData = true;
+ }
+ }
+
+ /**
* Method that returns the exit code of the last executed command.
*
* @param stdin The standard in buffer
*/
private boolean killCurrentCommand() {
synchronized (this.mSync) {
- //Is synchronous program? Otherwise it can't be cancelled
- if (!(this.mActiveCommand instanceof AsyncResultProgram)) {
- return false;
- }
// Check background console
try {
FileManagerApplication.getBackgroundConsole();
return false;
}
- final AsyncResultProgram program = (AsyncResultProgram)this.mActiveCommand;
- if (program.getCommand() != null) {
+ if (this.mActiveCommand.getCommand() != null) {
try {
- if (program.isCancellable()) {
+ boolean isCancellable = true;
+ if (this.mActiveCommand instanceof AsyncResultProgram) {
+ final AsyncResultProgram asyncCmd =
+ (AsyncResultProgram)this.mActiveCommand;
+ isCancellable = asyncCmd.isCancellable();
+ }
+
+ if (isCancellable) {
try {
- //Get the PID in background
- Integer pid =
- CommandHelper.getProcessId(
+ //Get the PIDs in background
+ List<Integer> pids =
+ CommandHelper.getProcessesIds(
null,
this.mShell.getPid(),
- program.getCommand(),
FileManagerApplication.getBackgroundConsole());
- if (pid != null) {
- CommandHelper.sendSignal(
- null,
- pid.intValue(),
- FileManagerApplication.getBackgroundConsole());
- try {
- //Wait for process kill
- Thread.sleep(100L);
- } catch (Throwable ex) {
- /**NON BLOCK**/
+ for (Integer pid: pids) {
+ if (pid != null) {
+ CommandHelper.sendSignal(
+ null,
+ pid.intValue(),
+ FileManagerApplication.getBackgroundConsole());
+ try {
+ //Wait for process to be killed
+ Thread.sleep(100L);
+ } catch (Throwable ex) {
+ /**NON BLOCK**/
+ }
}
- return true;
}
+ return true;
} finally {
// It's finished
this.mCancelled = true;
} catch (Throwable ex) {
Log.w(TAG,
String.format("Unable to kill current program: %s", //$NON-NLS-1$
- program.getCommand()), ex);
+ this.mActiveCommand.getCommand()), ex);
}
}
}
*/
private boolean sendSignalToCurrentCommand(SIGNAL signal) {
synchronized (this.mSync) {
- //Is synchronous program? Otherwise it can't be cancelled
- if (!(this.mActiveCommand instanceof AsyncResultProgram)) {
- return false;
- }
// Check background console
try {
FileManagerApplication.getBackgroundConsole();
return false;
}
- final AsyncResultProgram program = (AsyncResultProgram)this.mActiveCommand;
- if (program.getCommand() != null) {
+ if (this.mActiveCommand.getCommand() != null) {
try {
- if (program.isCancellable()) {
+ boolean isCancellable = true;
+ if (this.mActiveCommand instanceof AsyncResultProgram) {
+ final AsyncResultProgram asyncCmd =
+ (AsyncResultProgram)this.mActiveCommand;
+ isCancellable = asyncCmd.isCancellable();
+ }
+
+ if (isCancellable) {
try {
- //Get the PID in background
- Integer pid =
- CommandHelper.getProcessId(
+ //Get the PIDs in background
+ List<Integer> pids =
+ CommandHelper.getProcessesIds(
null,
this.mShell.getPid(),
- program.getCommand(),
FileManagerApplication.getBackgroundConsole());
- if (pid != null) {
- CommandHelper.sendSignal(
- null,
- pid.intValue(),
- signal,
- FileManagerApplication.getBackgroundConsole());
- try {
- //Wait for process kill
- Thread.sleep(100L);
- } catch (Throwable ex) {
- /**NON BLOCK**/
+ for (Integer pid: pids) {
+ if (pid != null) {
+ CommandHelper.sendSignal(
+ null,
+ pid.intValue(),
+ signal,
+ FileManagerApplication.getBackgroundConsole());
+ try {
+ //Wait for process to be signaled
+ Thread.sleep(100L);
+ } catch (Throwable ex) {
+ /**NON BLOCK**/
+ }
}
- return true;
}
+ return true;
} finally {
// It's finished
this.mCancelled = true;
} catch (Throwable ex) {
Log.w(TAG,
String.format("Unable to send signal to current program: %s", //$NON-NLS-1$
- program.getCommand()), ex);
+ this.mActiveCommand.getCommand()), ex);
}
}
}
}
/**
+ * Method that retrieves the process identifier of all the processes (a program
+ * owned by the main process of this application).
+ *
+ * @param context The current context (needed if console == null)
+ * @param pid The process id of the shell where the command is running
+ * @param console The console in which execute the program. <code>null</code>
+ * to attach to the default console
+ * @return List<Integer> The processes identifiers of the program or <code>null</code> if not exists
+ * @throws FileNotFoundException If the initial directory not exists
+ * @throws IOException If initial directory couldn't be checked
+ * @throws InvalidCommandDefinitionException If the command has an invalid definition
+ * @throws NoSuchFileOrDirectory If the file or directory was not found
+ * @throws ConsoleAllocException If the console can't be allocated
+ * @throws InsufficientPermissionsException If an operation requires elevated permissions
+ * @throws CommandNotFoundException If the command was not found
+ * @throws OperationTimeoutException If the operation exceeded the maximum time of wait
+ * @throws ExecutionException If the operation returns a invalid exit code
+ * @see ProcessIdExecutable
+ */
+ public static List<Integer> getProcessesIds(
+ Context context, int pid, Console console)
+ throws FileNotFoundException, IOException, ConsoleAllocException,
+ NoSuchFileOrDirectory, InsufficientPermissionsException,
+ CommandNotFoundException, OperationTimeoutException,
+ ExecutionException, InvalidCommandDefinitionException {
+ Console c = ensureConsole(context, console);
+ ProcessIdExecutable executable =
+ c.getExecutableFactory().newCreator().createProcessIdExecutable(pid);
+ execute(context, executable, c);
+ return executable.getResult();
+ }
+
+ /**
* Method that retrieves the process identifier of a process (a program
* owned by the main process of this application).
*
ProcessIdExecutable executable =
c.getExecutableFactory().newCreator().createProcessIdExecutable(pid, processName);
execute(context, executable, c);
- return executable.getResult();
+ List<Integer> pids = executable.getResult();
+ if (pids != null && pids.size() > 0) {
+ return pids.get(0);
+ }
+ return null;
}
/**