Skip to content

Commit 3be123c

Browse files
jiyongpthestinger
authored andcommitted
Refactor Memory Balloon control routines
* Created MemBalloonController as the single place for all routines * Stop using binder; it actually wasn't a binder object. * The periodic inflation is handled using scheduled executor service Bug: 402025547 Bug: 392791968 Test: intentionally set the period to 5s (for testing purpose), start the VM, go to background, and then come back. The log is as follows: 03-13 15:59:30.631 8298 8361 V VmTerminalApp: app resumed. deflating mem balloon to the minimum 03-13 15:59:51.863 8298 8361 V VmTerminalApp: inflating mem balloon to 10 % 03-13 15:59:56.863 8298 8361 V VmTerminalApp: inflating mem balloon to 15 % 03-13 16:00:01.864 8298 8361 V VmTerminalApp: inflating mem balloon to 20 % 03-13 16:00:06.864 8298 8361 V VmTerminalApp: inflating mem balloon to 25 % 03-13 16:00:08.315 8298 8361 V VmTerminalApp: app resumed. deflating mem balloon to the minimum 03-13 16:00:22.002 8298 8361 V VmTerminalApp: inflating mem balloon to 10 % 03-13 16:00:27.003 8298 8361 V VmTerminalApp: inflating mem balloon to 15 % 03-13 16:00:32.003 8298 8361 V VmTerminalApp: inflating mem balloon to 20 % 03-13 16:00:37.003 8298 8361 V VmTerminalApp: inflating mem balloon to 25 % 03-13 16:00:42.003 8298 8361 V VmTerminalApp: inflating mem balloon to 30 % Change-Id: I05e1f8d3635d14924fc1b7b4222d7bbd7e57b6c5
1 parent c035666 commit 3be123c

File tree

4 files changed

+99
-158
lines changed

4 files changed

+99
-158
lines changed

android/TerminalApp/java/com/android/virtualization/terminal/Application.kt

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,12 @@ package com.android.virtualization.terminal
1818
import android.app.Application as AndroidApplication
1919
import android.app.NotificationChannel
2020
import android.app.NotificationManager
21-
import android.content.ComponentName
2221
import android.content.Context
23-
import android.content.Intent
24-
import android.content.ServiceConnection
25-
import android.os.IBinder
26-
import androidx.lifecycle.DefaultLifecycleObserver
27-
import androidx.lifecycle.LifecycleOwner
28-
import androidx.lifecycle.ProcessLifecycleOwner
2922

