Skip to content
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

Possible memory leak in keyboard view #267

Closed
Glitchy-Tozier opened this issue Jan 28, 2021 · 36 comments
Closed

Possible memory leak in keyboard view #267

Glitchy-Tozier opened this issue Jan 28, 2021 · 36 comments
Assignees
Labels
bug A bug report bug-confirmed A confirmed and reproducible bug report
Milestone

Comments

@Glitchy-Tozier
Copy link
Collaborator

Glitchy-Tozier commented Jan 28, 2021

Version: 0.3.5
Source: Playstore
Android 10
Samsung Galaxy s9

~~~ 1611851663324.stacktrace ~~~

java.lang.OutOfMemoryError: Failed to allocate a 24 byte allocation with 2226872 free bytes and 2174KB until OOM, target footprint 268435456, growth limit 268435456; failed due to fragmentation (largest possible contiguous allocation 46399488 bytes)
	at java.lang.StringFactory.newStringFromChars(StringFactory.java:260)
	at java.lang.StringBuilder.toString(StringBuilder.java:413)
	at dev.patrickgold.florisboard.ime.media.emoji.EmojiKeyData.getCodePointsAsString(EmojiKeyData.kt:40)
	at dev.patrickgold.florisboard.ime.media.emoji.EmojiLayoutDataKt.parseRawEmojiSpecsFile(EmojiLayoutData.kt:139)
	at dev.patrickgold.florisboard.ime.media.emoji.EmojiKeyboardView$1.invokeSuspend(EmojiKeyboardView.kt:68)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
@Glitchy-Tozier Glitchy-Tozier added the bug A bug report label Jan 28, 2021
@patrickgold
Copy link
Member

Hah the good ol' OutOfMemoryError :)

I will investigate into this. Does this always occur when going into the emoji view or does this only occur after some time has passed using FlorisBoard continuously (without a service restart)? If it is the latter, this could point to a memory leak, which is problematic and can cause all sorts of issues.

@Glitchy-Tozier
Copy link
Collaborator Author

It's definitely not the emoji-option, so probably the second thing.

Also, this is the first time this ever happened to me.

@patrickgold
Copy link
Member

Are you able to reproduce this bug or did it just randomly appear once and now it is gone again?

@Glitchy-Tozier
Copy link
Collaborator Author

It just randomly happened once and now everything works finde again.

@patrickgold
Copy link
Member

This makes it definitely harder to fix this crash. I just did a memory stress test on my testing device but couldn't get the app to crash with an OutOfMemoryError. Either this was a very unlucky constellation where the system had low resources and FlorisBoard was very busy, or it is a very sneaky bug which only occurs when some special conditions are met. I will place this on my investigation list, can't say when this will be fixed because it is not reproducible for me.

@patrickgold patrickgold added this to the 0.4.0 milestone Jan 28, 2021
@patrickgold patrickgold added the not reproducible An issue which cannot be reproduced by the dev team label Jan 28, 2021
@Glitchy-Tozier
Copy link
Collaborator Author

Glitchy-Tozier commented Jan 28, 2021

I'll do my best to contact you if and when it occurs again.

@patrickgold patrickgold changed the title Florisboard Crashed Possible memery leak in keyboard view Jan 29, 2021
@Glitchy-Tozier
Copy link
Collaborator Author

Sooo... Here you go:

It happened while I was trying tp type sth. on Whatsapp while an overlay-youtube-video from Firefox was playing. The keyboard had lagged for a few minutes already (I think). It loaded for like a second, then disappeared. "Florisboard has stopped working".


java.lang.OutOfMemoryError: Failed to allocate a 16 byte allocation with 1106760 free bytes and 1080KB until OOM, target footprint 268435456, growth limit 268435456; failed due to fragmentation (largest possible contiguous allocation 126615552 bytes)
	at libcore.util.NativeAllocationRegistry.registerNativeAllocation(NativeAllocationRegistry.java:243)
	at java.util.regex.Matcher.usePattern(Matcher.java:239)
	at java.util.regex.Matcher.<init>(Matcher.java:185)
	at java.util.regex.Pattern.matcher(Pattern.java:1034)
	at kotlin.text.Regex.containsMatchIn(Regex.kt:110)
	at dev.patrickgold.florisboard.ime.media.emoji.EmojiLayoutDataKt.parseRawEmojiSpecsFile(EmojiLayoutData.kt:128)
	at dev.patrickgold.florisboard.ime.media.emoji.EmojiKeyboardView$1.invokeSuspend(EmojiKeyboardView.kt:68)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)

