Skip to content

Commit 8481c39

Browse files
authored
BatchOperation: set yield point after every 500 operations (#188)
* BatchOperation: set "yield allowed" after every 500 operations * Only use "max. operations per yield point" for tasks.org/OpenTasks operations
1 parent 1accbfd commit 8481c39

File tree

6 files changed

+57
-24
lines changed

6 files changed

+57
-24
lines changed

lib/src/androidTest/kotlin/at/bitfire/ical4android/AbstractTasksTest.kt renamed to lib/src/androidTest/kotlin/at/bitfire/ical4android/DmfsStyleProvidersTaskTest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import java.util.logging.Logger
1616

1717
@RunWith(Parameterized::class)
1818

19-
abstract class AbstractTasksTest(
19+
abstract class DmfsStyleProvidersTaskTest(
2020
val providerName: TaskProvider.ProviderName
2121
) {
2222

lib/src/androidTest/kotlin/at/bitfire/ical4android/DmfsTaskListTest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import org.junit.Assert.assertTrue
2121
import org.junit.Test
2222

2323
class DmfsTaskListTest(providerName: TaskProvider.ProviderName):
24-
AbstractTasksTest(providerName) {
24+
DmfsStyleProvidersTaskTest(providerName) {
2525

2626
private val testAccount = Account("AndroidTaskListTest", TaskContract.LOCAL_ACCOUNT_TYPE)
2727

lib/src/androidTest/kotlin/at/bitfire/ical4android/DmfsTaskTest.kt

+18-7
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ import android.content.ContentUris
99
import android.content.ContentValues
1010
import android.database.DatabaseUtils
1111
import android.net.Uri
12-
import androidx.test.filters.MediumTest
1312
import at.bitfire.ical4android.impl.TestTask
1413
import at.bitfire.ical4android.impl.TestTaskList
1514
import at.bitfire.ical4android.util.DateUtils
1615
import net.fortuna.ical4j.model.Date
1716
import net.fortuna.ical4j.model.DateList
1817
import net.fortuna.ical4j.model.DateTime
18+
import net.fortuna.ical4j.model.component.VAlarm
1919
import net.fortuna.ical4j.model.parameter.Email
2020
import net.fortuna.ical4j.model.parameter.RelType
2121
import net.fortuna.ical4j.model.parameter.TzId
@@ -52,8 +52,8 @@ import org.junit.Test
5252
import java.time.ZoneId
5353

5454
class DmfsTaskTest(
55-
providerName: TaskProvider.ProviderName
56-
): AbstractTasksTest(providerName) {
55+
providerName: TaskProvider.ProviderName
56+
): DmfsStyleProvidersTaskTest(providerName) {
5757

5858
private val tzVienna = DateUtils.ical4jTimeZone("Europe/Vienna")!!
5959
private val tzChicago = DateUtils.ical4jTimeZone("America/Chicago")!!
@@ -643,7 +643,6 @@ class DmfsTaskTest(
643643
}
644644

645645

646-
@MediumTest
647646
@Test
648647
fun testAddTask() {
649648
// build and write event to calendar provider
@@ -693,7 +692,6 @@ class DmfsTaskTest(
693692
}
694693
}
695694

696-
@MediumTest
697695
@Test(expected = CalendarStorageException::class)
698696
fun testAddTaskWithInvalidDue() {
699697
val task = Task()
@@ -705,7 +703,21 @@ class DmfsTaskTest(
705703
TestTask(taskList!!, task).add()
706704
}
707705

708-
@MediumTest
706+
@Test
707+
fun testAddTaskWithManyAlarms() {
708+
val task = Task()
709+
task.uid = "TaskWithManyAlarms"
710+
task.summary = "Task with many alarms"
711+
task.dtStart = DtStart(Date("20150102"))
712+
713+
for (i in 1..1050)
714+
task.alarms += VAlarm(java.time.Duration.ofMinutes(i.toLong()))
715+
716+
val uri = TestTask(taskList!!, task).add()
717+
val task2 = taskList!!.findById(ContentUris.parseId(uri))
718+
assertEquals(1050, task2.task?.alarms?.size)
719+
}
720+
709721
@Test
710722
fun testUpdateTask() {
711723
// add test event without reminder
@@ -739,7 +751,6 @@ class DmfsTaskTest(
739751
}
740752
}
741753

742-
@MediumTest
743754
@Test
744755
fun testBuildAllDayTask() {
745756
// add all-day event to calendar provider

lib/src/main/kotlin/at/bitfire/ical4android/BatchOperation.kt

+34-12
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,17 @@ import java.util.logging.Level
1717
import java.util.logging.Logger
1818

1919
class BatchOperation(
20-
private val providerClient: ContentProviderClient
20+
private val providerClient: ContentProviderClient,
21+
private val maxOperationsPerYieldPoint: Int? = null
2122
) {
22-
23+
24+
companion object {
25+
26+
/** Maximum number of operations per yield point in task providers that are based on SQLiteContentProvider. */
27+
const val TASKS_OPERATIONS_PER_YIELD_POINT = 499
28+
29+
}
30+
2331
private val logger = Logger.getLogger(javaClass.name)
2432

2533
private val queue = LinkedList<CpoBuilder>()
@@ -96,7 +104,6 @@ class BatchOperation(
96104

97105
try {
98106
val ops = toCPO(start, end)
99-
logger.fine("Running ${ops.size} operations ($start .. ${end - 1})")
100107
val partResults = providerClient.applyBatch(ops)
101108

102109
val n = end - start
@@ -133,6 +140,7 @@ class BatchOperation(
133140
* 2. If a back reference points to a row outside of start/end,
134141
* replace it by the actual result, which has already been calculated. */
135142

143+
var currentIdx = 0
136144
for (cpoBuilder in queue.subList(start, end)) {
137145
for ((backrefKey, backref) in cpoBuilder.valueBackrefs) {
138146
val originalIdx = backref.originalIndex
@@ -148,17 +156,22 @@ class BatchOperation(
148156
backref.setIndex(originalIdx - start)
149157
}
150158

159+
// Set a possible yield point every MAX_OPERATIONS_PER_YIELD_POINT operations for SQLiteContentProvider
160+
currentIdx += 1
161+
if (maxOperationsPerYieldPoint != null && currentIdx.mod(maxOperationsPerYieldPoint) == 0)
162+
cpoBuilder.withYieldAllowed()
163+
151164
cpo += cpoBuilder.build()
152165
}
153166
return cpo
154167
}
155168

156169

157170
class BackReference(
158-
/** index of the referenced row in the original, nonsplitted transaction */
159-
val originalIndex: Int
171+
/** index of the referenced row in the original, non-splitted transaction */
172+
val originalIndex: Int
160173
) {
161-
/** overriden index, i.e. index within the splitted transaction */
174+
/** overridden index, i.e. index within the splitted transaction */
162175
private var index: Int? = null
163176

164177
/**
@@ -182,8 +195,8 @@ class BatchOperation(
182195
* value back references.
183196
*/
184197
class CpoBuilder private constructor(
185-
val uri: Uri,
186-
val type: Type
198+
val uri: Uri,
199+
val type: Type
187200
) {
188201

189202
enum class Type { INSERT, UPDATE, DELETE }
@@ -197,11 +210,13 @@ class BatchOperation(
197210
}
198211

199212

200-
var selection: String? = null
201-
var selectionArguments: Array<String>? = null
213+
private var selection: String? = null
214+
private var selectionArguments: Array<String>? = null
215+
216+
internal val values = mutableMapOf<String, Any?>()
217+
internal val valueBackrefs = mutableMapOf<String, BackReference>()
202218

203-
val values = mutableMapOf<String, Any?>()
204-
val valueBackrefs = mutableMapOf<String, BackReference>()
219+
private var yieldAllowed = false
205220

206221

207222
fun withSelection(select: String, args: Array<String>): CpoBuilder {
@@ -226,6 +241,10 @@ class BatchOperation(
226241
return this
227242
}
228243

244+
fun withYieldAllowed() {
245+
yieldAllowed = true
246+
}
247+
229248

230249
fun build(): ContentProviderOperation {
231250
val builder = when (type) {
@@ -242,6 +261,9 @@ class BatchOperation(
242261
for ((key, backref) in valueBackrefs)
243262
builder.withValueBackReference(key, backref.getIndex())
244263

264+
if (yieldAllowed)
265+
builder.withYieldAllowed(true)
266+
245267
return builder.build()
246268
}
247269

lib/src/main/kotlin/at/bitfire/ical4android/DmfsTask.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ abstract class DmfsTask(
330330

331331

332332
fun add(): Uri {
333-
val batch = BatchOperation(taskList.provider)
333+
val batch = BatchOperation(taskList.provider, BatchOperation.TASKS_OPERATIONS_PER_YIELD_POINT)
334334

335335
val builder = CpoBuilder.newInsert(taskList.tasksSyncUri())
336336
buildTask(builder, false)
@@ -350,7 +350,7 @@ abstract class DmfsTask(
350350
this.task = task
351351
val existingId = requireNotNull(id)
352352

353-
val batch = BatchOperation(taskList.provider)
353+
val batch = BatchOperation(taskList.provider, BatchOperation.TASKS_OPERATIONS_PER_YIELD_POINT)
354354

355355
// remove associated rows which are added later again
356356
batch.enqueue(CpoBuilder

lib/src/main/kotlin/at/bitfire/ical4android/DmfsTaskList.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ abstract class DmfsTaskList<out T : DmfsTask>(
163163
*/
164164
fun touchRelations(): Int {
165165
logger.fine("Touching relations to set parent_id")
166-
val batchOperation = BatchOperation(provider)
166+
val batchOperation = BatchOperation(provider, BatchOperation.TASKS_OPERATIONS_PER_YIELD_POINT)
167167
provider.query(
168168
tasksSyncUri(true), null,
169169
"${Tasks.LIST_ID}=? AND ${Tasks.PARENT_ID} IS NULL AND ${Relation.MIMETYPE}=? AND ${Relation.RELATED_ID} IS NOT NULL",

0 commit comments

Comments
 (0)