Fix a sync problem in NativeDaemonConnector.

We had a gap in sync coverage between doing a check and waiting and a
matching gap between setting a condition and notifying.  It was possible
to get context switched just so and have the notify hit before the waiter
had started waiting.

bug:6492166
Change-Id: Idc876cf85b35902a79fae932547957ed5ef00e4f
This commit is contained in:
Robert Greenwalt
2012-06-05 11:48:40 -07:00
parent cb9495551c
commit 610bc9678d

View File

@@ -35,6 +35,9 @@ import java.io.OutputStream;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.LinkedList; import java.util.LinkedList;
/** /**
@@ -482,102 +485,108 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
private static class ResponseQueue { private static class ResponseQueue {
private static class Response { private static class PendingCmd {
public int cmdNum; public int cmdNum;
public LinkedList<NativeDaemonEvent> responses = new LinkedList<NativeDaemonEvent>(); public BlockingQueue<NativeDaemonEvent> responses =
new ArrayBlockingQueue<NativeDaemonEvent>(10);
public String request; public String request;
public Response(int c, String r) {cmdNum = c; request = r;}
// The availableResponseCount member is used to track when we can remove this
// instance from the ResponseQueue.
// This is used under the protection of a sync of the mPendingCmds object.
// A positive value means we've had more writers retreive this object while
// a negative value means we've had more readers. When we've had an equal number
// (it goes to zero) we can remove this object from the mPendingCmds list.
// Note that we may have more responses for this command (and more readers
// coming), but that would result in a new PendingCmd instance being created
// and added with the same cmdNum.
// Also note that when this goes to zero it just means a parity of readers and
// writers have retrieved this object - not that they are done using it. The
// responses queue may well have more responses yet to be read or may get more
// responses added to it. But all those readers/writers have retreived and
// hold references to this instance already so it can be removed from
// mPendingCmds queue.
public int availableResponseCount;
public PendingCmd(int c, String r) {cmdNum = c; request = r;}
} }
private final LinkedList<Response> mResponses; private final LinkedList<PendingCmd> mPendingCmds;
private int mMaxCount; private int mMaxCount;
ResponseQueue(int maxCount) { ResponseQueue(int maxCount) {
mResponses = new LinkedList<Response>(); mPendingCmds = new LinkedList<PendingCmd>();
mMaxCount = maxCount; mMaxCount = maxCount;
} }
public void add(int cmdNum, NativeDaemonEvent response) { public void add(int cmdNum, NativeDaemonEvent response) {
Response found = null; PendingCmd found = null;
synchronized (mResponses) { synchronized (mPendingCmds) {
for (Response r : mResponses) { for (PendingCmd pendingCmd : mPendingCmds) {
if (r.cmdNum == cmdNum) { if (pendingCmd.cmdNum == cmdNum) {
found = r; found = pendingCmd;
break; break;
} }
} }
if (found == null) { if (found == null) {
// didn't find it - make sure our queue isn't too big before adding // didn't find it - make sure our queue isn't too big before adding
// another.. while (mPendingCmds.size() >= mMaxCount) {
while (mResponses.size() >= mMaxCount) {
Slog.e("NativeDaemonConnector.ResponseQueue", Slog.e("NativeDaemonConnector.ResponseQueue",
"more buffered than allowed: " + mResponses.size() + "more buffered than allowed: " + mPendingCmds.size() +
" >= " + mMaxCount); " >= " + mMaxCount);
// let any waiter timeout waiting for this // let any waiter timeout waiting for this
Response r = mResponses.remove(); PendingCmd pendingCmd = mPendingCmds.remove();
Slog.e("NativeDaemonConnector.ResponseQueue", Slog.e("NativeDaemonConnector.ResponseQueue",
"Removing request: " + r.request + " (" + r.cmdNum + ")"); "Removing request: " + pendingCmd.request + " (" +
pendingCmd.cmdNum + ")");
} }
found = new Response(cmdNum, null); found = new PendingCmd(cmdNum, null);
mResponses.add(found); mPendingCmds.add(found);
} }
found.responses.add(response); found.availableResponseCount++;
} // if a matching remove call has already retrieved this we can remove this
synchronized (found) { // instance from our list
found.notify(); if (found.availableResponseCount == 0) mPendingCmds.remove(found);
} }
try {
found.responses.put(response);
} catch (InterruptedException e) { }
} }
// note that the timeout does not count time in deep sleep. If you don't want // note that the timeout does not count time in deep sleep. If you don't want
// the device to sleep, hold a wakelock // the device to sleep, hold a wakelock
public NativeDaemonEvent remove(int cmdNum, int timeoutMs, String origCmd) { public NativeDaemonEvent remove(int cmdNum, int timeoutMs, String origCmd) {
long endTime = SystemClock.uptimeMillis() + timeoutMs; PendingCmd found = null;
long nowTime; synchronized (mPendingCmds) {
Response found = null; for (PendingCmd pendingCmd : mPendingCmds) {
while (true) { if (pendingCmd.cmdNum == cmdNum) {
synchronized (mResponses) { found = pendingCmd;
for (Response response : mResponses) { break;
if (response.cmdNum == cmdNum) {
found = response;
// how many response fragments are left
switch (response.responses.size()) {
case 0: // haven't got any - must wait
break;
case 1: // last one - remove this from the master list
mResponses.remove(response); // fall through
default: // take one and move on
response.request = origCmd;
return response.responses.remove();
}
}
}
nowTime = SystemClock.uptimeMillis();
if (endTime <= nowTime) {
Slog.e("NativeDaemonConnector.ResponseQueue",
"Timeout waiting for response");
return null;
}
/* pre-allocate so we have something unique to wait on */
if (found == null) {
found = new Response(cmdNum, origCmd);
mResponses.add(found);
} }
} }
try { if (found == null) {
synchronized (found) { found = new PendingCmd(cmdNum, origCmd);
found.wait(endTime - nowTime); mPendingCmds.add(found);
}
} catch (InterruptedException e) {
// loop around to check if we're done or if it's time to stop waiting
} }
found.availableResponseCount--;
// if a matching add call has already retrieved this we can remove this
// instance from our list
if (found.availableResponseCount == 0) mPendingCmds.remove(found);
} }
NativeDaemonEvent result = null;
try {
result = found.responses.poll(timeoutMs, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {}
if (result == null) {
Slog.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response");
}
return result;
} }
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("Pending requests:"); pw.println("Pending requests:");
synchronized (mResponses) { synchronized (mPendingCmds) {
for (Response response : mResponses) { for (PendingCmd pendingCmd : mPendingCmds) {
pw.println(" Cmd " + response.cmdNum + " - " + response.request); pw.println(" Cmd " + pendingCmd.cmdNum + " - " + pendingCmd.request);
} }
} }
} }