@patrickgold
Copy link
Member

What really makes me wonder is why the emoji file is getting parsed in the background, even though you have been actively using FlorisBoard for quite a time. Normally, the emoji file gets parsed once (at the beginning of the Service lifetime) and that's it. So I am very concerned that something is resetting the layout cache and triggering an emoji reload over and over. As this file is quite big, this can very fast grow to a memory leak.

Are you by any chance using adaptive themes? I have some suspicion that this could be (one of) the reason(s) for the internal rebuild trigger.

@Glitchy-Tozier
Copy link
Collaborator Author

As this file is quite big, this can very fast grow to a memory leak.

What exactly is a memory leak?

Are you by any chance using adaptive themes? I have some suspicion that this could be (one of) the reason(s) for the internal rebuild trigger.

At the time of my first report, I did use adaptive themes (i think).
However, this occured while I way using a custom light theme.
Screenshot_20210131-075716_GitHub.jpg

@patrickgold
Copy link
Member

What exactly is a memory leak?

Under normal circumstances, an application dynamically allocates and dis-allocates memory from the system while in use. Some actions which happen often allocate memory often for temporary processing etc., but this temporary objects should get cleared. As Android runs on Java, memory dis-allocation is handled by the Garbage collector (GC). The GC checks every now and then if some allocated objects have any references. If they do, the GC skips this objects, else it dis-allocates the object and thus creating free memory again. If the app's logic has some flaw where not all references are cleared, the GC will not dis-allocate the memory and thus over time the RAM usage goes up and up but never down, until it hits the memory limit, where Java throws the OutOfMemoryError. This is exactly what is happening here.

At the time of my first report, I did use adaptive themes (i think).
However, this occured while I way using a custom light theme.

Hmm maybe themes in general do trigger an emoji re-parse, I will investigate the theme setter function in the core.

@Glitchy-Tozier
Copy link
Collaborator Author

Under normal circumstances, ...

Thank you for the thorough explanation!

Hmm maybe themes in general do trigger an emoji re-parse, I will investigate the theme setter function in the core.

Does that mean it shouldn't happen with the default theme?

@patrickgold
Copy link
Member

patrickgold commented Jan 31, 2021

Does that mean it shouldn't happen with the default theme?

Nope, it will happen with any theme set I think, if the problem is the core theme rebuild logic.

@Glitchy-Tozier
Copy link
Collaborator Author

I see.

This hasn't happened to anyone else, right?

@patrickgold
Copy link
Member

Not that I am aware of at least. I will conduct further memory stress tests on my older physical devices, maybe I am able to catch the source of the leak.

@Glitchy-Tozier
Copy link
Collaborator Author

The weird thing is: My phone is pretty good, actually. Making it confusing to me that the bug only seems to happen here.
Maybe it's influenced by rotating the screen a lot? (which is something I do)

@patrickgold
Copy link
Member

Maybe it's influenced by rotating the screen a lot?

Yep this could very likely be the reason of this bug and would explain the layout/emoji data re-parse, as an orientation change causes an internal service re-build.

@Glitchy-Tozier
Copy link
Collaborator Author

Yay :)


