-
Notifications
You must be signed in to change notification settings - Fork 626
Description
[READ] Step 1: Are you in the right place?
Yes
[REQUIRED] Step 2: Describe your environment
- Android Studio version: Any
- Firebase Component: firebase-ml-modeldownloader
- Component version: 24.0.2
[REQUIRED] Step 3: Describe the problem
Steps to reproduce:
Note this is an edge case the requirement to hit this edge case are as below:
Be in the process of migrating from FirebaseModelManager (old version) to FirebaseModelDownloader (open source).
Why because they store the same model in the exact same destination. Path (/data/user/0/<app package name>/no_backup/com.google.firebase.ml.custom.models/<firebase persistent key>/<model name>) verified by checking the file paths via both the downloaders. But they store model details in different locations, i.e details in the SharedPreferences cache are in different namespaces. (Open source firebase model downloader stores it in com.google.firebase.ml.modelDownloader and likely old FirebaseModelManager stores it here com.google.firebase.ml.common.modeldownload)
Two models need to be triggered for download at the same time (It leads to partial information present in the SharedPreferences cache) and with the larger model already downloaded on the device. Smaller model download = faster download time relative to larger model download.
Why because when the smaller model completes downloading and the larger model is in progress when we look to clean up files post smaller model download, we find the larger model folder but do not find the larger model file path in the SharedPreferences cache (although the file is on the device). Thus we get an empty file path for the larger model resulting in the below stack trace.
java.lang.NumberFormatException: For input string: ""
java.lang.Integer.parseInt(Integer.java:627)
java.lang.Integer.parseInt(Integer.java:650)
com.google.firebase.ml.modeldownloader.internal.ModelFileManager.deleteOldModels(ModelFileManager.java:222)
com.google.firebase.ml.modeldownloader.internal.ModelFileManager.deleteNonLatestCustomModels(ModelFileManager.java:82)
com.google.firebase.ml.modeldownloader.internal.ModelFileDownloadService.maybeCleanUpOldModels(ModelFileDownloadService.java:463)
com.google.firebase.ml.modeldownloader.internal.ModelFileDownloadService.loadNewlyDownloadedModelFile(ModelFileDownloadService.java:436)
com.google.firebase.ml.modeldownloader.FirebaseModelDownloader.finishModelDownload(FirebaseModelDownloader.java:432)
com.google.firebase.ml.modeldownloader.FirebaseModelDownloader.lambda$getCustomModelTask$2$FirebaseModelDownloader(FirebaseModelDownloader.java:361)
com.google.firebase.ml.modeldownloader.-$$Lambda$FirebaseModelDownloader$GcaMffATH6wSK_VJ7z2TtucDhnk.then(Unknown Source:6)
com.google.android.gms.tasks.zzg.run(com.google.android.gms:play-services-tasks@@17.0.2:2)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
java.lang.Thread.run(Thread.java:920)
We would not see this issue:
- If we had simultaneous downloads and the larger model was not already present on the device (via FirebaseModelManager (old version)), we would not try to iterate through the larger model folder, and thus not try to fetch details about this model from the SharedPreferences cache.
- If we had sequential downloads, we would fill up our preferences cache sequentially, and with complete information one at a time. Even if we triggered smaller model download first although we would still iterate through the larger model folder we would not find any information in the SharedPreferences cache (instead of partial information that we find in simultaneous downloads). Thus leading to model = null condition as shown here and avoid the crash.
Relevant Code:
val remotedModel = FirebaseCustomRemoteModel.Builder(largeModelName).build();
//note this is a different version of model downloader the ones below, it is firebaseModelManager
firebaseModelManager
.download(remoteModel, conditions)
.addSuccessListener{}
.addFailureListener{}
//restart app and simultaneously trigger
firebaseModelDownloader
.getModel(largeModelName, DownloadType.LOCAL_MODEL, conditions)
.addSuccessListener{}
.addFailureListener{}
firebaseModelDownloader
.getModel(smallerModelName, DownloadType.LOCAL_MODEL, conditions)
.addSuccessListener{}
.addFailureListener{}
//The smallerModelName will result in a failure with the above stack trace.