Skip to content

Commit 59da677

Browse files
committed
ui: do not OOM when leaving log window open for a while
Signed-off-by: Jason A. Donenfeld <[email protected]>
1 parent 18a06b0 commit 59da677

File tree

1 file changed

+39
-14
lines changed

1 file changed

+39
-14
lines changed

ui/src/main/java/com/wireguard/android/activity/LogViewerActivity.kt

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import android.view.View
2727
import android.view.ViewGroup
2828
import androidx.activity.result.contract.ActivityResultContracts
2929
import androidx.appcompat.app.AppCompatActivity
30+
import androidx.collection.CircularArray
3031
import androidx.core.app.ShareCompat
3132
import androidx.core.content.res.ResourcesCompat
3233
import androidx.lifecycle.lifecycleScope
@@ -62,8 +63,8 @@ import java.util.regex.Pattern
6263
class LogViewerActivity : AppCompatActivity() {
6364
private lateinit var binding: LogViewerActivityBinding
6465
private lateinit var logAdapter: LogEntryAdapter
65-
private var logLines = arrayListOf<LogLine>()
66-
private var rawLogLines = StringBuffer()
66+
private var logLines = CircularArray<LogLine>()
67+
private var rawLogLines = CircularArray<String>()
6768
private var recyclerView: RecyclerView? = null
6869
private var saveButton: MenuItem? = null
6970
private val year by lazy {
@@ -113,7 +114,7 @@ class LogViewerActivity : AppCompatActivity() {
113114
binding.shareFab.setOnClickListener {
114115
revokeLastUri()
115116
val key = KeyPair().privateKey.toHex()
116-
LOGS[key] = rawLogLines.toString().toByteArray(Charsets.UTF_8)
117+
LOGS[key] = rawLogBytes()
117118
lastUri = Uri.parse("content://${BuildConfig.APPLICATION_ID}.exported-log/$key")
118119
val shareIntent = ShareCompat.IntentBuilder(this)
119120
.setType("text/plain")
@@ -150,13 +151,24 @@ class LogViewerActivity : AppCompatActivity() {
150151

151152
private val downloadsFileSaver = DownloadsFileSaver(this)
152153

154+
private fun rawLogBytes() : ByteArray {
155+
val builder = StringBuilder()
156+
for (i in 0 until rawLogLines.size()) {
157+
builder.append(rawLogLines[i])
158+
builder.append('\n')
159+
}
160+
val ret = builder.toString().toByteArray(Charsets.UTF_8)
161+
builder.clear()
162+
return ret
163+
}
164+
153165
private suspend fun saveLog() {
154166
var exception: Throwable? = null
155167
var outputFile: DownloadsFileSaver.DownloadsFile? = null
156168
withContext(Dispatchers.IO) {
157169
try {
158170
outputFile = downloadsFileSaver.save("wireguard-log.txt", "text/plain", true)
159-
outputFile?.outputStream?.write(rawLogLines.toString().toByteArray(Charsets.UTF_8))
171+
outputFile?.outputStream?.write(rawLogBytes())
160172
} catch (e: Throwable) {
161173
outputFile?.delete()
162174
exception = e
@@ -191,24 +203,27 @@ class LogViewerActivity : AppCompatActivity() {
191203
var priorModified = false
192204
val bufferedLogLines = arrayListOf<LogLine>()
193205
var timeout = 1000000000L / 2 // The timeout is initially small so that the view gets populated immediately.
206+
val MAX_LINES = (1 shl 17) - 1
207+
val MAX_BUFFERED_LINES = (1 shl 14) - 1
194208

195209
while (true) {
196210
val line = stdout.readLine() ?: break
197-
rawLogLines.append(line)
198-
rawLogLines.append('\n')
211+
if (rawLogLines.size() >= MAX_LINES)
212+
rawLogLines.popFirst()
213+
rawLogLines.addLast(line)
199214
val logLine = parseLine(line)
200215
if (logLine != null) {
201216
bufferedLogLines.add(logLine)
202217
} else {
203218
if (bufferedLogLines.isNotEmpty()) {
204219
bufferedLogLines.last().msg += "\n$line"
205-
} else if (logLines.isNotEmpty()) {
206-
logLines.last().msg += "\n$line"
220+
} else if (!logLines.isEmpty) {
221+
logLines[logLines.size() - 1].msg += "\n$line"
207222
priorModified = true
208223
}
209224
}
210225
val timeNow = System.nanoTime()
211-
if ((timeNow - timeLastNotify) < timeout && stdout.ready())
226+
if (bufferedLogLines.size < MAX_BUFFERED_LINES && (timeNow - timeLastNotify) < timeout && stdout.ready())
212227
continue
213228
timeout = 1000000000L * 5 / 2 // Increase the timeout after the initial view has something in it.
214229
timeLastNotify = timeNow
@@ -219,13 +234,23 @@ class LogViewerActivity : AppCompatActivity() {
219234
logAdapter.notifyItemChanged(posStart - 1)
220235
priorModified = false
221236
}
222-
logLines.addAll(bufferedLogLines)
237+
val fullLen = logLines.size() + bufferedLogLines.size
238+
if (fullLen >= MAX_LINES) {
239+
val numToRemove = fullLen - MAX_LINES + 1
240+
logLines.removeFromStart(numToRemove)
241+
logAdapter.notifyItemRangeRemoved(0, numToRemove)
242+
posStart -= numToRemove
243+
244+
}
245+
for (bufferedLine in bufferedLogLines) {
246+
logLines.addLast(bufferedLine)
247+
}
223248
bufferedLogLines.clear()
224-
logAdapter.notifyItemRangeInserted(posStart, logLines.size - posStart)
225-
posStart = logLines.size
249+
logAdapter.notifyItemRangeInserted(posStart, logLines.size() - posStart)
250+
posStart = logLines.size()
226251

227252
if (isScrolledToBottomAlready) {
228-
recyclerView?.scrollToPosition(logLines.size - 1)
253+
recyclerView?.scrollToPosition(logLines.size() - 1)
229254
}
230255
}
231256
}
@@ -279,7 +304,7 @@ class LogViewerActivity : AppCompatActivity() {
279304
}
280305
}
281306

282-
override fun getItemCount() = logLines.size
307+
override fun getItemCount() = logLines.size()
283308

284309
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
285310
val view = LayoutInflater.from(parent.context)

0 commit comments

Comments
 (0)