java.lang.OutOfMemoryError: Failed to allocate a 144 byte allocation with 1628824 free bytes and 1590KB until OOM, target footprint 268435456, growth limit 268435456; failed due to fragmentation (largest possible contiguous allocation 58720256 bytes)
	at kotlin.reflect.jvm.internal.impl.protobuf.ByteString$Output.<init>(ByteString.java:789)
	at kotlin.reflect.jvm.internal.impl.protobuf.ByteString.newOutput(ByteString.java:751)
	at kotlin.reflect.jvm.internal.impl.metadata.ProtoBuf$Function.<init>(ProtoBuf.java:14349)
	at kotlin.reflect.jvm.internal.impl.metadata.ProtoBuf$Function.<init>(ProtoBuf.java:14321)
	at kotlin.reflect.jvm.internal.impl.metadata.ProtoBuf$Function$1.parsePartialFrom(ProtoBuf.java:14516)
	at kotlin.reflect.jvm.internal.impl.metadata.ProtoBuf$Function$1.parsePartialFrom(ProtoBuf.java:14511)
	at kotlin.reflect.jvm.internal.impl.protobuf.CodedInputStream.readMessage(CodedInputStream.java:495)
	at kotlin.reflect.jvm.internal.impl.metadata.ProtoBuf$Class.<init>(ProtoBuf.java:8633)
	at kotlin.reflect.jvm.internal.impl.metadata.ProtoBuf$Class.<init>(ProtoBuf.java:8499)
	at kotlin.reflect.jvm.internal.impl.metadata.ProtoBuf$Class$1.parsePartialFrom(ProtoBuf.java:8785)
	at kotlin.reflect.jvm.internal.impl.metadata.ProtoBuf$Class$1.parsePartialFrom(ProtoBuf.java:8780)
	at kotlin.reflect.jvm.internal.impl.protobuf.CodedInputStream.readMessage(CodedInputStream.java:495)
	at kotlin.reflect.jvm.internal.impl.metadata.ProtoBuf$PackageFragment.<init>(ProtoBuf.java:22558)
	at kotlin.reflect.jvm.internal.impl.metadata.ProtoBuf$PackageFragment.<init>(ProtoBuf.java:22466)
	at kotlin.reflect.jvm.internal.impl.metadata.ProtoBuf$PackageFragment$1.parsePartialFrom(ProtoBuf.java:22588)
	at kotlin.reflect.jvm.internal.impl.metadata.ProtoBuf$PackageFragment$1.parsePartialFrom(ProtoBuf.java:22583)
	at kotlin.reflect.jvm.internal.impl.protobuf.AbstractParser.parsePartialFrom(AbstractParser.java:192)
	at kotlin.reflect.jvm.internal.impl.protobuf.AbstractParser.parseFrom(AbstractParser.java:209)
	at kotlin.reflect.jvm.internal.impl.protobuf.AbstractParser.parseFrom(AbstractParser.java:49)
	at kotlin.reflect.jvm.internal.impl.metadata.ProtoBuf$PackageFragment.parseFrom(ProtoBuf.java:22802)
	at kotlin.reflect.jvm.internal.impl.serialization.deserialization.builtins.BuiltInsPackageFragmentImpl$Companion.create(BuiltInsPackageFragmentImpl.kt:49)
	at kotlin.reflect.jvm.internal.impl.builtins.jvm.JvmBuiltInsPackageFragmentProvider.findPackage(JvmBuiltInsPackageFragmentProvider.kt:61)
	at kotlin.reflect.jvm.internal.impl.serialization.deserialization.AbstractDeserializedPackageFragmentProvider$fragments$1.invoke(AbstractDeserializedPackageFragmentProvider.kt:34)
	at kotlin.reflect.jvm.internal.impl.serialization.deserialization.AbstractDeserializedPackageFragmentProvider$fragments$1.invoke(AbstractDeserializedPackageFragmentProvider.kt:26)
	at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$MapBasedMemoizedFunction.invoke(LockBasedStorageManager.java:527)
	at kotlin.reflect.jvm.internal.impl.serialization.deserialization.AbstractDeserializedPackageFragmentProvider.getPackageFragments(AbstractDeserializedPackageFragmentProvider.kt:41)
	at kotlin.reflect.jvm.internal.impl.descriptors.impl.CompositePackageFragmentProvider.getPackageFragments(CompositePackageFragmentProvider.kt:31)
	at kotlin.reflect.jvm.internal.impl.descriptors.impl.CompositePackageFragmentProvider.getPackageFragments(CompositePackageFragmentProvider.kt:31)
	at kotlin.reflect.jvm.internal.impl.descriptors.impl.LazyPackageViewDescriptorImpl$fragments$2.invoke(LazyPackageViewDescriptorImpl.kt:37)
	at kotlin.reflect.jvm.internal.impl.descriptors.impl.LazyPackageViewDescriptorImpl$fragments$2.invoke(LazyPackageViewDescriptorImpl.kt:30)
	at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedLazyValue.invoke(LockBasedStorageManager.java:370)
	at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedNotNullLazyValue.invoke(LockBasedStorageManager.java:489)
	at kotlin.reflect.jvm.internal.impl.storage.StorageKt.getValue(storage.kt:42)
	at kotlin.reflect.jvm.internal.impl.descriptors.impl.LazyPackageViewDescriptorImpl.getFragments(Unknown Source:7)
	at kotlin.reflect.jvm.internal.impl.descriptors.impl.LazyPackageViewDescriptorImpl$memberScope$1.invoke(LazyPackageViewDescriptorImpl.kt:41)
	at kotlin.reflect.jvm.internal.impl.descriptors.impl.LazyPackageViewDescriptorImpl$memberScope$1.invoke(LazyPackageViewDescriptorImpl.kt:30)
	at kotlin.reflect.jvm.internal.impl.resolve.scopes.LazyScopeAdapter$lazyScope$1.invoke(LazyScopeAdapter.kt:28)
	at kotlin.reflect.jvm.internal.impl.resolve.scopes.LazyScopeAdapter$lazyScope$1.invoke(LazyScopeAdapter.kt:22)
	at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedLazyValue.invoke(LockBasedStorageManager.java:370)
	at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedNotNullLazyValue.invoke(LockBasedStorageManager.java:489)
	at kotlin.reflect.jvm.internal.impl.resolve.scopes.LazyScopeAdapter.getWorkerScope(LazyScopeAdapter.kt:34)
	at kotlin.reflect.jvm.internal.impl.resolve.scopes.AbstractScopeAdapter.getContributedClassifier(AbstractScopeAdapter.kt:44)
	at kotlin.reflect.jvm.internal.impl.descriptors.FindClassInModuleKt.findClassifierAcrossModuleDependencies(findClassInModule.kt:26)
	at kotlin.reflect.jvm.internal.impl.serialization.deserialization.TypeDeserializer.computeClassifierDescriptor(TypeDeserializer.kt:240)
	at kotlin.reflect.jvm.internal.impl.serialization.deserialization.TypeDeserializer.access$computeClassifierDescriptor(TypeDeserializer.kt:23)
	at kotlin.reflect.jvm.internal.impl.serialization.deserialization.TypeDeserializer$classifierDescriptors$1.invoke(TypeDeserializer.kt:33)
	at kotlin.reflect.jvm.internal.impl.serialization.deserialization.TypeDeserializer$classifierDescriptors$1.invoke(TypeDeserializer.kt:23)
	at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$MapBasedMemoizedFunction.invoke(LockBasedStorageManager.java:527)
	at kotlin.reflect.jvm.internal.impl.serialization.deserialization.TypeDeserializer.typeConstructor(TypeDeserializer.kt:132)
	at kotlin.reflect.jvm.internal.impl.serialization.deserialization.TypeDeserializer.simpleType(TypeDeserializer.kt:76)
	at kotlin.reflect.jvm.internal.impl.serialization.deserialization.TypeDeserializer.type(TypeDeserializer.kt:64)
	at kotlin.reflect.jvm.internal.impl.serialization.deserialization.MemberDeserializer.valueParameters(MemberDeserializer.kt:417)
	at kotlin.reflect.jvm.internal.impl.serialization.deserialization.MemberDeserializer.loadConstructor(MemberDeserializer.kt:342)
	at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor.computePrimaryConstructor(DeserializedClassDescriptor.kt:126)
	at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor.access$computePrimaryConstructor(DeserializedClassDescriptor.kt:36)
	at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor$primaryConstructor$1.invoke(DeserializedClassDescriptor.kt:67)
	at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor$primaryConstructor$1.invoke(DeserializedClassDescriptor.kt:36)
	at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedLazyValue.invoke(LockBasedStorageManager.java:370)
	at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor.getUnsubstitutedPrimaryConstructor(DeserializedClassDescriptor.kt:130)
	at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor.computeConstructors(DeserializedClassDescriptor.kt:133)
	at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor.access$computeConstructors(DeserializedClassDescriptor.kt:36)
	at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor$constructors$1.invoke(DeserializedClassDescriptor.kt:68)
	at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor$constructors$1.invoke(DeserializedClassDescriptor.kt:36)
	at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedLazyValue.invoke(LockBasedStorageManager.java:370)
	at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedNotNullLazyValue.invoke(LockBasedStorageManager.java:489)
	at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor.getConstructors(DeserializedClassDescriptor.kt:141)
	at kotlin.reflect.jvm.internal.KClassImpl.getConstructorDescriptors(KClassImpl.kt:202)
	at kotlin.reflect.jvm.internal.KClassImpl$Data$constructors$2.invoke(KClassImpl.kt:93)
	at kotlin.reflect.jvm.internal.KClassImpl$Data$constructors$2.invoke(KClassImpl.kt:46)
	at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:92)
	at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:31)
	at kotlin.reflect.jvm.internal.KClassImpl$Data.getConstructors(Unknown Source:7)
	at kotlin.reflect.jvm.internal.KClassImpl.getConstructors(KClassImpl.kt:237)
	at kotlin.reflect.full.KClasses.getPrimaryConstructor(KClasses.kt:40)
	at com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory.create(KotlinJsonAdapter.kt:219)
	at com.squareup.moshi.Moshi.adapter(Moshi.java:138)
	at com.squareup.moshi.Moshi.adapter(Moshi.java:98)
	at com.squareup.moshi.Moshi.adapter(Moshi.java:72)
	at dev.patrickgold.florisboard.ime.media.emoticon.EmoticonLayoutData$Companion.fromJsonFile(EmoticonLayoutData.kt:40)
	at dev.patrickgold.florisboard.ime.media.emoticon.EmoticonKeyboardView$1.invokeSuspend(EmoticonKeyboardView.kt:39)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)