3023
public class Application : AndroidApplication() {
3124
override fun onCreate() {
3225
super.onCreate()
3326
setupNotificationChannels()
34-
val lifecycleObserver = ApplicationLifecycleObserver()
35-
ProcessLifecycleOwner.get().lifecycle.addObserver(lifecycleObserver)
3627
}
3728

3829
private fun setupNotificationChannels() {
@@ -61,45 +52,4 @@ public class Application : AndroidApplication() {
6152

6253
fun getInstance(c: Context): Application = c.getApplicationContext() as Application
6354
}
64-
65-
/**
66-
* Observes application lifecycle events and interacts with the VmLauncherService to manage
67-
* virtual machine state based on application lifecycle transitions. This class binds to the
68-
* VmLauncherService and notifies it of application lifecycle events (onStart, onStop), allowing
69-
* the service to manage the VM accordingly.
70-
*/
71-
inner class ApplicationLifecycleObserver() : DefaultLifecycleObserver {
72-
private var vmLauncherService: VmLauncherService? = null
73-
private val connection =
74-
object : ServiceConnection {
75-
override fun onServiceConnected(className: ComponentName, service: IBinder) {
76-
val binder = service as VmLauncherService.VmLauncherServiceBinder
77-
vmLauncherService = binder.getService()
78-
}
79-
80-
override fun onServiceDisconnected(arg0: ComponentName) {
81-
vmLauncherService = null
82-
}
83-
}
84-
85-
override fun onCreate(owner: LifecycleOwner) {
86-
super.onCreate(owner)
87-
bindToVmLauncherService()
88-
}
89-
90-
override fun onStart(owner: LifecycleOwner) {
91-
super.onStart(owner)
92-
vmLauncherService?.processAppLifeCycleEvent(ApplicationLifeCycleEvent.APP_ON_START)
93-
}
94-
95-
override fun onStop(owner: LifecycleOwner) {
96-
vmLauncherService?.processAppLifeCycleEvent(ApplicationLifeCycleEvent.APP_ON_STOP)
97-
super.onStop(owner)
98-
}
99-
100-
fun bindToVmLauncherService() {
101-
val intent = Intent(this@Application, VmLauncherService::class.java)
102-
this@Application.bindService(intent, connection, 0) // No BIND_AUTO_CREATE
103-
}
104-
}
10555
}

android/TerminalApp/java/com/android/virtualization/terminal/ApplicationLifeCycleEvent.kt

Lines changed: 0 additions & 21 deletions
This file was deleted.
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright (C) 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.android.virtualization.terminal
17+
18+
import android.content.Context
19+
import android.system.virtualmachine.VirtualMachine
20+
import android.util.Log
21+
import androidx.lifecycle.DefaultLifecycleObserver
22+
import androidx.lifecycle.LifecycleOwner
23+
import androidx.lifecycle.ProcessLifecycleOwner
24+
import com.android.virtualization.terminal.MainActivity.Companion.TAG
25+
import java.util.concurrent.Executors
26+
import java.util.concurrent.ScheduledFuture
27+
import java.util.concurrent.TimeUnit
28+
29+
/**
30+
* MemBalloonController is responsible for adjusting the memory ballon size of a VM depending on
31+
* whether the app is visible or running in the background
32+
*/
33+
class MemBalloonController(val context: Context, val vm: VirtualMachine) {
34+
companion object {
35+
private const val INITIAL_PERCENT = 10
36+
private const val MAX_PERCENT = 50
37+
private const val INFLATION_STEP_PERCENT = 5
38+
private const val INFLATION_PERIOD_SEC = 60L
39+
}
40+
41+
private val executor =
42+
Executors.newSingleThreadScheduledExecutor(
43+
TerminalThreadFactory(context.getApplicationContext())
44+
)
45+
46+
private val observer =
47+
object : DefaultLifecycleObserver {
48+
49+
// If the app is started or resumed, give deflate the balloon to 0 to give maximum
50+
// available memory to the virtual machine
51+
override fun onResume(owner: LifecycleOwner) {
52+
ongoingInflation?.cancel(false)
53+
executor.submit({
54+
Log.v(TAG, "app resumed. deflating mem balloon to the minimum")
55+
vm.setMemoryBalloonByPercent(0)
56+
})
57+
}
58+
59+
// If the app goes into background, progressively inflate the balloon from
60+
// INITIAL_PERCENT until it reaches MAX_PERCENT
61+
override fun onStop(owner: LifecycleOwner) {
62+
ongoingInflation?.cancel(false)
63+
balloonPercent = INITIAL_PERCENT
64+
ongoingInflation =
65+
executor.scheduleAtFixedRate(
66+
{
67+
if (balloonPercent <= MAX_PERCENT) {
68+
Log.v(TAG, "inflating mem balloon to ${balloonPercent} %")
69+
vm.setMemoryBalloonByPercent(balloonPercent)
70+
balloonPercent += INFLATION_STEP_PERCENT
71+
} else {
72+
Log.v(TAG, "mem balloon is inflated to its max (${MAX_PERCENT} %)")
73+
ongoingInflation!!.cancel(false)
74+
}
75+
},
76+
0 /* initialDelay */,
77+
INFLATION_PERIOD_SEC,
78+
TimeUnit.SECONDS,
79+
)
80+
}
81+
}
82+
83+
private var balloonPercent = 0
84+
private var ongoingInflation: ScheduledFuture<*>? = null
85+
86+
fun start() {
87+
ProcessLifecycleOwner.get().lifecycle.addObserver(observer)
88+
}
89+
90+
fun stop() {
91+
ProcessLifecycleOwner.get().lifecycle.removeObserver(observer)
92+
executor.shutdown()
93+
}
94+
}

android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt

Lines changed: 5 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ import io.grpc.okhttp.OkHttpServerBuilder
5454
import java.io.File
5555
import java.io.FileOutputStream
5656
import java.io.IOException
57-
import java.lang.Math.min
5857
import java.lang.RuntimeException
5958
import java.net.InetSocketAddress
6059
import java.net.SocketAddress
@@ -65,12 +64,6 @@ import java.util.concurrent.Executors
6564
import java.util.concurrent.TimeUnit
6665

6766
class VmLauncherService : Service() {
68-
inner class VmLauncherServiceBinder : android.os.Binder() {
69-
fun getService(): VmLauncherService = this@VmLauncherService
70-
}
71-
72-
private val binder = VmLauncherServiceBinder()
73-
7467
private lateinit var executorService: ExecutorService
7568

7669
// TODO: using lateinit for some fields to avoid null
@@ -79,42 +72,6 @@ class VmLauncherService : Service() {
7972
private var server: Server? = null
8073
private var debianService: DebianServiceImpl? = null
8174
private var portNotifier: PortNotifier? = null
82-
private var mLock = Object()
83-
@GuardedBy("mLock") private var currentMemBalloonPercent = 0
84-
85-
@GuardedBy("mLock") private val inflateMemBalloonHandler = Handler(Looper.getMainLooper())
86-
private val inflateMemBalloonTask: Runnable =
87-
object : Runnable {
88-
override fun run() {
89-
synchronized(mLock) {
90-
if (
91-
currentMemBalloonPercent < INITIAL_MEM_BALLOON_PERCENT ||
92-
currentMemBalloonPercent > MAX_MEM_BALLOON_PERCENT
93-
) {
94-
Log.e(
95-
TAG,
96-
"currentBalloonPercent=$currentMemBalloonPercent is invalid," +
97-
" should be in range: " +
98-
"$INITIAL_MEM_BALLOON_PERCENT~$MAX_MEM_BALLOON_PERCENT",
99-
)
100-
return
101-
}
102-
// Increases the balloon size by MEM_BALLOON_PERCENT_STEP% every time
103-
if (currentMemBalloonPercent < MAX_MEM_BALLOON_PERCENT) {
104-
currentMemBalloonPercent =
105-
min(
106-
MAX_MEM_BALLOON_PERCENT,
107-
currentMemBalloonPercent + MEM_BALLOON_PERCENT_STEP,
108-
)
109-
virtualMachine?.setMemoryBalloonByPercent(currentMemBalloonPercent)
110-
inflateMemBalloonHandler.postDelayed(
111-
this,
112-
MEM_BALLOON_INFLATE_INTERVAL_MILLIS,
113-
)
114-
}
115-
}
116-
}
117-
}
11875

11976
interface VmLauncherServiceCallback {
12077
fun onVmStart()
@@ -127,45 +84,7 @@ class VmLauncherService : Service() {
12784
}
12885

12986
override fun onBind(intent: Intent?): IBinder? {
130-
return binder
131-
}
132-
133-
/**
134-
* Processes application lifecycle events and adjusts the virtual machine's memory balloon
135-
* accordingly.
136-
*
137-
* @param event The application lifecycle event.
138-
*/
139-
fun processAppLifeCycleEvent(event: ApplicationLifeCycleEvent) {
140-
when (event) {
141-
// When the app starts, reset the memory balloon to 0%.
142-
// This gives the app maximum available memory.
143-
ApplicationLifeCycleEvent.APP_ON_START -> {
144-
synchronized(mLock) {
145-
inflateMemBalloonHandler.removeCallbacks(inflateMemBalloonTask)
146-
currentMemBalloonPercent = 0
147-
virtualMachine?.setMemoryBalloonByPercent(currentMemBalloonPercent)
148-
}
149-
}
150-
ApplicationLifeCycleEvent.APP_ON_STOP -> {
151-
// When the app stops, inflate the memory balloon to INITIAL_MEM_BALLOON_PERCENT.
152-
// Inflate the balloon by MEM_BALLOON_PERCENT_STEP every
153-
// MEM_BALLOON_INFLATE_INTERVAL_MILLIS milliseconds until reaching
154-
// MAX_MEM_BALLOON_PERCENT of total memory. This allows the system to reclaim
155-
// memory while the app is in the background.
156-
synchronized(mLock) {
157-
currentMemBalloonPercent = INITIAL_MEM_BALLOON_PERCENT
158-
virtualMachine?.setMemoryBalloonByPercent(currentMemBalloonPercent)
159-
inflateMemBalloonHandler.postDelayed(
160-
inflateMemBalloonTask,
161-
MEM_BALLOON_INFLATE_INTERVAL_MILLIS,
162-
)
163-
}
164-
}
165-
else -> {
166-
Log.e(TAG, "unrecognized lifecycle event: $event")
167-
}
168-
}
87+
return null
16988
}
17089

17190
override fun onCreate() {
@@ -219,7 +138,11 @@ class VmLauncherService : Service() {
219138
ResultReceiver::class.java,
220139
)
221140

141+
val mbc = MemBalloonController(this, virtualMachine!!)
142+
mbc.start()
143+
222144
runner.exitStatus.thenAcceptAsync { success: Boolean ->
145+
mbc.stop()
223146
resultReceiver?.send(if (success) RESULT_STOP else RESULT_ERROR, null)
224147
stopSelf()
225148
}
@@ -491,11 +414,6 @@ class VmLauncherService : Service() {
491414
}
492415
}()
493416

494-
private const val INITIAL_MEM_BALLOON_PERCENT = 10
495-
private const val MAX_MEM_BALLOON_PERCENT = 50
496-
private const val MEM_BALLOON_INFLATE_INTERVAL_MILLIS = 60000L
497-
private const val MEM_BALLOON_PERCENT_STEP = 5
498-
499417
private fun getMyIntent(context: Context): Intent {
500418
return Intent(context.getApplicationContext(), VmLauncherService::class.java)
501419
}

0 commit comments

Comments
 (0)