/* * Copyright (C) 2011 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jboss.errai.bus.server; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.math.BigDecimal; import java.math.MathContext; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.LockSupport; import org.jboss.errai.bus.client.tests.support.RandomProvider; import org.jboss.errai.bus.server.io.ByteWriteAdapter; import org.jboss.errai.bus.server.io.OutputStreamWriteAdapter; import org.jboss.errai.bus.server.io.buffers.BufferColor; import org.jboss.errai.bus.server.io.buffers.TransmissionBuffer; import junit.framework.TestCase; /** * @author Mike Brock */ public class TransmissionBufferTests extends TestCase { static { // make sure protocol provider is initialized; } public void testBufferWriteAndRead() { final TransmissionBuffer buffer = TransmissionBuffer.createDirect(); final String s = "This is a test"; final BufferColor colorA = BufferColor.getNewColor(); try { final ByteArrayInputStream bInputStream = new ByteArrayInputStream(s.getBytes()); buffer.write(s.length(), bInputStream, colorA); final ByteArrayOutputStream bOutputStream = new ByteArrayOutputStream(); buffer.read(new OutputStreamWriteAdapter(bOutputStream), colorA); assertEquals(s, new String(bOutputStream.toByteArray())); } catch (final IOException e) { throw new RuntimeException(e); } } public void testBufferCycle() throws IOException { final TransmissionBuffer buffer = TransmissionBuffer.create(10, 10); final BufferColor color = BufferColor.getNewColor(); final String s = "12345789012345"; final long start = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { final ByteArrayInputStream bInputStream = new ByteArrayInputStream(s.getBytes()); final ByteArrayOutputStream bOutputStream = new ByteArrayOutputStream(); buffer.write(s.length(), bInputStream, color); buffer.read(new OutputStreamWriteAdapter(bOutputStream), color); assertEquals(s, new String(bOutputStream.toByteArray())); } System.out.println(System.currentTimeMillis() - start); } public void testColorInterleaving() throws IOException { final TransmissionBuffer buffer = TransmissionBuffer.create(10, 20); final BufferColor colorA = BufferColor.getNewColor(); final BufferColor colorB = BufferColor.getNewColor(); final BufferColor colorC = BufferColor.getNewColor(); final String stringA = "12345678"; final String stringB = "ABCDEFGH"; final String stringC = "IJKLMNOP"; final long start = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { ByteArrayInputStream bInputStream = new ByteArrayInputStream(stringA.getBytes()); buffer.write(stringA.length(), bInputStream, colorA); bInputStream = new ByteArrayInputStream(stringB.getBytes()); buffer.write(stringB.length(), bInputStream, colorB); bInputStream = new ByteArrayInputStream(stringC.getBytes()); buffer.write(stringC.length(), bInputStream, colorC); ByteArrayOutputStream bOutputStream = new ByteArrayOutputStream(); buffer.read(new OutputStreamWriteAdapter(bOutputStream), colorA); assertEquals(stringA, new String(bOutputStream.toByteArray())); bOutputStream = new ByteArrayOutputStream(); buffer.read(new OutputStreamWriteAdapter(bOutputStream), colorB); assertEquals(stringB, new String(bOutputStream.toByteArray())); bOutputStream = new ByteArrayOutputStream(); buffer.read(new OutputStreamWriteAdapter(bOutputStream), colorC); assertEquals(stringC, new String(bOutputStream.toByteArray())); } System.out.println(System.currentTimeMillis() - start); } final static int COLOR_COUNT = 1; public void testAudited() throws Exception { final List<BufferColor> colors = new ArrayList<>(); final TransmissionBuffer buffer = TransmissionBuffer.create(); for (int i = 0; i < COLOR_COUNT; i++) { colors.add(BufferColor.getNewColor()); } final Random random = new Random(2234); final String[] writeString = {"<JIMMY>", "<CRAB>", "<KITTY>", "<DOG>", "<JONATHAN>"}; final Map<Short, List<String>> writeLog = new HashMap<>(); final int createCount = 500; final AtomicInteger totalWrites = new AtomicInteger(); final List<String> results = Collections.synchronizedList(new ArrayList<String>()); for (int i = 0; i < createCount; i++) { final BufferColor toContend = colors.get(random.nextInt(COLOR_COUNT)); assertNotNull(toContend); new Runnable() { @Override public void run() { try { final String toWrite = writeString[random.nextInt(writeString.length)]; final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(toWrite.getBytes()); buffer.write(toWrite.getBytes().length, byteArrayInputStream, toContend); totalWrites.incrementAndGet(); List<String> stack = writeLog.get(toContend.getColor()); if (stack == null) { writeLog.put(toContend.getColor(), stack = new ArrayList<>()); } stack.add(toWrite); System.out.println("Wrote color " + toContend.getColor() + ": " + toWrite + ". Total writes is now " + totalWrites); } catch (final IOException e) { e.printStackTrace(); } } }.run(); } assertEquals(createCount, totalWrites.intValue()); final AtomicInteger resultSequenceNumber = new AtomicInteger(); for (int i = 0; i < COLOR_COUNT; i++) { resultSequenceNumber.incrementAndGet(); final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); byteArrayOutputStream.reset(); assertEquals(0, byteArrayOutputStream.size()); buffer.read(new OutputStreamWriteAdapter(byteArrayOutputStream), colors.get(i)); assertTrue("Expected >0 bytes; got " + byteArrayOutputStream.size(), byteArrayOutputStream.size() > 0); final String val = new String(byteArrayOutputStream.toByteArray()); results.add(val); final List<String> buildResultList = new ArrayList<>(); int st = 0; for (int c = 0; c < val.length(); c++) { switch (val.charAt(c)) { case '>': c++; buildResultList.add(val.substring(st, st = c)); } } final List<String> resultList = new ArrayList<>(buildResultList); final List<String> log = new ArrayList<>(writeLog.get(colors.get(i).getColor())); while (!log.isEmpty() && !resultList.isEmpty()) { final String nm = log.remove(0); final String test = resultList.remove(0); if (!nm.equals(test)) { System.out.println("[" + resultSequenceNumber + "] expected : " + nm + " -- but found: " + test + " (color: " + colors.get(i).getColor() + ")"); System.out.println(" --> log: " + writeLog.get(colors.get(i).getColor()) + " vs result: " + buildResultList); } } if (!log.isEmpty()) System.out.println("[" + resultSequenceNumber + "] results have missing items: " + log + " (color: " + colors.get(i).getColor() + ")"); if (!resultList.isEmpty()) System.out.println("[" + resultSequenceNumber + "] results contain items not logged: " + resultList + " (color: " + colors.get(i).getColor() + ")"); } assertEquals(COLOR_COUNT, results.size()); int count = 0; for (final String res : results) { for (int i = 0; i < res.length(); i++) { if (res.charAt(i) == '<') count++; } System.out.println(); System.out.print(res); } buffer.dumpSegments(new PrintWriter(System.out)); assertEquals(createCount, count); } public void testLargeOversizedSegments() { final TransmissionBuffer buffer = TransmissionBuffer.create(); final BufferColor colorA = BufferColor.getNewColor(); final RandomProvider random = new RandomProvider() { private final char[] CHARS = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}; private final Random random = new Random(System.nanoTime()); @Override public boolean nextBoolean() { return random.nextBoolean(); } @Override public int nextInt(final int upper) { return random.nextInt(upper); } @Override public double nextDouble() { return new BigDecimal(random.nextDouble(), MathContext.DECIMAL32).doubleValue(); } @Override public char nextChar() { return CHARS[nextInt(1000) % CHARS.length]; } @Override public String randString() { final StringBuilder builder = new StringBuilder(); final int len = nextInt(25) + 5; for (int i = 0; i < len; i++) { builder.append(nextChar()); } return builder.toString(); } }; final StringBuilder builder = new StringBuilder(); for (int i = 0; i < 1024 * 10; i++) { builder.append(random.nextChar()); } final String s = builder.toString(); final ByteArrayInputStream bInputStream = new ByteArrayInputStream(s.getBytes()); final ByteArrayOutputStream bOutputStream = new ByteArrayOutputStream(); try { for (int i = 0; i < 10000; i++) { bInputStream.reset(); buffer.write(s.length(), bInputStream, colorA); bOutputStream.reset(); buffer.read(new OutputStreamWriteAdapter(bOutputStream), colorA); assertEquals(s, new String(bOutputStream.toByteArray())); } } catch (final IOException e) { throw new RuntimeException(e); } } final static int SEGMENT_COUNT = 10; /** * ****** * * @throws Exception */ @SuppressWarnings({"ResultOfMethodCallIgnored", "ConstantConditions"}) public void testMultithreadedBufferUse() throws Exception { final File logFile = new File("multithread_test.log"); final File rawBufferFile = new File("raw_buffer.log"); if (!logFile.exists()) logFile.createNewFile(); if (!rawBufferFile.exists()) rawBufferFile.createNewFile(); final TransmissionBuffer buffer = TransmissionBuffer.createDirect(32, 32000); final OutputStream fileLog = new BufferedOutputStream(new FileOutputStream(logFile, false)); final OutputStream rawBuffer = new BufferedOutputStream(new FileOutputStream(rawBufferFile, false)); final PrintWriter logWriter = new PrintWriter(fileLog); logWriter.println("START SESSION: " + new Date().toString()); try { final List<BufferColor> segs = new ArrayList<>(); for (int i = 0; i < SEGMENT_COUNT; i++) { segs.add(BufferColor.getNewColor()); } final Collection<String> writeAuditLog = new ConcurrentLinkedQueue<>(); final Collection<String> readAuditLog = new ConcurrentLinkedQueue<>(); final int createCount = 10000; final String[] writeString = new String[createCount]; for (int i = 0; i < createCount; i++) { writeString[i] = "<:::" + i + ":::>"; } final ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(10); // logWriter.print("SESSION NUMBER " + outerCount); System.out.println("Running multi-threaded stress test ..."); writeAuditLog.clear(); readAuditLog.clear(); final AtomicInteger totalWrites = new AtomicInteger(); final AtomicInteger totalReads = new AtomicInteger(); final CountDownLatch latch = new CountDownLatch(createCount); class TestReader { volatile boolean running = true; public void read(final BufferColor color, final boolean wait, final boolean straggle) throws Exception { final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); final OutputStreamWriteAdapter adapter = new OutputStreamWriteAdapter(byteArrayOutputStream); if (straggle) { buffer.readWait(TimeUnit.SECONDS, 30, adapter, color); LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(10)); } else if (wait) { buffer.readWait(TimeUnit.SECONDS, 1, adapter, color); } else { buffer.read(adapter, color); } final String val = new String(byteArrayOutputStream.toByteArray()).trim(); final List<String> buildResultList = new ArrayList<>(); logWriter.println(val); int st = 0; for (int c = 0; c < val.length(); c++) { switch (val.charAt(c)) { case '>': { buildResultList.add(val.substring(st, st = (c + 1))); } } } if (st < val.length()) { fail("malformed data: {{" + val + "}} length wrong: (waitread:" + wait + ")"); } if (val.length() > 0 && val.charAt(val.length() - 1) != '>') { fail("malformed data: {{" + val + "}} (waitread:" + wait + ")"); } boolean match; for (final String s : buildResultList) { match = false; for (final String testString : writeString) { if (s.equals(testString)) { totalReads.incrementAndGet(); match = true; } } assertTrue("unrecognized test string: {{" + s + "}}", match); } readAuditLog.addAll(buildResultList); } } final TestReader testReader = new TestReader(); final Thread[] readers = new Thread[SEGMENT_COUNT]; readers[0] = new Thread() { final BufferColor color = segs.get(0); @Override public void run() { try { while (testReader.running) { testReader.read(color, true, true); } } catch (final Throwable t) { t.printStackTrace(); } } }; readers[0].start(); for (int i = 1; i < SEGMENT_COUNT; i++) { final int item = i; readers[i] = new Thread() { final BufferColor color = segs.get(item); @Override public void run() { try { while (testReader.running) { testReader.read(color, true, false); } } catch (final Throwable t) { t.printStackTrace(); } } }; readers[i].start(); } for (int i = 0; i < createCount; i++) { final int item = i; exec.execute(new Runnable() { @Override public void run() { try { final String toWrite = writeString[item]; writeAuditLog.add(toWrite); final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(toWrite.getBytes()); buffer.write(toWrite.length(), byteArrayInputStream, segs.get(item % SEGMENT_COUNT)); totalWrites.incrementAndGet(); latch.countDown(); } catch (final Throwable e) { e.printStackTrace(); } } }); } /** * Wait a maximum of 20 seconds. */ latch.await(30, TimeUnit.SECONDS); LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(2)); testReader.running = false; for (final Thread t : readers) { t.join(); } exec.shutdownNow(); if (totalWrites.intValue() != totalReads.intValue()) { /** * Double check that there isn't anything un-read. */ LockSupport.parkNanos(100000); for (int i = 0; i < SEGMENT_COUNT; i++) { try { testReader.read(segs.get(i), false, false); } catch (final Exception e) { e.printStackTrace(); } } if (totalWrites.intValue() != totalReads.intValue()) { // new ReadWriteOrderAnalysis().analyze(); System.out.println("-----"); System.out.println("different number of reads and writes (writes=" + totalWrites + ";reads=" + totalReads + ")"); } } System.out.println("Read / Write Symmetry Analysis ... "); for (final String s : writeAuditLog) { if (!readAuditLog.contains(s)) { final Collection<String> leftDiff = new ArrayList<>(writeAuditLog); leftDiff.removeAll(readAuditLog); final Collection<String> rightDiff = new ArrayList<>(readAuditLog); rightDiff.removeAll(writeAuditLog); final Set<String> uniqueReads = new HashSet<>(readAuditLog); final List<String> duplicates = new ArrayList<>(readAuditLog); if (uniqueReads.size() < readAuditLog.size()) { for (final String str : uniqueReads) { duplicates.remove(duplicates.indexOf(str)); } } System.out.println("duplicates: " + duplicates); // new ReadWriteOrderAnalysis().analyze(); fail(s + " was written, but never read (leftDiff=" + leftDiff + ";rightDiff=" + rightDiff + ";duplicatesInReadLog=" + duplicates + ")"); } } System.out.println("Done.\n"); } finally { buffer.dumpSegments(logWriter); logWriter.flush(); fileLog.flush(); fileLog.close(); rawBuffer.flush(); rawBuffer.close(); } } public void testMultiThreadedManyTimes() throws Exception { for (int i = 0; i < 5; i++) { testMultithreadedBufferUse(); } } public void testGloballyVisibleColors() throws IOException { final BufferColor colorA = BufferColor.getNewColor(); final BufferColor colorB = BufferColor.getNewColor(); final BufferColor globalColor = BufferColor.getAllBuffersColor(); final TransmissionBuffer buffer = TransmissionBuffer.create(5, 2500); final String stringA = "12345678"; final String stringB = "ABCDEFGH"; final String stringC = "IJKLMNOP"; final long start = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { ByteArrayInputStream bInputStream = new ByteArrayInputStream(stringA.getBytes()); buffer.write(stringA.length(), bInputStream, colorA); bInputStream = new ByteArrayInputStream(stringB.getBytes()); buffer.write(stringB.length(), bInputStream, colorB); bInputStream = new ByteArrayInputStream(stringC.getBytes()); buffer.write(stringC.length(), bInputStream, globalColor); ByteArrayOutputStream bOutputStream = new ByteArrayOutputStream(); buffer.read(new OutputStreamWriteAdapter(bOutputStream), colorA); assertEquals(stringA + stringC, new String(bOutputStream.toByteArray())); bOutputStream = new ByteArrayOutputStream(); buffer.read(new OutputStreamWriteAdapter(bOutputStream), colorB); assertEquals(stringB + stringC, new String(bOutputStream.toByteArray())); } System.out.println(System.currentTimeMillis() - start); } public void testBufferColorCyclesAroundCorrectly() throws IOException { final int loopMax = Short.MAX_VALUE * 2 + 10; final short allBufferColor = BufferColor.getAllBuffersColor().getColor(); for (int i = 0; i < loopMax; i++) { if (BufferColor.getNewColor().getColor() == allBufferColor) { fail("a new color should never be the all-buffers color!"); } } } public static String createGiantString() { final int size = TransmissionBuffer.DEFAULT_SEGMENT_SIZE * 3; final StringBuilder sb = new StringBuilder(size + 10); int i = 0; while (sb.length() < size) { sb.append(String.format("%10d,", i++)); } return sb.toString(); } public void testExtremelyLargeSegmentsInterleavedWithSmallSegments() throws IOException { final TransmissionBuffer buffer = TransmissionBuffer.create(); final BufferColor globalColor = BufferColor.getAllBuffersColor(); final BufferColor red = BufferColor.getNewColor(); final StringBuilder sb = new StringBuilder(); for (int i = 0; i < 1; i++) { final String s = createGiantString(); sb.append(s); final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(s.getBytes()); buffer.write(byteArrayInputStream.available(), byteArrayInputStream, globalColor); } final String s = "this is a short string"; final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(s.getBytes()); sb.append(s); buffer.write(byteArrayInputStream.available(), byteArrayInputStream, globalColor); byteArrayInputStream.reset(); final StringBuilder out = new StringBuilder(); final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // for (int i = 0; i < 5; i++) { buffer.read(new OutputStreamWriteAdapter(byteArrayOutputStream), red); // } for (final byte b : byteArrayOutputStream.toByteArray()) { out.append((char) b); } assertEquals(sb.toString(), out.toString()); } public void testContendedReads() throws IOException, InterruptedException { final TransmissionBuffer buffer = TransmissionBuffer.create(); final BufferColor globalColor = BufferColor.getAllBuffersColor(); final BufferColor red = BufferColor.getNewColor(); final Thread[] threads = new Thread[2]; class Runstatus { boolean run = true; } final Runstatus runstatus = new Runstatus(); for (int i = 0; i < threads.length; i++) { threads[i] = new Thread() { @Override public void run() { try { while (runstatus.run) { final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); final OutputStreamWriteAdapter adapter = new OutputStreamWriteAdapter(byteArrayOutputStream); buffer.readWait(TimeUnit.MILLISECONDS, 100, adapter, red); final StringBuilder out = new StringBuilder(); for (final byte b : byteArrayOutputStream.toByteArray()) { out.append((char) b); } } } catch (final Throwable t) { t.printStackTrace(); } } }; } for (final Thread thread : threads) { thread.start(); } final String s = "this is a short string"; final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(s.getBytes()); buffer.write(byteArrayInputStream.available(), byteArrayInputStream, red); Thread.sleep(2000); runstatus.run = false; for (final Thread thread : threads) { thread.join(); } } public void testNoIndexOutOfBoundsExceptionWhenWritingAfterWriteHeadGetsLarge() throws Exception { // Preparation to get the writeSequenceNumber large enough final int segmentSize = 1; final int segments = Integer.MAX_VALUE / 4; final TransmissionBuffer buffer = TransmissionBuffer.createDirect(segmentSize, segments); final InputStream nullInputStream = new InputStream() { @Override public int read() throws IOException { return 0; } }; final BufferColor color = BufferColor.getNewColorFromHead(buffer); long accum = 0; final int writeSize = segmentSize * segments; while ((accum + writeSize) < Integer.MAX_VALUE) { buffer.write(writeSize, nullInputStream, color); accum += writeSize; } // Actual test try { buffer.write(writeSize, nullInputStream, color); } catch (final IndexOutOfBoundsException ex) { throw new AssertionError(ex); } } public void testNoIndexOutOfBoundsExceptionWhenReadingAfterWriteHeadGetsLarge() throws Exception { // Preparation to get the writeSequenceNumber large enough final int segmentSize = Integer.MAX_VALUE / 4; final int segments = 4; final TransmissionBuffer buffer = TransmissionBuffer.createDirect(segmentSize, segments); final InputStream nullInputStream = new InputStream() { @Override public int read() throws IOException { return 0; } }; final BufferColor color = BufferColor.getNewColorFromHead(buffer); long accum = 0; final int writeSize = segmentSize * segments; while ((accum + writeSize) < Integer.MAX_VALUE) { buffer.write(writeSize, nullInputStream, color); accum += writeSize; } buffer.write(writeSize, nullInputStream, color); // Actual test try { buffer.read(new ByteWriteAdapter() { @Override public void write(final byte[] b) throws IOException { } @Override public void write(final byte b) throws IOException { } @Override public void write(final int b) throws IOException { } @Override public void flush() throws IOException { } }, color); } catch (final IndexOutOfBoundsException ex) { throw new AssertionError(ex); } } }