@Glitchy-Tozier
Copy link
Collaborator Author

Glitchy-Tozier commented Feb 1, 2021

Okay, I can replicate it now.
I just

  1. opened some app (Github)
  2. clicked on a text-field so that the keyboard was active
  3. turned the screen back and forth 1-2 minutes.

It started loading long enough that I could notice the loading-screen, then it ltarted taking its time before even showing the loading-screen. Then the screen-rotations felt like they took longer and longer to start.
Then FlorisBoard got stuck on the loading-screen for a few seconds, before crashing.

((The log in my previous comment is not the log from this last reproduction-crash))

@kj7rrv
Copy link

kj7rrv commented Feb 1, 2021 via email

@patrickgold
Copy link
Member

Thanks for your observations! I've profiled the memory state of FlorisBoard when rotating the screen often and found out, that emoji parsing is indeed triggered a lot, but it isn't even the biggest memory leak. The internal event listener chain didn't clear itself up (except for the rewritten theme manager), so the event listener count got bigger and bigger, up to a few 100k (!) items (The normal count is ~1800 listeners at max). This of course massively slowed down things. Another thing I observed is that there seems to be a memory leak in the creation of the UI itself, which of course happens again and again for every rotation.

Smaller leaks are within the TabLayout from Android itself, but this is currently unfixable for me. Also, setting the corner radius for drawables seems to not allow the GC to clean up the previous cached bitmap, I will investigate into this as well.

I am currently fixing and re-testing the memory state and will get back to you with more results.

@patrickgold patrickgold added bug-confirmed A confirmed and reproducible bug report and removed not reproducible An issue which cannot be reproduced by the dev team labels Feb 1, 2021
@patrickgold patrickgold self-assigned this Feb 1, 2021
@patrickgold
Copy link
Member

Above commit adds improvements and prevents memory leaks, in detail:

  • The crazy event listener count bug has been fixed, all event listeners correctly register/unregister now.
  • The emoji layout data file is cached once loaded, so this makes building the UI after rotating both faster and prevents a memory leak, as some references didn't clear up correctly.
  • The SmartbarView didn't unregister at all from the TextInputManager and thus a dead reference was kept, which prevented the GC from cleaning up the old SmartbarView.
  • Some minor memory leaks when setting the corner radii of a drawable have been resolved.
  • The KeyView class now correctly changes capitalization for Turkish (not related to this issue really, but I wasn't able to extract this change into a separate commit).

Note, that this is still far from perfect and I am almost certain that you'll be able to reproduce the crash even in the newest version, but it will probably take way more rotations of the screen before this will occur. Because of this, I will let this issue open, as it isn't fully resolved yet.

@Glitchy-Tozier
Copy link
Collaborator Author

I see. Are the remaining parts of this bug the ones you said you didn't know how to fix right now?

@patrickgold
Copy link
Member

Partly, but there are also some issues in some generic dispatcher handles, where I don't know yet what FlorisBoard method they are actually representing.

@Glitchy-Tozier Glitchy-Tozier changed the title Possible memery leak in keyboard view Possible memory leak in keyboard view Feb 2, 2021
@D3SOX
Copy link

D3SOX commented Feb 27, 2021

I got this using the latest master version (commit 38baac1). Not sure if it is related to this, I can create a new issue if it's not.

~~~ 1614442129207.stacktrace ~~~

java.lang.OutOfMemoryError: Failed to allocate a 288 byte allocation with 104 free bytes and 104B until OOM, target footprint 268435456, growth limit 268435456
	at timber.log.Timber$DebugTree.getTag(Timber.java:601)
	at timber.log.Timber$Tree.prepareLog(Timber.java:510)
	at timber.log.Timber$Tree.i(Timber.java:420)
	at timber.log.Timber$1.i(Timber.java:264)
	at timber.log.Timber.i(Timber.java:53)
	at dev.patrickgold.florisboard.ime.core.FlorisBoard.onStartInput(FlorisBoard.kt:312)
	at android.inputmethodservice.InputMethodService.doStartInput(InputMethodService.java:2093)
	at android.inputmethodservice.InputMethodService$InputMethodImpl.startInput(InputMethodService.java:556)
	at android.inputmethodservice.InputMethodService$InputMethodImpl.dispatchStartInputWithToken(InputMethodService.java:585)
	at android.inputmethodservice.IInputMethodWrapper.executeMessage(IInputMethodWrapper.java:194)
	at com.android.internal.os.HandlerCaller$MyHandler.handleMessage(HandlerCaller.java:44)
	at android.os.Handler.dispatchMessage(Handler.java:107)
	at android.os.Looper.loop(Looper.java:214)
	at android.app.ActivityThread.main(ActivityThread.java:7397)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:491)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

