Skip to content

Commit 58baa9c

Browse files
committed
experimental query retry configuration
1 parent c9fe9d3 commit 58baa9c

File tree

6 files changed

+105
-14
lines changed

6 files changed

+105
-14
lines changed

objectbox-java/src/main/java/io/objectbox/BoxStore.java

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,10 @@ public static String getVersion() {
169169

170170
private int objectBrowserPort;
171171

172+
private final int defaultQueryAttempts;
173+
174+
private final TxCallback defaultFailedReadTxAttemptCallback;
175+
172176
BoxStore(BoxStoreBuilder builder) {
173177
NativeLibraryLoader.ensureLoaded();
174178

@@ -226,6 +230,9 @@ public static String getVersion() {
226230
}
227231

228232
objectClassPublisher = new ObjectClassPublisher(this);
233+
234+
defaultFailedReadTxAttemptCallback = builder.defaultFailedReadTxAttemptCallback;
235+
defaultQueryAttempts = builder.defaultQueryAttempts < 1 ? 1 : builder.defaultQueryAttempts;
229236
}
230237

231238
private static void verifyNotAlreadyOpen(String canonicalPath) {
@@ -608,16 +615,22 @@ public <T> T callInReadTxWithRetry(Callable<T> callable, int attempts, int initi
608615
return callInReadTx(callable);
609616
} catch (DbException e) {
610617
lastException = e;
618+
619+
String diagnose = diagnose();
620+
String message = attempt + ". of " + attempts + " attempts of calling a read TX failed:";
611621
if (logAndHeal) {
612-
System.err.println(attempt + ". of " + attempts + " attempts of calling a read TX failed:");
622+
System.err.println(message);
613623
e.printStackTrace();
614-
System.err.println(diagnose());
624+
System.err.println(diagnose);
615625
System.err.flush();
616626

617627
System.gc();
618628
System.runFinalization();
619629
cleanStaleReadTransactions();
620630
}
631+
if (defaultFailedReadTxAttemptCallback != null) {
632+
defaultFailedReadTxAttemptCallback.txFinished(null, new DbException(message + " \n" + diagnose, e));
633+
}
621634
try {
622635
Thread.sleep(backoffInMs);
623636
} catch (InterruptedException ie) {
@@ -854,6 +867,16 @@ public boolean isDebugRelations() {
854867
return debugRelations;
855868
}
856869

870+
@Internal
871+
public int internalDefaultQueryAttempts() {
872+
return defaultQueryAttempts;
873+
}
874+
875+
@Internal
876+
public TxCallback internalDefaultFailedReadTxAttemptCallback() {
877+
return defaultFailedReadTxAttemptCallback;
878+
}
879+
857880
void setDebugFlags(int debugFlags) {
858881
nativeSetDebugFlags(handle, debugFlags);
859882
}

objectbox-java/src/main/java/io/objectbox/BoxStoreBuilder.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import javax.annotation.Nonnull;
2525
import javax.annotation.Nullable;
2626

27+
import io.objectbox.annotation.apihint.Experimental;
2728
import io.objectbox.annotation.apihint.Internal;
2829
import io.objectbox.ideasonly.ModelUpdate;
2930

@@ -74,6 +75,10 @@ public class BoxStoreBuilder {
7475

7576
int maxReaders;
7677

78+
int defaultQueryAttempts;
79+
80+
TxCallback defaultFailedReadTxAttemptCallback;
81+
7782
final List<EntityInfo> entityInfoList = new ArrayList<>();
7883

7984
@Internal
@@ -235,7 +240,6 @@ BoxStoreBuilder modelUpdate(ModelUpdate modelUpdate) {
235240
* <p>
236241
* In general, a maximum size prevents the DB from growing indefinitely when something goes wrong
237242
* (for example you insert data in an infinite loop).
238-
*
239243
*/
240244
public BoxStoreBuilder maxSizeInKByte(long maxSizeInKByte) {
241245
this.maxSizeInKByte = maxSizeInKByte;
@@ -265,6 +269,18 @@ public BoxStoreBuilder debugRelations() {
265269
return this;
266270
}
267271

272+
@Experimental
273+
public BoxStoreBuilder defaultQueryAttempts(int defaultQueryAttempts) {
274+
this.defaultQueryAttempts = defaultQueryAttempts;
275+
return this;
276+
}
277+
278+
@Experimental
279+
public BoxStoreBuilder defaultFailedReadTxAttemptCallback(TxCallback defaultFailedReadTxAttemptCallback) {
280+
this.defaultFailedReadTxAttemptCallback = defaultFailedReadTxAttemptCallback;
281+
return this;
282+
}
283+
268284
/**
269285
* Builds a {@link BoxStore} using any given configuration.
270286
*/

objectbox-java/src/main/java/io/objectbox/query/Query.java

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,12 @@ public class Query<T> {
6565
native void nativeSetParameter(long handle, int propertyId, String parameterAlias, long value);
6666

6767
native void nativeSetParameters(long handle, int propertyId, String parameterAlias, long value1,
68-
long value2);
68+
long value2);
6969

7070
native void nativeSetParameter(long handle, int propertyId, String parameterAlias, double value);
7171

7272
native void nativeSetParameters(long handle, int propertyId, String parameterAlias, double value1,
73-
double value2);
73+
double value2);
7474

7575
private final Box<T> box;
7676
private final BoxStore store;
@@ -79,12 +79,16 @@ native void nativeSetParameters(long handle, int propertyId, String parameterAli
7979
private final List<EagerRelation> eagerRelations;
8080
private final QueryFilter<T> filter;
8181
private final Comparator<T> comparator;
82+
private final int queryAttempts;
83+
private final int initialRetryBackOffInMs = 10;
84+
8285
long handle;
8386

8487
Query(Box<T> box, long queryHandle, boolean hasOrder, List<EagerRelation> eagerRelations, QueryFilter<T> filter,
8588
Comparator<T> comparator) {
8689
this.box = box;
8790
store = box.getStore();
91+
queryAttempts = store.internalDefaultQueryAttempts();
8892
handle = queryHandle;
8993
this.hasOrder = hasOrder;
9094
publisher = new QueryPublisher<>(this, box);
@@ -115,15 +119,15 @@ public synchronized void close() {
115119
@Nullable
116120
public T findFirst() {
117121
ensureNoFilterNoComparator();
118-
return store.callInReadTx(new Callable<T>() {
122+
return store.callInReadTxWithRetry(new Callable<T>() {
119123
@Override
120124
public T call() {
121125
@SuppressWarnings("unchecked")
122126
T entity = (T) nativeFindFirst(handle, InternalAccess.getActiveTxCursorHandle(box));
123127
resolveEagerRelation(entity);
124128
return entity;
125129
}
126-
});
130+
}, queryAttempts, initialRetryBackOffInMs, true);
127131
}
128132

129133
private void ensureNoFilterNoComparator() {
@@ -149,23 +153,23 @@ private void ensureNoComparator() {
149153
@Nullable
150154
public T findUnique() {
151155
ensureNoFilterNoComparator();
152-
return store.callInReadTx(new Callable<T>() {
156+
return store.callInReadTxWithRetry(new Callable<T>() {
153157
@Override
154158
public T call() {
155159
@SuppressWarnings("unchecked")
156160
T entity = (T) nativeFindUnique(handle, InternalAccess.getActiveTxCursorHandle(box));
157161
resolveEagerRelation(entity);
158162
return entity;
159163
}
160-
});
164+
}, queryAttempts, initialRetryBackOffInMs, true);
161165
}
162166

163167
/**
164168
* Find all Objects matching the query.
165169
*/
166170
@Nonnull
167171
public List<T> find() {
168-
return store.callInReadTx(new Callable<List<T>>() {
172+
return store.callInReadTxWithRetry(new Callable<List<T>>() {
169173
@Override
170174
public List<T> call() throws Exception {
171175
long cursorHandle = InternalAccess.getActiveTxCursorHandle(box);
@@ -185,7 +189,7 @@ public List<T> call() throws Exception {
185189
}
186190
return entities;
187191
}
188-
});
192+
}, queryAttempts, initialRetryBackOffInMs, true);
189193
}
190194

191195
/**
@@ -194,15 +198,15 @@ public List<T> call() throws Exception {
194198
@Nonnull
195199
public List<T> find(final long offset, final long limit) {
196200
ensureNoFilterNoComparator();
197-
return store.callInReadTx(new Callable<List<T>>() {
201+
return store.callInReadTxWithRetry(new Callable<List<T>>() {
198202
@Override
199203
public List<T> call() {
200204
long cursorHandle = InternalAccess.getActiveTxCursorHandle(box);
201205
List entities = nativeFind(handle, cursorHandle, offset, limit);
202206
resolveEagerRelations(entities);
203207
return entities;
204208
}
205-
});
209+
}, queryAttempts, initialRetryBackOffInMs, true);
206210
}
207211

208212
/**

tests/objectbox-java-test/src/main/java/io/objectbox/AbstractObjectBoxTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ protected long time() {
147147
return System.currentTimeMillis();
148148
}
149149

150-
byte[] createTestModel(boolean withIndex) {
150+
protected byte[] createTestModel(boolean withIndex) {
151151
ModelBuilder modelBuilder = new ModelBuilder();
152152
addTestEntity(modelBuilder, withIndex);
153153
modelBuilder.lastEntityId(lastEntityId, lastEntityUid);

tests/objectbox-java-test/src/main/java/io/objectbox/BoxStoreTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import java.io.File;
2222
import java.util.concurrent.Callable;
2323

24+
import javax.annotation.Nullable;
25+
2426
import io.objectbox.exception.DbException;
2527

2628
import static org.junit.Assert.*;
@@ -159,6 +161,27 @@ public void testCallInReadTxWithRetry_fail() {
159161
store.callInReadTxWithRetry(createTestCallable(countHolder), 4, 0, true);
160162
}
161163

164+
@Test
165+
public void testCallInReadTxWithRetry_callback() {
166+
closeStoreForTest();
167+
final int[] countHolder = {0};
168+
final int[] countHolderCallback = {0};
169+
170+
BoxStoreBuilder builder = new BoxStoreBuilder(createTestModel(false)).directory(boxStoreDir)
171+
.defaultFailedReadTxAttemptCallback(new TxCallback() {
172+
@Override
173+
public void txFinished(@Nullable Object result, @Nullable Throwable error) {
174+
assertNotNull(error);
175+
countHolderCallback[0]++;
176+
}
177+
});
178+
store = builder.build();
179+
String value = store.callInReadTxWithRetry(createTestCallable(countHolder), 5, 0, true);
180+
assertEquals("42", value);
181+
assertEquals(5, countHolder[0]);
182+
assertEquals(4, countHolderCallback[0]);
183+
}
184+
162185
private Callable<String> createTestCallable(final int[] countHolder) {
163186
return new Callable<String>() {
164187
@Override

tests/objectbox-java-test/src/main/java/io/objectbox/query/QueryTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,14 @@
2323
import java.util.Comparator;
2424
import java.util.List;
2525

26+
import javax.annotation.Nullable;
27+
2628
import io.objectbox.AbstractObjectBoxTest;
2729
import io.objectbox.Box;
30+
import io.objectbox.BoxStoreBuilder;
2831
import io.objectbox.TestEntity;
2932
import io.objectbox.TestEntity_;
33+
import io.objectbox.TxCallback;
3034
import io.objectbox.query.QueryBuilder.StringOrder;
3135

3236
import static io.objectbox.TestEntity_.*;
@@ -457,6 +461,27 @@ public int compare(TestEntity o1, TestEntity o2) {
457461
assertEquals("apple", entities.get(4).getSimpleString());
458462
}
459463

464+
@Test
465+
// TODO can we improve? More than just "still works"?
466+
public void testQueryAttempts() {
467+
store.close();
468+
BoxStoreBuilder builder = new BoxStoreBuilder(createTestModel(false)).directory(boxStoreDir)
469+
.defaultQueryAttempts(5)
470+
.defaultFailedReadTxAttemptCallback(new TxCallback() {
471+
@Override
472+
public void txFinished(@Nullable Object result, @Nullable Throwable error) {
473+
error.printStackTrace();
474+
}
475+
});
476+
builder.entity(new TestEntity_());
477+
478+
store = builder.build();
479+
putTestEntitiesScalars();
480+
481+
Query<TestEntity> query = store.boxFor(TestEntity.class).query().equal(simpleInt, 2007).build();
482+
assertEquals(2007, query.findFirst().getSimpleInt());
483+
}
484+
460485
private QueryFilter<TestEntity> createTestFilter() {
461486
return new QueryFilter<TestEntity>() {
462487
@Override

0 commit comments

Comments
 (0)