private boolean tryEmit(Buffer buffer) { IN next; Iterator<Buffer.View> views = codec.iterateDecode(buffer, decoderContext); if (!views.hasNext()) { combine(buffer); return false; } Buffer.View cursor; while (views.hasNext()) { cursor = views.next(); if (cursor != null) { next = codec.decodeNext(cursor.get(), decoderContext); if (next != null && BackpressureUtils.getAndSub(PENDING_UPDATER, this, 1L) > 0) { subscriber.onNext(next); } else { combine(buffer.slice(cursor.getStart(), buffer.limit())); return next != null; } } else { combine(buffer); return false; } } if (buffer.remaining() > 0) { combine(buffer); return false; } return true; }
@Override public Buffer get() { Buffer b = Buffer.this.duplicate(); b.limit(end); b.position(start); return b; }
/** * Create a new {@code Buffer} by copying the underlying {@link ByteBuffer} into a newly-allocated * {@code Buffer}. * * @return the new {@code Buffer} */ public Buffer copy() { if (buffer == null) return new Buffer(); snapshot(); Buffer b = new Buffer(buffer.remaining(), false); b.append(buffer); reset(); return b.flip(); }
@Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Buffer)) { return false; } Buffer that = (Buffer) o; return buffer.equals(that.byteBuffer()); }
/** * Very efficient method for parsing an {@link Integer} from the given {@literal Buffer} range. * Much faster than {@link Integer#parseInt(String)}. * * @param b The {@literal Buffer} to slice. * @param start start of the range. * @param end end of the range. * @return The int value or {@literal null} if the {@literal Buffer} could not be read. */ public static Integer parseInt(Buffer b, int start, int end) { b.snapshot(); b.buffer.limit(end); b.buffer.position(start); Integer i = parseInt(b); b.reset(); return i; }
/** * Append the given {@link Buffer} to this {@literal Buffer}. * * @param buffers The {@link Buffer Buffers} to append. * @return {@literal this} */ public Buffer append(Buffer... buffers) { if (buffers == null) return this; for (Buffer b : buffers) { if (b == null) continue; int pos = position(); int len = b.remaining(); ensureCapacity(len); if (b.byteBuffer() != null) { buffer.put(b.byteBuffer()); buffer.position(pos + len); } } return this; }
@Override public IN apply(Buffer buffer) { if (consumer != null) { int pos; while ((pos = buffer.position()) < buffer.limit()) { super.apply(buffer); if (pos == buffer.position()) { break; } } return null; } return super.apply(buffer); }
private String readTypeName(Buffer buffer) { int len = buffer.readInt(); if (buffer.remaining() <= len) { throw new IllegalArgumentException( "Incomplete buffer. Must contain " + len + " bytes, " + "but only " + buffer.remaining() + " were found."); } byte[] bytes = new byte[len]; buffer.read(bytes); return new String(bytes); }
@Override protected IN decodeNext(Buffer buffer, Object context) { try { Class<IN> clazz = readType(buffer); byte[] bytes = buffer.asBytes(); buffer.position(buffer.limit()); return deserializer(engine, clazz).apply(bytes); } catch (RuntimeException e) { if (log.isErrorEnabled()) { log.error("Could not decode " + buffer, e); } throw e; } }
/** * Split this buffer on the given delimiter. Save memory by reusing the provided {@code List}. The * delimiter is stripped from the end of the segment if {@code stripDelimiter} is {@code true}. * * @param views The already-allocated List to reuse. * @param delimiter The multi-byte delimiter. * @param stripDelimiter {@literal true} to ignore the delimiter, {@literal false} to leave it in * the returned data. * @return An {@link Iterable} of {@link View Views} that point to the segments of this buffer. */ public Iterable<View> split(List<View> views, Buffer delimiter, boolean stripDelimiter) { snapshot(); byte[] delimBytes = delimiter.asBytes(); if (delimBytes.length == 0) { return Collections.emptyList(); } int start = this.position; for (byte b : this) { if (b != delimBytes[0]) { continue; } int end = -1; for (int i = 1; i < delimBytes.length; i++) { if (read() == delimBytes[i]) { end = stripDelimiter ? buffer.position() - delimBytes.length : buffer.position(); } else { end = -1; break; } } if (end > 0) { views.add(createView(start, end)); start = end + (stripDelimiter ? delimBytes.length : 0); } } if (start != buffer.position()) { buffer.position(start); } reset(); return views; }
@Test public void testAutoExpand() { int initial_small_size = Buffer.SMALL_BUFFER_SIZE; int initial_max_size = Buffer.MAX_BUFFER_SIZE; try { Buffer b = new Buffer(); Buffer.SMALL_BUFFER_SIZE = 20; // to speed up the test Buffer.MAX_BUFFER_SIZE = 100; for (int i = 0; i < Buffer.MAX_BUFFER_SIZE - Buffer.SMALL_BUFFER_SIZE; i++) { b.append((byte) 0x1); } } finally { Buffer.SMALL_BUFFER_SIZE = initial_small_size; Buffer.MAX_BUFFER_SIZE = initial_max_size; } }
@Test public void testCompleteSignalIsReceived() throws InterruptedException { AeronProcessor processor = AeronProcessor.create(createContext()); Flux.just(Buffer.wrap("One"), Buffer.wrap("Two"), Buffer.wrap("Three")).subscribe(processor); TestSubscriber<String> subscriber = TestSubscriber.create(0); Buffer.bufferToString(processor).subscribe(subscriber); subscriber.request(1); subscriber.awaitAndAssertNextValues("One"); subscriber.request(1); subscriber.awaitAndAssertNextValues("Two"); subscriber.request(1); subscriber.awaitAndAssertNextValues("Three").assertComplete(); }
@Override protected ChannelFuture doOnWrite(Object data, ChannelHandlerContext ctx) { if (data.getClass().equals(Buffer.class)) { body.append((Buffer) data); return null; } else { return ctx.write(data); } }
@Test public void testClientReceivesException() throws InterruptedException { AeronProcessor processor = AeronProcessor.create(createContext()); // as error is delivered on a different channelId compared to signal // its delivery could shutdown the processor before the processor subscriber // receives signal Flux.concat( Flux.just(Buffer.wrap("Item")), Flux.error(new RuntimeException("Something went wrong"))) .subscribe(processor); TestSubscriber<String> subscriber = TestSubscriber.create(); Buffer.bufferToString(processor).subscribe(subscriber); subscriber .await(TIMEOUT) .assertErrorWith(t -> assertThat(t.getMessage(), is("Something went wrong"))); }
@Override protected void doOnTerminate( ChannelHandlerContext ctx, ChannelFuture last, final ChannelPromise promise) { if (request.method() == Method.WS) { return; } ByteBuffer byteBuffer = body.flip().byteBuffer(); if (request.checkHeader()) { HttpRequest req = new DefaultFullHttpRequest( request.getNettyRequest().getProtocolVersion(), request.getNettyRequest().getMethod(), request.getNettyRequest().getUri(), byteBuffer != null ? Unpooled.wrappedBuffer(byteBuffer) : Unpooled.EMPTY_BUFFER); req.headers().add(request.headers().delegate()); if (byteBuffer != null) { HttpHeaders.setContentLength(req, body.limit()); } ctx.writeAndFlush(req) .addListener( new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) { promise.trySuccess(); } else { promise.tryFailure(future.cause()); } } }); } else { ctx.write( new DefaultHttpContent( byteBuffer != null ? Unpooled.wrappedBuffer(byteBuffer) : Unpooled.EMPTY_BUFFER)); } body.reset(); }
@Test public void testRemotePublisherReceivesErrorBeforeProcessorIsShutdown() throws InterruptedException { AeronProcessor processor = AeronProcessor.create(createContext()); Flux.<Buffer>error(new Exception("Oops!")).subscribe(processor); TestSubscriber<String> subscriber = TestSubscriber.create(0); Buffer.bufferToString(processor).subscribe(subscriber); AeronFlux remotePublisher = new AeronFlux(createContext()); TestSubscriber<String> remoteSubscriber = TestSubscriber.create(0); Buffer.bufferToString(remotePublisher).subscribe(remoteSubscriber); subscriber.request(1); remoteSubscriber.request(1); subscriber.await(TIMEOUT).assertError(); remoteSubscriber.await(TIMEOUT).assertError(); }
/** * Very efficient method for parsing an {@link Integer} from the given {@literal Buffer}. Much * faster than {@link Integer#parseInt(String)}. * * @param b The {@literal Buffer} to slice. * @return The int value or {@literal null} if the {@literal Buffer} could not be read. */ public static Integer parseInt(Buffer b) { if (b.remaining() == 0) { return null; } b.snapshot(); int len = b.remaining(); int num = 0; int dec = 1; for (int i = (b.position + len); i > b.position; ) { char c = (char) b.buffer.get(--i); num += Character.getNumericValue(c) * dec; dec *= 10; } b.reset(); return num; }
@Test public void decode() throws InterruptedException { Stream<ByteBuffer> source = Streams.just(Buffer.wrap("{\"foo\": \"foofoo\", \"bar\": \"barbar\"}").byteBuffer()); List<Object> results = Streams.wrap(decoder.decode(source, ResolvableType.forClass(Pojo.class), null)) .toList() .await(); assertEquals(1, results.size()); assertEquals("foofoo", ((Pojo) results.get(0)).getFoo()); }
@Test public void testRemotePublisherReceivesCompleteBeforeProcessorIsShutdown() throws InterruptedException { AeronProcessor processor = AeronProcessor.create(createContext()); Flux.just(Buffer.wrap("Live")).subscribe(processor); TestSubscriber<String> subscriber = TestSubscriber.create(0); Buffer.bufferToString(processor).subscribe(subscriber); AeronFlux remotePublisher = new AeronFlux(createContext()); TestSubscriber<String> remoteSubscriber = TestSubscriber.create(0); Buffer.bufferToString(remotePublisher).subscribe(remoteSubscriber); subscriber.request(1); remoteSubscriber.request(1); subscriber.awaitAndAssertNextValues("Live").assertComplete(); remoteSubscriber.awaitAndAssertNextValues("Live").assertComplete(); }
private Buffer combine(Buffer buffer) { Buffer aggregate = this.aggregate; Buffer combined; for (; ; ) { combined = buffer.newBuffer().append(aggregate).append(buffer).flip(); if (AGGREGATE.compareAndSet(this, aggregate, combined)) { return combined; } aggregate = this.aggregate; } }
@Test public void testExceptionWithNullMessageIsHandled() throws InterruptedException { AeronProcessor processor = AeronProcessor.create(createContext()); TestSubscriber<String> subscriber = TestSubscriber.create(); Buffer.bufferToString(processor).subscribe(subscriber); Flux<Buffer> sourceStream = Flux.error(new RuntimeException()); sourceStream.subscribe(processor); subscriber.await(TIMEOUT).assertErrorWith(t -> assertThat(t.getMessage(), is(""))); }
@Test public void testEquals() { Buffer buffer = Buffer.wrap("Hello"); assertTrue(buffer.equals(Buffer.wrap("Hello"))); assertFalse(buffer.equals(Buffer.wrap("Other"))); }
@Test public void decodeMultipleChunksToArray() throws InterruptedException { JsonObjectDecoder decoder = new JsonObjectDecoder(true); Stream<ByteBuffer> source = Streams.just( Buffer.wrap("[{\"foo\": \"foofoo\", \"bar\"").byteBuffer(), Buffer.wrap(": \"barbar\"},{\"foo\": \"foofoofoo\", \"bar\": \"barbarbar\"}]") .byteBuffer()); List<String> results = Streams.wrap(decoder.decode(source, null, null)) .map( chunk -> { byte[] b = new byte[chunk.remaining()]; chunk.get(b); return new String(b, StandardCharsets.UTF_8); }) .toList() .await(); assertEquals(2, results.size()); assertEquals("{\"foo\": \"foofoo\", \"bar\": \"barbar\"}", results.get(0)); assertEquals("{\"foo\": \"foofoofoo\", \"bar\": \"barbarbar\"}", results.get(1)); }
@Test public void testCancelsUpstreamSubscriptionWhenLastSubscriptionIsCancelledAndAutoCancel() throws InterruptedException { AeronProcessor processor = AeronProcessor.create(createContext().autoCancel(true)); final CountDownLatch subscriptionCancelledLatch = new CountDownLatch(1); Publisher<Buffer> dataPublisher = new Publisher<Buffer>() { @Override public void subscribe(Subscriber<? super Buffer> subscriber) { subscriber.onSubscribe( new Subscription() { @Override public void request(long n) { System.out.println("Requested: " + n); } @Override public void cancel() { System.out.println("Upstream subscription cancelled"); subscriptionCancelledLatch.countDown(); } }); } }; dataPublisher.subscribe(processor); TestSubscriber<String> client = TestSubscriber.create(); Buffer.bufferToString(processor).subscribe(client); processor.onNext(Buffer.wrap("Hello")); client.awaitAndAssertNextValues("Hello").cancel(); assertTrue( "Subscription wasn't cancelled", subscriptionCancelledLatch.await(TIMEOUT.getSeconds(), TimeUnit.SECONDS)); }
protected <T> void assertTcpClientServerExchangedData( Class<? extends reactor.io.net.tcp.TcpServer> serverType, Class<? extends reactor.io.net.tcp.TcpClient> clientType, Buffer data) throws InterruptedException { assertTcpClientServerExchangedData( serverType, clientType, StandardCodecs.PASS_THROUGH_CODEC, data, (Buffer b) -> { byte[] b1 = data.flip().asBytes(); byte[] b2 = b.asBytes(); return Arrays.equals(b1, b2); }); }
/** * Very efficient method for parsing a {@link Long} from the given {@literal Buffer}. Much faster * than {@link Long#parseLong(String)}. * * @param b The {@literal Buffer} to slice. * @return The long value or {@literal null} if the {@literal Buffer} could not be read. */ public static Long parseLong(Buffer b) { if (b.remaining() == 0) { return null; } ByteBuffer bb = b.buffer; int origPos = bb.position(); int len = bb.remaining(); long num = 0; int dec = 1; for (int i = len; i > 0; ) { char c = (char) bb.get(--i); num += Character.getNumericValue(c) * dec; dec *= 10; } bb.position(origPos); return num; }
@Override public Buffer.View next() { int limit = buffer.limit(); int endchunk = canDecodeNext(buffer, context); if (endchunk == -1) { return null; } Buffer.View view = buffer.createView(buffer.position(), endchunk); if (buffer.remaining() > 0) { buffer.position(Math.min(limit, view.getEnd())); buffer.limit(limit); } return view; }
@Test public void testWorksWithTwoSubscribersViaEmitter() throws InterruptedException { AeronProcessor processor = AeronProcessor.create(createContext()); Flux.just(Buffer.wrap("Live"), Buffer.wrap("Hard"), Buffer.wrap("Die"), Buffer.wrap("Harder")) .subscribe(processor); FluxProcessor<Buffer, Buffer> emitter = EmitterProcessor.create(); processor.subscribe(emitter); TestSubscriber<String> subscriber1 = TestSubscriber.create(); Buffer.bufferToString(emitter).subscribe(subscriber1); TestSubscriber<String> subscriber2 = TestSubscriber.create(); Buffer.bufferToString(emitter).subscribe(subscriber2); subscriber1.awaitAndAssertNextValues("Live", "Hard", "Die", "Harder").assertComplete(); subscriber2.awaitAndAssertNextValues("Live", "Hard", "Die", "Harder").assertComplete(); }
@Test public void testNextSignalIsReceived() throws InterruptedException { AeronProcessor processor = AeronProcessor.create(createContext()); TestSubscriber<String> subscriber = TestSubscriber.create(0); Buffer.bufferToString(processor).subscribe(subscriber); subscriber.request(4); Flux.just( Buffer.wrap("Live"), Buffer.wrap("Hard"), Buffer.wrap("Die"), Buffer.wrap("Harder"), Buffer.wrap("Extra")) .subscribe(processor); subscriber.awaitAndAssertNextValues("Live", "Hard", "Die", "Harder"); subscriber.request(1); subscriber.awaitAndAssertNextValues("Extra"); subscriber.await(); }
@Override public Buffer createElement(int element) { return Buffer.wrap("" + element); }