@patrickgold
Copy link
Member

Not sure if it is related to this, I can create a new issue if it's not.

Thanks for reporting this, I think it is best if it stays within this issue, as this is related to a memory leak.

I got this using the latest master version (commit 38baac1).

As you have the latest master debug version, did you have suggestions enabled or not? This makes a huge difference, because the memory management in the suggestions has some bugs in it.

@D3SOX
Copy link

D3SOX commented Feb 27, 2021

did you have suggestions enabled or not?

suggestions enabled and only English QWERTY subtype added

I think it's the same problem as #403

@patrickgold
Copy link
Member

Yup, I also think there's something wrong with my suggestion algorithm, I am currently looking into it.

@kammt
Copy link

kammt commented Apr 19, 2021

Hey there!
I was having the same OutOfMemoryError on my device while using the numpad. The stack trace is appended below. Just out of curiosity: Have you tried using LeakCanary yet? This library has helped me get rid of memory leaks in my personal Android projects.

Anyway, the keyboard is awesome, thank you for your hard work on it!

java.lang.OutOfMemoryError: Failed to allocate a 808128 byte allocation with 12792 free bytes and 12KB until OOM, target footprint 201326592, growth limit 201326592
	at java.lang.StringFactory.newStringFromChars(StringFactory.java:260)
	at java.lang.StringBuffer.toString(StringBuffer.java:671)
	at java.io.StringWriter.toString(StringWriter.java:210)
	at kotlin.io.TextStreamsKt.readText(ReadWrite.kt:108)
	at dev.patrickgold.florisboard.ime.extension.AssetManager.loadAssetRaw-IoAF18A(AssetManager.kt:202)
	at dev.patrickgold.florisboard.ime.text.gestures.GlideTypingManager$setWordData$1.invokeSuspend(GlideTypingManager.kt:78)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)

