Skip to content

Commit

Permalink
Fix IndexOutOfBounds after getRefreshKey
Browse files Browse the repository at this point in the history
It's possible that on a paging source that's not in the initial
generation, we fail with an `IndexOutOfBoundsException`. This
is caused when the underlying data source undergoes a delete, which
then invalidates the paging source, which then calls
`getRefreshKey`. In the event that the the refresh key is the
very last index of the previous paging source, we would fail
with the above exception because that index does not exist
in the next generation paging source.

Closes cashapp#2434.
  • Loading branch information
kevincianfarini committed Jul 28, 2021
1 parent 51d7b08 commit 005b2a0
Show file tree
Hide file tree
Showing 2 changed files with 21 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,13 @@ internal class OffsetQueryPagingSource<RowType : Any>(
params: LoadParams<Long>
): LoadResult<Long, RowType> = withContext(dispatcher) {
try {
val key = params.key ?: 0L
transacter.transactionWithResult {
val count = countQuery.executeAsOne()
val key = when (params) {
is LoadParams.Refresh -> params.key?.let { notNullKey -> minOf(count - 1, notNullKey) }
else -> params.key
} ?: 0L

if (count != 0L && key >= count) throw IndexOutOfBoundsException()

val loadSize = if (key < 0) params.loadSize + key else params.loadSize
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ class OffsetQueryPagingSourceTest {
transacter = object : TransacterImpl(driver) {}
}

@After
fun after() {
@After fun after() {
File("test.db").delete()
}

Expand Down Expand Up @@ -241,11 +240,25 @@ class OffsetQueryPagingSourceTest {

runBlocking {
assertFailsWith<IndexOutOfBoundsException> {
source.load(Refresh(10, 2, false))
source.load(PagingSource.LoadParams.Append(10, 2, false))
}
}
}

@Test fun `refresh key too big gets truncated`() {
val source = OffsetQueryPagingSource(
this::query,
countQuery(),
transacter,
TestCoroutineDispatcher()
)

runBlocking {
val results = source.load(Refresh(10, 2, false))
assertEquals(listOf(9L), (results as LoadResult.Page).data)
}
}

@Test fun `query invaliation invalidates paging source`() {
val query = query(2, 0)
val source = OffsetQueryPagingSource(
Expand Down

0 comments on commit 005b2a0

Please sign in to comment.