1919package org .apache .bookkeeper .proto ;
2020
2121import static org .junit .Assert .assertEquals ;
22+ import static org .junit .Assert .assertNotNull ;
2223import static org .junit .Assert .assertTrue ;
2324import static org .mockito .ArgumentMatchers .any ;
2425import static org .mockito .ArgumentMatchers .anyLong ;
26+ import static org .mockito .ArgumentMatchers .eq ;
2527import static org .mockito .Mockito .doAnswer ;
2628import static org .mockito .Mockito .mock ;
29+ import static org .mockito .Mockito .never ;
2730import static org .mockito .Mockito .times ;
2831import static org .mockito .Mockito .verify ;
2932import static org .mockito .Mockito .when ;
4649import org .apache .bookkeeper .common .concurrent .FutureUtils ;
4750import org .apache .bookkeeper .proto .BookieProtocol .Response ;
4851import org .apache .bookkeeper .stats .NullStatsLogger ;
52+ import org .apache .bookkeeper .util .ByteBufList ;
4953import org .junit .Before ;
5054import org .junit .Test ;
5155
@@ -221,4 +225,171 @@ public void testNonFenceRequest() throws Exception {
221225 assertEquals (BookieProtocol .BATCH_READ_ENTRY , response .getOpCode ());
222226 assertEquals (BookieProtocol .EOK , response .getErrorCode ());
223227 }
224- }
228+
229+ @ Test
230+ public void testReadDataPredictsMaxCountFromUniformFirstEntrySize () throws Exception {
231+ long ledgerId = 1234L ;
232+ long firstEntryId = 1L ;
233+ int entrySize = 20 ;
234+ long maxSize = 24 + 8 + Integer .BYTES + (entrySize + Integer .BYTES ) * 2L ;
235+
236+ ByteBuf firstEntry = entryBuffer (entrySize );
237+ ByteBuf secondEntry = entryBuffer (entrySize );
238+ when (bookie .readEntry (eq (ledgerId ), eq (firstEntryId ))).thenReturn (firstEntry );
239+ when (bookie .readEntry (eq (ledgerId ), eq (firstEntryId + 1 ))).thenReturn (secondEntry );
240+
241+ BatchedReadEntryProcessor processor = createProcessor (ledgerId , firstEntryId , 5 , maxSize );
242+ ByteBufList data = (ByteBufList ) processor .readData ();
243+ assertNotNull (data );
244+ try {
245+ assertEquals (2 , data .size ());
246+ } finally {
247+ data .release ();
248+ }
249+
250+ verify (bookie , times (1 )).readEntry (eq (ledgerId ), eq (firstEntryId ));
251+ verify (bookie , times (1 )).readEntry (eq (ledgerId ), eq (firstEntryId + 1 ));
252+ verify (bookie , never ()).readEntry (eq (ledgerId ), eq (firstEntryId + 2 ));
253+ }
254+
255+ @ Test
256+ public void testReadDataReturnsFirstEntryEvenIfItAloneExceedsMaxSize () throws Exception {
257+ long ledgerId = 1235L ;
258+ long firstEntryId = 1L ;
259+ int firstEntrySize = 20 ;
260+ long maxSize = 50 ;
261+
262+ ByteBuf firstEntry = entryBuffer (firstEntrySize );
263+ when (bookie .readEntry (eq (ledgerId ), eq (firstEntryId ))).thenReturn (firstEntry );
264+
265+ BatchedReadEntryProcessor processor = createProcessor (ledgerId , firstEntryId , 5 , maxSize );
266+ ByteBufList data = (ByteBufList ) processor .readData ();
267+ assertNotNull (data );
268+ try {
269+ assertEquals (1 , data .size ());
270+ } finally {
271+ data .release ();
272+ }
273+
274+ verify (bookie , times (1 )).readEntry (eq (ledgerId ), eq (firstEntryId ));
275+ verify (bookie , never ()).readEntry (eq (ledgerId ), eq (firstEntryId + 1 ));
276+ }
277+
278+ @ Test
279+ public void testReadDataReleasesOneOverReadEntryWhenSizesGrow () throws Exception {
280+ long ledgerId = 1236L ;
281+ long firstEntryId = 1L ;
282+ int firstEntrySize = 10 ;
283+ int secondEntrySize = 40 ;
284+ long maxSize = 80 ;
285+
286+ ByteBuf firstEntry = entryBuffer (firstEntrySize );
287+ ByteBuf secondEntry = entryBuffer (secondEntrySize );
288+ when (bookie .readEntry (eq (ledgerId ), eq (firstEntryId ))).thenReturn (firstEntry );
289+ when (bookie .readEntry (eq (ledgerId ), eq (firstEntryId + 1 ))).thenReturn (secondEntry );
290+
291+ BatchedReadEntryProcessor processor = createProcessor (ledgerId , firstEntryId , 5 , maxSize );
292+ ByteBufList data = (ByteBufList ) processor .readData ();
293+ assertNotNull (data );
294+ try {
295+ assertEquals (1 , data .size ());
296+ assertEquals (0 , secondEntry .refCnt ());
297+ } finally {
298+ data .release ();
299+ }
300+
301+ verify (bookie , times (1 )).readEntry (eq (ledgerId ), eq (firstEntryId ));
302+ verify (bookie , times (1 )).readEntry (eq (ledgerId ), eq (firstEntryId + 1 ));
303+ verify (bookie , never ()).readEntry (eq (ledgerId ), eq (firstEntryId + 2 ));
304+ }
305+
306+ @ Test
307+ public void testReadDataStopsOnMissingSubsequentEntry () throws Exception {
308+ long ledgerId = 1237L ;
309+ long firstEntryId = 1L ;
310+ int firstEntrySize = 20 ;
311+
312+ ByteBuf firstEntry = entryBuffer (firstEntrySize );
313+ when (bookie .readEntry (eq (ledgerId ), eq (firstEntryId ))).thenReturn (firstEntry );
314+ when (bookie .readEntry (eq (ledgerId ), eq (firstEntryId + 1 )))
315+ .thenThrow (new Bookie .NoEntryException (ledgerId , firstEntryId + 1 ));
316+
317+ BatchedReadEntryProcessor processor = createProcessor (ledgerId , firstEntryId , 5 , 1024 );
318+ ByteBufList data = (ByteBufList ) processor .readData ();
319+ assertNotNull (data );
320+ try {
321+ assertEquals (1 , data .size ());
322+ } finally {
323+ data .release ();
324+ }
325+ }
326+
327+ @ Test
328+ public void testReadDataStopsOnIOExceptionAfterFirstEntry () throws Exception {
329+ long ledgerId = 1238L ;
330+ long firstEntryId = 1L ;
331+
332+ ByteBuf firstEntry = entryBuffer (20 );
333+ when (bookie .readEntry (eq (ledgerId ), eq (firstEntryId ))).thenReturn (firstEntry );
334+ when (bookie .readEntry (eq (ledgerId ), eq (firstEntryId + 1 )))
335+ .thenThrow (new IOException ("disk error" ));
336+
337+ BatchedReadEntryProcessor processor = createProcessor (ledgerId , firstEntryId , 5 , 1024 );
338+ ByteBufList data = (ByteBufList ) processor .readData ();
339+ assertNotNull (data );
340+ try {
341+ assertEquals (1 , data .size ());
342+ } finally {
343+ data .release ();
344+ }
345+ }
346+
347+ @ Test
348+ public void testProcessPacketReturnsPrefixWhenSubsequentReadFails () throws Exception {
349+ ChannelPromise promise = new DefaultChannelPromise (channel );
350+ AtomicReference <Object > writtenObject = new AtomicReference <>();
351+ CountDownLatch latch = new CountDownLatch (1 );
352+ doAnswer (invocationOnMock -> {
353+ writtenObject .set (invocationOnMock .getArgument (0 ));
354+ promise .setSuccess ();
355+ latch .countDown ();
356+ return promise ;
357+ }).when (channel ).writeAndFlush (any (Response .class ));
358+
359+ long ledgerId = 1239L ;
360+ long firstEntryId = 1L ;
361+ ByteBuf firstEntry = entryBuffer (20 );
362+ when (bookie .readEntry (eq (ledgerId ), eq (firstEntryId ))).thenReturn (firstEntry );
363+ when (bookie .readEntry (eq (ledgerId ), eq (firstEntryId + 1 )))
364+ .thenThrow (new IOException ("disk error" ));
365+
366+ BatchedReadEntryProcessor processor = createProcessor (ledgerId , firstEntryId , 5 , 1024 );
367+ processor .run ();
368+
369+ latch .await ();
370+ assertTrue (writtenObject .get () instanceof Response );
371+ BookieProtocol .BatchedReadResponse response = (BookieProtocol .BatchedReadResponse ) writtenObject .get ();
372+ try {
373+ assertEquals (BookieProtocol .EOK , response .getErrorCode ());
374+ assertEquals (1 , response .getData ().size ());
375+ } finally {
376+ response .release ();
377+ }
378+ assertEquals (0 , firstEntry .refCnt ());
379+ }
380+
381+ private BatchedReadEntryProcessor createProcessor (long ledgerId , long entryId , int maxCount , long maxSize ) {
382+ ExecutorService service = mock (ExecutorService .class );
383+ BookieProtocol .BatchedReadRequest request = BookieProtocol .BatchedReadRequest .create (
384+ BookieProtocol .CURRENT_PROTOCOL_VERSION , ledgerId , entryId , BookieProtocol .FLAG_NONE , new byte [] {},
385+ 0L , maxCount , maxSize );
386+ return BatchedReadEntryProcessor .create (request , requestHandler , requestProcessor , service , true ,
387+ 5 * 1024 * 1024 );
388+ }
389+
390+ private static ByteBuf entryBuffer (int size ) {
391+ ByteBuf entry = ByteBufAllocator .DEFAULT .buffer (size );
392+ entry .writeZero (size );
393+ return entry ;
394+ }
395+ }
0 commit comments