Skip to content

Commit f64567e

Browse files
committed
Assert lease wraps the same pooled QueryImpl
borrowQuery() returns a thin Query lease that is freshly allocated on every borrow, while the heavy state it delegates to -- the per-worker QueryImpl -- is pre-allocated once and reused across borrows. Nothing pinned that the fresh wrapper actually points back at the one pooled QueryImpl, so a regression that allocated a new QueryImpl per borrow (or dropped the worker's reuse) would have gone unnoticed here. Add testLeaseWrapsSamePooledQueryImpl: two lease() calls on the same worker must return distinct wrappers (assertNotSame) that delegate to the same pooled QueryImpl (assertSame on the reflected impl field). lease() never dereferences the client or pool, so the worker is built with nulls, mirroring the null-worker shortcut the reset test already uses.
1 parent e30a59c commit f64567e

1 file changed

Lines changed: 35 additions & 0 deletions

File tree

core/src/test/java/io/questdb/client/test/impl/QueryImplResetTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,41 @@ public void testResetForBorrowClearsBuilderState() throws Exception {
9797
doneF.getBoolean(q));
9898
}
9999

100+
/**
101+
* {@code QuestDB#borrowQuery()} returns a thin lease that is freshly
102+
* allocated per borrow, but the heavy state it wraps -- the per-worker
103+
* {@code QueryImpl} -- is pre-allocated once and reused across borrows. This
104+
* pins that contract: two {@code lease()} calls on the same worker return
105+
* distinct lease wrappers that delegate to the same pooled {@code QueryImpl}.
106+
* Reaches both package-private classes by reflection.
107+
*/
108+
@Test
109+
public void testLeaseWrapsSamePooledQueryImpl() throws Exception {
110+
Class<?> workerClass = Class.forName("io.questdb.client.impl.QueryWorker");
111+
Class<?> poolClass = Class.forName("io.questdb.client.impl.QueryClientPool");
112+
Class<?> clientClass = Class.forName("io.questdb.client.cutlass.qwp.client.QwpQueryClient");
113+
Class<?> leaseClass = Class.forName("io.questdb.client.impl.QueryLease");
114+
115+
// lease() never dereferences the client or pool (it only resets the
116+
// reused QueryImpl and stamps the current generation), so nulls are fine
117+
// for this structure-only test -- mirrors the null-worker shortcut above.
118+
Constructor<?> ctor = workerClass.getDeclaredConstructor(clientClass, poolClass, int.class);
119+
ctor.setAccessible(true);
120+
Object worker = ctor.newInstance(null, null, 0);
121+
122+
Method leaseM = workerClass.getDeclaredMethod("lease");
123+
leaseM.setAccessible(true);
124+
Object leaseA = leaseM.invoke(worker);
125+
Object leaseB = leaseM.invoke(worker);
126+
127+
Assert.assertNotSame("each borrow must hand back a fresh lease wrapper", leaseA, leaseB);
128+
129+
Field implF = leaseClass.getDeclaredField("impl");
130+
implF.setAccessible(true);
131+
Assert.assertSame("both leases must wrap the same pooled QueryImpl (zero-allocation reuse of the heavy state)",
132+
implF.get(leaseA), implF.get(leaseB));
133+
}
134+
100135
private static final class NoopHandler implements QwpColumnBatchHandler {
101136
@Override
102137
public void onBatch(QwpColumnBatch batch) {

0 commit comments

Comments
 (0)