Skip to content

read logcat logs in nonblocking mode with end timeout #466

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Sep 7, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 4 additions & 13 deletions src/main/java/org/acra/collector/LogCatCollector.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,28 +97,19 @@ public String collectLogCat(@NonNull ACRAConfiguration config, @Nullable String
commandLine.addAll(logcatArgumentsList);

try {
final Process process = Runtime.getRuntime().exec(commandLine.toArray(new String[commandLine.size()]));
final Process process = new ProcessBuilder().command(commandLine).redirectErrorStream(true).start();

if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Retrieving logcat output...");

// Dump stderr to null
new Thread(new Runnable() {
@Override
public void run() {
try {
IOUtils.streamToString(process.getErrorStream());
} catch (IOException ignored) {
}
}
}).start();

final String finalMyPidStr = myPidStr;
logcatBuf.add(IOUtils.streamToString(process.getInputStream(), new Predicate<String>() {
logcatBuf.add(IOUtils.streamToStringAsync(process.getInputStream(), new Predicate<String>() {
@Override
public boolean apply(String s) {
return finalMyPidStr == null || s.contains(finalMyPidStr);
}
}));
}, tailCount));
process.destroy();

} catch (IOException e) {
ACRA.log.e(LOG_TAG, "LogCatCollector.collectLogCat could not retrieve data.", e);
Expand Down
33 changes: 33 additions & 0 deletions src/main/java/org/acra/util/IOUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public boolean apply(String s) {
}
};
private static final int NO_LIMIT = -1;
private static final int READ_TIMEOUT = 3000;

private IOUtils() {
}
Expand Down Expand Up @@ -128,4 +129,36 @@ public static String streamToString(@NonNull InputStream input, Predicate<String
safeClose(reader);
}
}

/**
* Reads an InputStream into a string in an async way
*
* @param input the stream
* @param filter should return false for lines which should be excluded
* @param limit the maximum number of lines to read (the last x lines are kept)
* @return the read string
* @throws IOException
*/
@NonNull
public static String streamToStringAsync(@NonNull InputStream input, Predicate<String> filter, int limit) throws IOException {
final BufferedReader reader = new BufferedReader(new InputStreamReader(input), ACRAConstants.DEFAULT_BUFFER_SIZE_IN_BYTES);
final NonblockingBufferedReader nonBlockingReader = new NonblockingBufferedReader(reader);
try {
String line;
final List<String> buffer = limit == NO_LIMIT ? new LinkedList<String>() : new BoundedLinkedList<String>(limit);
long end = System.currentTimeMillis() + READ_TIMEOUT;
while ((System.currentTimeMillis() < end)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this mean that it will always block for 3 seconds?

Copy link
Contributor Author

@c-romeo c-romeo Jun 4, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I've added a read timeout limit. Not sure if this should be exposed to ACRAConfiguration.
In my tests if timeout was reached in few seconds I received on remote server logs with empty logcat field.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And if a developer don't want any delay on main / exception thread, acra's work should be done in a separate thread. For the moment I use a helper method in which I use a Runnable and start a new thread; again, not sure if a queue should be use.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point is, is that it's not a timeout.
This read will block for exactly 3 seconds regardless of whether there is any log to read.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point. this is the fix
if ((line = nonBlockingReader.readLine()) != null) { if (filter.apply(line)) { buffer.add(line); } } else { break; }

try {
if ((line = nonBlockingReader.readLine()) != null) {
if (filter.apply(line)) {
buffer.add(line);
}
}
} catch (InterruptedException e) {}
}
return TextUtils.join("\n", buffer);
} finally {
nonBlockingReader.close();
}
}
}
52 changes: 52 additions & 0 deletions src/main/java/org/acra/util/NonblockingBufferedReader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package org.acra.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
* @author C-Romeo
* @since 4.9.0
*/
public class NonblockingBufferedReader {
private final BlockingQueue<String> lines = new LinkedBlockingQueue<String>();
private boolean closed = false;
private Thread backgroundReaderThread = null;

public NonblockingBufferedReader(final BufferedReader bufferedReader) {
backgroundReaderThread = new Thread(new Runnable() {
@Override
public void run() {
try {
while (!Thread.interrupted()) {
String line = bufferedReader.readLine();
if (line == null) {
break;
}
lines.add(line);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
closed = true;
IOUtils.safeClose(bufferedReader);
}
}
});
backgroundReaderThread.setDaemon(true);
backgroundReaderThread.start();
}

public String readLine() throws InterruptedException {
return closed && lines.isEmpty() ? null : lines.poll(500L, TimeUnit.MILLISECONDS);
}

public void close() {
if (backgroundReaderThread != null) {
backgroundReaderThread.interrupt();
backgroundReaderThread = null;
}
}
}