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

Queue show notes downloading #1390

Merged
merged 5 commits into from
Sep 21, 2023
Merged

Queue show notes downloading #1390

merged 5 commits into from
Sep 21, 2023

Conversation

geekygecko
Copy link
Member

@geekygecko geekygecko commented Sep 20, 2023

Description

After doing some debugging with the Background Task Inspector, I noticed that when you download multiple episodes at once, the show notes tasks run in parallel. A single queue that caches the show notes seems to be a better approach. If you download multiple episodes from the same podcast the show notes will be cached with the first call, before the same file would be downloaded multiple times until the cache was populated.

This PR also stops some reports to Sentry, I will comment on them inline.

Testing Instructions

  1. Add the "package:mine UpdateShowNotesTask"
  2. Subscribe to some podcasts
  3. Go to a podcast page
  4. Long press an episode
  5. Press the toolbar overflow menu and select all
  6. Tap download in the toolbar
  7. ✅ Verify after the first "Worker completed" line, the time taken is much shorter indicating the cache is being used.

fun toDate(value: Long?): Date? {
return if (value == null) null else Date(value)
fun toDate(value: Long?): Date {
return if (value == null) Date() else Date(value)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should stop the following Sentry error.

au.com.shiftyjelly.pocketcasts.models.db.dao.EpisodeDao_Impl$44 in call
IllegalStateException
Expected non-null java.util.Date, but it was null.

It seems like the database episode has a publish date of null. I can't find how this is possible but this should stop the crash happening when the data is read.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice job avoiding this crash. I wonder if returning a dynamic date might not be the best approach though.

I'm not sure about this, but maybe we'd be better off returning a static date object (i.e., something like the UNIX epoch), so the behavior is consistent and doesn't return something different each time it is called with null. 🤔 This is just me thinking out loud. I'm seriously not sure here.

I think it would be good to log a Sentry breadcrumb here if the Long is null, so that if this somehow causes a problem later on that shows up in Sentry, this will get flagged for us. Something like:

if (value == null) {
    Sentry.addBreadcrumb("DateTypeConverter.toDate() Long parameter is null. Returning default date")
    Date.from(Instant.EPOCH)
} else Date(value)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe LogBuffer.log would be better than just adding the breadcrumb.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent idea to log and return a constant date. I have made this change.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That log just saved me! It gets called with dates that need to be null. I'm reverting this issue fix.

Timber.i(e, message)
} else {
Timber.e(e, message)
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should stop the following Sentry error as I can see the previous call is the ratings endpoint.

HttpException
HTTP 404 

@@ -125,13 +126,13 @@ class DownloadEpisodeTask @AssistedInject constructor(
this.episode = runBlocking { episodeManager.findEpisodeByUuid(episodeUUID!!) } ?: return Result.failure()

if (this.episode.downloadTaskId != id.toString()) {
LogBuffer.e(LogBuffer.TAG_BACKGROUND_TASKS, "Mismatched download task id for episode ${episode.title}. Cancelling.")
LogBuffer.i(LogBuffer.TAG_BACKGROUND_TASKS, "Mismatched download task id for episode ${episode.title}. Cancelling.")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few issues reported in this class that are being sent to Sentry. If we aren't going address them I believe they should just be the log level of info.

Copy link
Contributor

@mchowning mchowning Sep 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the other places you've made this change, but I wonder if we should keep this one since if it shot up all of a sudden that might be an issue we'd want to know about.

This isn't something you changed, but while we're here, do you think it would be worth including the downloadTaskId and id in the error message. Seems like that would be helpful in tracking down why these are occurring. Just a thought, I don't feel strongly.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have kept this and added the extra ids in the message. Thanks for the suggestions.

@geekygecko geekygecko marked this pull request as ready for review September 20, 2023 11:42
@geekygecko geekygecko requested a review from a team as a code owner September 20, 2023 11:42
@geekygecko geekygecko force-pushed the update/show-notes-task branch from c15ca38 to 0a578a8 Compare September 20, 2023 11:43
SentryHelper.recordException(e)
}
info("Worker failed - took ${SystemClock.elapsedRealtime() - startTime} ms")
val logPriority = if (e is HttpException) Log.INFO else Log.ERROR
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if there is a better way to do this. Maybe I should add a common way to add network exceptions which doesn't report to Sentry. 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not a bad idea

Copy link
Contributor

@mchowning mchowning left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really nice work. Just had a couple of comments/questions, but nothing big. This is a great improvement. 🤩

fun toDate(value: Long?): Date? {
return if (value == null) null else Date(value)
fun toDate(value: Long?): Date {
return if (value == null) Date() else Date(value)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice job avoiding this crash. I wonder if returning a dynamic date might not be the best approach though.

I'm not sure about this, but maybe we'd be better off returning a static date object (i.e., something like the UNIX epoch), so the behavior is consistent and doesn't return something different each time it is called with null. 🤔 This is just me thinking out loud. I'm seriously not sure here.

I think it would be good to log a Sentry breadcrumb here if the Long is null, so that if this somehow causes a problem later on that shows up in Sentry, this will get flagged for us. Something like:

if (value == null) {
    Sentry.addBreadcrumb("DateTypeConverter.toDate() Long parameter is null. Returning default date")
    Date.from(Instant.EPOCH)
} else Date(value)

.addTag(episode.uuid)
.setConstraints(constraints)
.build()
WorkManager.getInstance(context).beginUniqueWork(TASK_NAME, ExistingWorkPolicy.APPEND, workRequest).enqueue()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is APPEND here better than APPEND_OR_REPLACE? I'm not particularly familiar with this, so I may be misunderstanding, but I was thinking that APPEND_OR_REPLACE might be better to avoid dropping subsequent tasks if an early one fails.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The show notes task always returns Result.success() so I think this shouldn't matter but it seems like a safer option, I will change it.

SentryHelper.recordException(e)
}
info("Worker failed - took ${SystemClock.elapsedRealtime() - startTime} ms")
val logPriority = if (e is HttpException) Log.INFO else Log.ERROR
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not a bad idea

fun toDate(value: Long?): Date? {
return if (value == null) null else Date(value)
fun toDate(value: Long?): Date {
return if (value == null) Date() else Date(value)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe LogBuffer.log would be better than just adding the breadcrumb.

@@ -125,13 +126,13 @@ class DownloadEpisodeTask @AssistedInject constructor(
this.episode = runBlocking { episodeManager.findEpisodeByUuid(episodeUUID!!) } ?: return Result.failure()

if (this.episode.downloadTaskId != id.toString()) {
LogBuffer.e(LogBuffer.TAG_BACKGROUND_TASKS, "Mismatched download task id for episode ${episode.title}. Cancelling.")
LogBuffer.i(LogBuffer.TAG_BACKGROUND_TASKS, "Mismatched download task id for episode ${episode.title}. Cancelling.")
Copy link
Contributor

@mchowning mchowning Sep 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the other places you've made this change, but I wonder if we should keep this one since if it shot up all of a sudden that might be an issue we'd want to know about.

This isn't something you changed, but while we're here, do you think it would be worth including the downloadTaskId and id in the error message. Seems like that would be helpful in tracking down why these are occurring. Just a thought, I don't feel strongly.

return try {
showNotesManager.downloadShowNotes(podcastUuid = podcastUuid, episodeUuid = episodeUuid)
showNotesManager.downloadToCacheShowNotes(podcastUuid = podcastUuid)
info("Worker completed - took ${SystemClock.elapsedRealtime() - startTime} ms")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Including the timing here is nice!

Comment on lines 118 to 120
fun log(priority: Int, tag: String, throwable: Throwable, message: String, vararg args: Any) {
addLog(priority, tag, throwable, message, *args)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of adding a new log method, could we get the same effect by renaming addLog to log and making that public? Certainly not a big deal, but it feels a bit more straightforward.

@geekygecko geekygecko force-pushed the update/show-notes-task branch from 725f6f5 to 5efd4f8 Compare September 21, 2023 05:42
@geekygecko geekygecko merged commit fa5a2e1 into main Sep 21, 2023
@geekygecko geekygecko deleted the update/show-notes-task branch September 21, 2023 06:30
@ashiagr ashiagr added this to the 7.48 ❄️ milestone Sep 22, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants