/*
* Copyright (c) 2012 the original author or authors.
*
* 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.eclipse.jetty.spdy;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
import org.eclipse.jetty.spdy.api.DataInfo;
import org.eclipse.jetty.spdy.api.Handler;
import org.eclipse.jetty.spdy.api.Headers;
import org.eclipse.jetty.spdy.api.ReplyInfo;
import org.eclipse.jetty.spdy.api.Session;
import org.eclipse.jetty.spdy.api.Stream;
import org.eclipse.jetty.spdy.api.StreamFrameListener;
import org.eclipse.jetty.spdy.api.StringDataInfo;
import org.eclipse.jetty.spdy.api.SynInfo;
import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
import org.junit.Assert;
import org.junit.Test;
public class SynDataReplyDataLoadTest extends AbstractTest
{
@Test
public void testSynDataReplyDataLoad() throws Exception
{
ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
{
@Override
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
{
stream.reply(new ReplyInfo(synInfo.getHeaders(), false));
return new StreamFrameListener.Adapter()
{
@Override
public void onData(Stream stream, DataInfo dataInfo)
{
ByteBuffer buffer = dataInfo.asByteBuffer(true);
stream.data(new ByteBufferDataInfo(buffer, dataInfo.isClose()));
}
};
}
};
final Session session = startClient(startServer(serverSessionFrameListener), null);
final int iterations = 500;
final int count = 50;
final Headers headers = new Headers();
headers.put("method", "get");
headers.put("url", "/");
headers.put("version", "http/1.1");
headers.put("host", "localhost:8080");
headers.put("content-type", "application/octet-stream");
final CountDownLatch latch = new CountDownLatch(count * iterations);
session.addListener(new Session.StreamListener.Adapter()
{
@Override
public void onStreamClosed(Stream stream)
{
latch.countDown();
}
});
List<Callable<Object>> tasks = new ArrayList<>();
for (int i = 0; i < count; ++i)
{
tasks.add(new Callable<Object>()
{
@Override
public Object call() throws Exception
{
synCompletedData(session, headers, iterations);
return null;
}
});
}
ExecutorService threadPool = Executors.newFixedThreadPool(count);
List<Future<Object>> futures = threadPool.invokeAll(tasks);
for (Future<Object> future : futures)
future.get(iterations, TimeUnit.SECONDS);
Assert.assertTrue(latch.await(count * iterations, TimeUnit.SECONDS));
tasks.clear();
for (int i = 0; i < count; ++i)
{
tasks.add(new Callable<Object>()
{
@Override
public Object call() throws Exception
{
synGetDataGet(session, headers, iterations);
return null;
}
});
}
futures = threadPool.invokeAll(tasks);
for (Future<Object> future : futures)
future.get(iterations, TimeUnit.SECONDS);
Assert.assertTrue(latch.await(count * iterations, TimeUnit.SECONDS));
threadPool.shutdown();
}
private void synCompletedData(Session session, Headers headers, int iterations) throws Exception
{
final Map<Integer, Integer> counter = new ConcurrentHashMap<>(iterations);
final CountDownLatch latch = new CountDownLatch(2 * iterations);
for (int i = 0; i < iterations; ++i)
{
final AtomicInteger count = new AtomicInteger(2);
final int index = i;
counter.put(index, index);
session.syn(new SynInfo(headers, false), new StreamFrameListener.Adapter()
{
@Override
public void onReply(Stream stream, ReplyInfo replyInfo)
{
Assert.assertEquals(2, count.getAndDecrement());
latch.countDown();
}
@Override
public void onData(Stream stream, DataInfo dataInfo)
{
// TCP can split the data frames, so I may be receiving more than 1 data frame
dataInfo.asBytes(true);
if (dataInfo.isClose())
{
Assert.assertEquals(1, count.getAndDecrement());
counter.remove(index);
latch.countDown();
}
}
}, 0, TimeUnit.SECONDS, new Handler.Adapter<Stream>()
{
@Override
public void completed(Stream stream)
{
stream.data(new StringDataInfo("data_" + stream.getId(), true), 0, TimeUnit.SECONDS, null);
}
});
}
Assert.assertTrue(latch.await(iterations, TimeUnit.SECONDS));
Assert.assertTrue(counter.toString(), counter.isEmpty());
}
private void synGetDataGet(Session session, Headers headers, int iterations) throws Exception
{
final Map<Integer, Integer> counter = new ConcurrentHashMap<>(iterations);
final CountDownLatch latch = new CountDownLatch(2 * iterations);
for (int i = 0; i < iterations; ++i)
{
final AtomicInteger count = new AtomicInteger(2);
final int index = i;
counter.put(index, index);
Stream stream = session.syn(new SynInfo(headers, false), new StreamFrameListener.Adapter()
{
@Override
public void onReply(Stream stream, ReplyInfo replyInfo)
{
Assert.assertEquals(2, count.getAndDecrement());
latch.countDown();
}
@Override
public void onData(Stream stream, DataInfo dataInfo)
{
// TCP can split the data frames, so I may be receiving more than 1 data frame
dataInfo.asBytes(true);
if (dataInfo.isClose())
{
Assert.assertEquals(1, count.getAndDecrement());
counter.remove(index);
latch.countDown();
}
}
}).get(5, TimeUnit.SECONDS);
stream.data(new StringDataInfo("data_" + stream.getId(), true)).get(5, TimeUnit.SECONDS);
}
Assert.assertTrue(latch.await(iterations, TimeUnit.SECONDS));
Assert.assertTrue(counter.toString(), counter.isEmpty());
}
}