Merge changes from topic "lmkd_async_msg"

* changes:
  Support unsolicited message from lmkd
  Implement ActivityManager to LMKD reconnect functionality
This commit is contained in:
Jing Ji
2019-12-13 00:27:38 +00:00
committed by Gerrit Code Review
2 changed files with 432 additions and 91 deletions

View File

@@ -0,0 +1,300 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.am;
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.os.MessageQueue;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import libcore.io.IoUtils;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
/**
* Lmkd connection to communicate with lowmemorykiller daemon.
*/
public class LmkdConnection {
private static final String TAG = TAG_WITH_CLASS_NAME ? "LmkdConnection" : TAG_AM;
// lmkd reply max size in bytes
private static final int LMKD_REPLY_MAX_SIZE = 12;
// connection listener interface
interface LmkdConnectionListener {
boolean onConnect(OutputStream ostream);
void onDisconnect();
/**
* Check if received reply was expected (reply to an earlier request)
*
* @param replyBuf The buffer provided in exchange() to receive the reply.
* It can be used by exchange() caller to store reply-specific
* tags for later use in isReplyExpected() to verify if
* received packet is the expected reply.
* @param dataReceived The buffer holding received data
* @param receivedLen Size of the data received
*/
boolean isReplyExpected(ByteBuffer replyBuf, ByteBuffer dataReceived,
int receivedLen);
/**
* Handle the received message if it's unsolicited.
*
* @param dataReceived The buffer holding received data
* @param receivedLen Size of the data received
* @return True if the message has been handled correctly, false otherwise.
*/
boolean handleUnsolicitedMessage(ByteBuffer dataReceived, int receivedLen);
}
private final MessageQueue mMsgQueue;
// lmkd connection listener
private final LmkdConnectionListener mListener;
// mutex to synchronize access to the socket
private final Object mLmkdSocketLock = new Object();
// socket to communicate with lmkd
@GuardedBy("mLmkdSocketLock")
private LocalSocket mLmkdSocket = null;
// socket I/O streams
@GuardedBy("mLmkdSocketLock")
private OutputStream mLmkdOutputStream = null;
@GuardedBy("mLmkdSocketLock")
private InputStream mLmkdInputStream = null;
// buffer to store incoming data
private final ByteBuffer mInputBuf =
ByteBuffer.allocate(LMKD_REPLY_MAX_SIZE);
// object to protect mReplyBuf and to wait/notify when reply is received
private final Object mReplyBufLock = new Object();
// reply buffer
@GuardedBy("mReplyBufLock")
private ByteBuffer mReplyBuf = null;
//////////////////// END FIELDS ////////////////////
LmkdConnection(MessageQueue msgQueue, LmkdConnectionListener listener) {
mMsgQueue = msgQueue;
mListener = listener;
}
boolean connect() {
synchronized (mLmkdSocketLock) {
if (mLmkdSocket != null) {
return true;
}
// temporary sockets and I/O streams
final LocalSocket socket = openSocket();
if (socket == null) {
Slog.w(TAG, "Failed to connect to lowmemorykiller, retry later");
return false;
}
final OutputStream ostream;
final InputStream istream;
try {
ostream = socket.getOutputStream();
istream = socket.getInputStream();
} catch (IOException ex) {
IoUtils.closeQuietly(socket);
return false;
}
// execute onConnect callback
if (mListener != null && !mListener.onConnect(ostream)) {
Slog.w(TAG, "Failed to communicate with lowmemorykiller, retry later");
IoUtils.closeQuietly(socket);
return false;
}
// connection established
mLmkdSocket = socket;
mLmkdOutputStream = ostream;
mLmkdInputStream = istream;
mMsgQueue.addOnFileDescriptorEventListener(mLmkdSocket.getFileDescriptor(),
EVENT_INPUT | EVENT_ERROR,
new MessageQueue.OnFileDescriptorEventListener() {
public int onFileDescriptorEvents(FileDescriptor fd, int events) {
return fileDescriptorEventHandler(fd, events);
}
}
);
mLmkdSocketLock.notifyAll();
}
return true;
}
private int fileDescriptorEventHandler(FileDescriptor fd, int events) {
if (mListener == null) {
return 0;
}
if ((events & EVENT_INPUT) != 0) {
processIncomingData();
}
if ((events & EVENT_ERROR) != 0) {
synchronized (mLmkdSocketLock) {
// stop listening on this socket
mMsgQueue.removeOnFileDescriptorEventListener(
mLmkdSocket.getFileDescriptor());
IoUtils.closeQuietly(mLmkdSocket);
mLmkdSocket = null;
}
// wake up reply waiters if any
synchronized (mReplyBufLock) {
if (mReplyBuf != null) {
mReplyBuf = null;
mReplyBufLock.notifyAll();
}
}
// notify listener
mListener.onDisconnect();
return 0;
}
return (EVENT_INPUT | EVENT_ERROR);
}
private void processIncomingData() {
int len = read(mInputBuf);
if (len > 0) {
synchronized (mReplyBufLock) {
if (mReplyBuf != null) {
if (mListener.isReplyExpected(mReplyBuf, mInputBuf, len)) {
// copy into reply buffer
mReplyBuf.put(mInputBuf.array(), 0, len);
mReplyBuf.rewind();
// wakeup the waiting thread
mReplyBufLock.notifyAll();
} else if (!mListener.handleUnsolicitedMessage(mInputBuf, len)) {
// received unexpected packet
// treat this as an error
mReplyBuf = null;
mReplyBufLock.notifyAll();
Slog.e(TAG, "Received an unexpected packet from lmkd");
}
} else if (!mListener.handleUnsolicitedMessage(mInputBuf, len)) {
// received asynchronous communication from lmkd
// but we don't recognize it.
Slog.w(TAG, "Received an unexpected packet from lmkd");
}
}
}
}
boolean isConnected() {
synchronized (mLmkdSocketLock) {
return (mLmkdSocket != null);
}
}
boolean waitForConnection(long timeoutMs) {
synchronized (mLmkdSocketLock) {
if (mLmkdSocket != null) {
return true;
}
try {
mLmkdSocketLock.wait(timeoutMs);
return (mLmkdSocket != null);
} catch (InterruptedException e) {
return false;
}
}
}
private LocalSocket openSocket() {
final LocalSocket socket;
try {
socket = new LocalSocket(LocalSocket.SOCKET_SEQPACKET);
socket.connect(
new LocalSocketAddress("lmkd",
LocalSocketAddress.Namespace.RESERVED));
} catch (IOException ex) {
Slog.e(TAG, "Connection failed: " + ex.toString());
return null;
}
return socket;
}
private boolean write(ByteBuffer buf) {
synchronized (mLmkdSocketLock) {
try {
mLmkdOutputStream.write(buf.array(), 0, buf.position());
} catch (IOException ex) {
return false;
}
return true;
}
}
private int read(ByteBuffer buf) {
synchronized (mLmkdSocketLock) {
try {
return mLmkdInputStream.read(buf.array(), 0, buf.array().length);
} catch (IOException ex) {
}
return -1;
}
}
/**
* Exchange a request/reply packets with lmkd
*
* @param req The buffer holding the request data to be sent
* @param repl The buffer to receive the reply
*/
public boolean exchange(ByteBuffer req, ByteBuffer repl) {
if (repl == null) {
return write(req);
}
boolean result = false;
// set reply buffer to user-defined one to fill it
synchronized (mReplyBufLock) {
mReplyBuf = repl;
if (write(req)) {
try {
// wait for the reply
mReplyBufLock.wait();
result = (mReplyBuf != null);
} catch (InterruptedException ie) {
result = false;
}
}
// reset reply buffer
mReplyBuf = null;
}
return result;
}
}