@patrickgold
Copy link
Member

patrickgold commented Apr 19, 2021

I was having the same OutOfMemoryError on my device while using the numpad.

This indicates to me that the described memory spikes in #711 could theoretically be one reason for the many crashes.

The stack trace is appended below. Just out of curiosity: Have you tried using LeakCanary yet?

Yes, I have. Sadly LeakCanary only works properly for Activity or Fragment contexts, this IME's issues are primary in the Service context, which LeakCanary cannot really detect.

@Glitchy-Tozier
Copy link
Collaborator Author

Glitchy-Tozier commented Apr 19, 2021

This indicates to me that the described memory spikes in #711 could theoretically be one reason for the many crashes.

No idea if that's helpful information but it's also possible to make Florisboard crash in a password-field, simply by rotating the device back and fourth.

(v.0.3.10, suggestions and glide typing both disabled.)

@Glitchy-Tozier
Copy link
Collaborator Author

Glitchy-Tozier commented Apr 19, 2021

Also, the Florisboard-keyboard doesn't need to be active to crash. You can use it until it starts lagging, then go into the florisboard settings. When I deleted a few layouts, it Florisboard crashed.

~~~ 1618825581256.stacktrace ~~~

java.lang.OutOfMemoryError: Failed to allocate a 16 byte allocation with 2179984 free bytes and 2128KB until OOM, target footprint 268435456, growth limit 268435456; failed due to fragmentation (largest possible contiguous allocation 215220224 bytes)
	at android.os.ThreadLocalWorkSource.setUid(ThreadLocalWorkSource.java:68)
	at android.os.Binder.execTransact(Binder.java:1027)

EDIT: Also, it looked as if the last changes I made seconds before the crash were never implemented. I had to re-delete layouts.

@patrickgold
Copy link
Member

Also, the Florisboard-keyboard doesn't need to be active to crash

The service class is active as soon as it was started and will be active until either it crashes, it is force-closed or if you switch to another keyboard. In all other cases, the service will continue to receive input events, even if it is physically not visible.

@patrickgold
Copy link
Member

EDIT: Also, it looked as if the last changes I made seconds before the crash were never implemented. I had to re-delete layouts.

Because the shared preference batch together changes to avoid writing to the disk too often, this issue occurs if the keyboard crashes seconds after setting a preference (deleting the subtype is settings a preference value).

@patrickgold
Copy link
Member

Reviewing the backlog of issues I saw how old the first report of OOM crashes is. A lot of work and changes has undergone this project since then and most of the classes described in this issue's stacktrace do not even exist in that form anymore. The main point of this issue where memory leaks in the text keyboard view (which have been pretty much fixed since v0.3.11-beta02's layout rework) and memory leaks in the emojis (which have been partially fixed and the UI creation has been vastly improved).

The rotation bug will still be reproducible in v0.3.11, but should be a lot harder to get, as more and more objects are cached and reused instead of rebuilt when rebuilding the UI. As the OOM errors in general are batched together in v0.3.11, I think that closing this issue is best to keep the list clean.

@Glitchy-Tozier
Copy link
Collaborator Author

Kk, good job with 0.3.11, I'll have to find new ways of breaking your app then ;)

@patrickgold
Copy link
Member

Kk, good job with 0.3.11, I'll have to find new ways of breaking your app then ;)

The question is not if you find a new way to break the logic, but more when :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A bug report bug-confirmed A confirmed and reproducible bug report
Projects
None yet
Development

No branches or pull requests

5 participants