View File

@@ -57,8 +57,6 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.res.Resources;
import android.graphics.Point;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.os.AppZygote;
import android.os.Binder;
import android.os.Build;
@@ -103,11 +101,8 @@ import com.android.server.wm.WindowManagerService;
import dalvik.system.VMRuntime;
import libcore.io.IoUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.ByteBuffer;
@@ -118,11 +113,6 @@ import java.util.List;
/**
* Activity manager code dealing with processes.
*
* Method naming convention:
* <ul>
* <li> Methods suffixed with "LS" should be called within the {@link #sLmkdSocketLock} lock.
* </ul>
*/
public final class ProcessList {
static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM;
@@ -263,11 +253,16 @@ public final class ProcessList {
// LMK_PROCREMOVE <pid>
// LMK_PROCPURGE
// LMK_GETKILLCNT
// LMK_PROCKILL
static final byte LMK_TARGET = 0;
static final byte LMK_PROCPRIO = 1;
static final byte LMK_PROCREMOVE = 2;
static final byte LMK_PROCPURGE = 3;
static final byte LMK_GETKILLCNT = 4;
static final byte LMK_PROCKILL = 5; // Note: this is an unsolicated command
// lmkd reconnect delay in msecs
private static final long LMKD_RECONNECT_DELAY_MS = 1000;
ActivityManagerService mService = null;
@@ -303,16 +298,9 @@ public final class ProcessList {
private boolean mHaveDisplaySize;
private static Object sLmkdSocketLock = new Object();
private static LmkdConnection sLmkdConnection = null;
@GuardedBy("sLmkdSocketLock")
private static LocalSocket sLmkdSocket;
@GuardedBy("sLmkdSocketLock")
private static OutputStream sLmkdOutputStream;
@GuardedBy("sLmkdSocketLock")
private static InputStream sLmkdInputStream;
private boolean mOomLevelsSet = false;
/**
* Temporary to avoid allocations. Protected by main lock.
@@ -369,6 +357,12 @@ public final class ProcessList {
ActiveUids mActiveUids;
/**
* The listener who is intereted with the lmkd kills.
*/
@GuardedBy("mService")
private LmkdKillListener mLmkdKillListener = null;
/**
* The currently running isolated processes.
*/
@@ -387,6 +381,13 @@ public final class ProcessList {
private PlatformCompat mPlatformCompat = null;
interface LmkdKillListener {
/**
* Called when there is a process kill by lmkd.
*/
void onLmkdKillOccurred(int pid, int uid);
}
final class IsolatedUidRange {
@VisibleForTesting
public final int mFirstUid;
@@ -539,6 +540,8 @@ public final class ProcessList {
final class KillHandler extends Handler {
static final int KILL_PROCESS_GROUP_MSG = 4000;
static final int LMKD_RECONNECT_MSG = 4001;
static final int LMKD_PROC_KILLED_MSG = 4002;
public KillHandler(Looper looper) {
super(looper, null, true);
@@ -552,6 +555,18 @@ public final class ProcessList {
Process.killProcessGroup(msg.arg1 /* uid */, msg.arg2 /* pid */);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case LMKD_RECONNECT_MSG:
if (!sLmkdConnection.connect()) {
Slog.i(TAG, "Failed to connect to lmkd, retry after "
+ LMKD_RECONNECT_DELAY_MS + " ms");
// retry after LMKD_RECONNECT_DELAY_MS
sKillHandler.sendMessageDelayed(sKillHandler.obtainMessage(
KillHandler.LMKD_RECONNECT_MSG), LMKD_RECONNECT_DELAY_MS);
}
break;
case LMKD_PROC_KILLED_MSG:
handleLmkdProcKilled(msg.arg1 /* pid */, msg.arg2 /* uid */);
break;
default:
super.handleMessage(msg);
@@ -579,6 +594,50 @@ public final class ProcessList {
THREAD_PRIORITY_BACKGROUND, true /* allowIo */);
sKillThread.start();
sKillHandler = new KillHandler(sKillThread.getLooper());
sLmkdConnection = new LmkdConnection(sKillThread.getLooper().getQueue(),
new LmkdConnection.LmkdConnectionListener() {
@Override
public boolean onConnect(OutputStream ostream) {
Slog.i(TAG, "Connection with lmkd established");
return onLmkdConnect(ostream);
}
@Override
public void onDisconnect() {
Slog.w(TAG, "Lost connection to lmkd");
// start reconnection after delay to let lmkd restart
sKillHandler.sendMessageDelayed(sKillHandler.obtainMessage(
KillHandler.LMKD_RECONNECT_MSG), LMKD_RECONNECT_DELAY_MS);
}
@Override
public boolean isReplyExpected(ByteBuffer replyBuf,
ByteBuffer dataReceived, int receivedLen) {
// compare the preambule (currently one integer) to check if
// this is the reply packet we are waiting for
return (receivedLen == replyBuf.array().length
&& dataReceived.getInt(0) == replyBuf.getInt(0));
}
@Override
public boolean handleUnsolicitedMessage(ByteBuffer dataReceived,
int receivedLen) {
if (receivedLen < 4) {
return false;
}
switch (dataReceived.getInt(0)) {
case LMK_PROCKILL:
if (receivedLen != 12) {
return false;
}
sKillHandler.obtainMessage(KillHandler.LMKD_PROC_KILLED_MSG,
dataReceived.getInt(4), dataReceived.getInt(8))
.sendToTarget();
return true;
default:
return false;
}
}
}
);
}
}
@@ -684,6 +743,7 @@ public final class ProcessList {
writeLmkd(buf, null);
SystemProperties.set("sys.sysctl.extra_free_kbytes", Integer.toString(reserve));
mOomLevelsSet = true;
}
// GB: 2048,3072,4096,6144,7168,8192
// HC: 8192,10240,12288,14336,16384,20480
@@ -1223,93 +1283,50 @@ public final class ProcessList {
buf.putInt(LMK_GETKILLCNT);
buf.putInt(min_oom_adj);
buf.putInt(max_oom_adj);
if (writeLmkd(buf, repl)) {
int i = repl.getInt();
if (i != LMK_GETKILLCNT) {
Slog.e("ActivityManager", "Failed to get kill count, code mismatch");
return null;
}
// indicate what we are waiting for
repl.putInt(LMK_GETKILLCNT);
repl.rewind();
if (writeLmkd(buf, repl) && repl.getInt() == LMK_GETKILLCNT) {
return new Integer(repl.getInt());
}
return null;
}
@GuardedBy("sLmkdSocketLock")
private static boolean openLmkdSocketLS() {
boolean onLmkdConnect(OutputStream ostream) {
try {
sLmkdSocket = new LocalSocket(LocalSocket.SOCKET_SEQPACKET);
sLmkdSocket.connect(
new LocalSocketAddress("lmkd",
LocalSocketAddress.Namespace.RESERVED));
sLmkdOutputStream = sLmkdSocket.getOutputStream();
sLmkdInputStream = sLmkdSocket.getInputStream();
} catch (IOException ex) {
Slog.w(TAG, "lowmemorykiller daemon socket open failed");
sLmkdSocket = null;
return false;
}
return true;
}
// Never call directly, use writeLmkd() instead
@GuardedBy("sLmkdSocketLock")
private static boolean writeLmkdCommandLS(ByteBuffer buf) {
try {
sLmkdOutputStream.write(buf.array(), 0, buf.position());
} catch (IOException ex) {
Slog.w(TAG, "Error writing to lowmemorykiller socket");
IoUtils.closeQuietly(sLmkdSocket);
sLmkdSocket = null;
return false;
}
return true;
}
// Never call directly, use writeLmkd() instead
@GuardedBy("sLmkdSocketLock")
private static boolean readLmkdReplyLS(ByteBuffer buf) {
int len;
try {
len = sLmkdInputStream.read(buf.array(), 0, buf.array().length);
if (len == buf.array().length) {
return true;
// Purge any previously registered pids
ByteBuffer buf = ByteBuffer.allocate(4);
buf.putInt(LMK_PROCPURGE);
ostream.write(buf.array(), 0, buf.position());
if (mOomLevelsSet) {
// Reset oom_adj levels
buf = ByteBuffer.allocate(4 * (2 * mOomAdj.length + 1));
buf.putInt(LMK_TARGET);
for (int i = 0; i < mOomAdj.length; i++) {
buf.putInt((mOomMinFree[i] * 1024) / PAGE_SIZE);
buf.putInt(mOomAdj[i]);
}
ostream.write(buf.array(), 0, buf.position());
}
} catch (IOException ex) {
Slog.w(TAG, "Error reading from lowmemorykiller socket");
return false;
}
IoUtils.closeQuietly(sLmkdSocket);
sLmkdSocket = null;
return false;
return true;
}
private static boolean writeLmkd(ByteBuffer buf, ByteBuffer repl) {
synchronized (sLmkdSocketLock) {
for (int i = 0; i < 3; i++) {
if (sLmkdSocket == null) {
if (openLmkdSocketLS() == false) {
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
}
continue;
}
if (!sLmkdConnection.isConnected()) {
// try to connect immediately and then keep retrying
sKillHandler.sendMessage(
sKillHandler.obtainMessage(KillHandler.LMKD_RECONNECT_MSG));
// Purge any previously registered pids
ByteBuffer purge_buf = ByteBuffer.allocate(4);
purge_buf.putInt(LMK_PROCPURGE);
if (writeLmkdCommandLS(purge_buf) == false) {
// Write failed, skip the rest and retry
continue;
}
}
if (writeLmkdCommandLS(buf) && (repl == null || readLmkdReplyLS(repl))) {
return true;
}
// wait for connection retrying 3 times (up to 3 seconds)
if (!sLmkdConnection.waitForConnection(3 * LMKD_RECONNECT_DELAY_MS)) {
return false;
}
}
return false;
return sLmkdConnection.exchange(buf, repl);
}
static void killProcessGroup(int uid, int pid) {
@@ -3214,4 +3231,28 @@ public final class ProcessList {
mService.doStopUidLocked(uidRec.uid, uidRec);
}
}
void setLmkdKillListener(final LmkdKillListener listener) {
synchronized (mService) {
mLmkdKillListener = listener;
}
}
private void handleLmkdProcKilled(final int pid, final int uid) {
// Log only now
if (DEBUG_PROCESSES) {
Slog.i(TAG, "lmkd kill: pid=" + pid + " uid=" + uid);
}
if (mService == null) {
return;
}
// Notify any interesed party regarding the lmkd kills
synchronized (mService) {
final LmkdKillListener listener = mLmkdKillListener;
if (listener != null) {
mService.mHandler.post(()-> listener.onLmkdKillOccurred(pid, uid));
}
}
}
}