poor performance of rpcProxy based with netty - performance

I designed the rpc structure named tinywhale, which is is used for communication between providers and consumers. Below are the details for the consumer to invoke provider:
Below code is used to create the proxy to invoke provider interface:
public class xxxService {
#Autowired
private TinyWhaleProxy rpcProxy;
private HelloService helloService;
public String invoke(String message) throws InterruptedException {
if (helloService == null) {
helloService = rpcProxy.create(HelloService.class);
}
String result = helloService.hello(message);
return result;
}
}
Below code is the TinyWhaleProxy.create method to create proxy for the consumer, from the code you can see that I use cglib's proxy to create the Proxy instance. Then in the callback method, I create a new NettyClient to sendRequest to NettyServer to get the invoke response.:
public <T> T create(Class<?> interfaceClass) {
Enhancer enhancer = new Enhancer();
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
NettyRequest request = new NettyRequest();
request.setRequestId(UUID.randomUUID().toString());
request.setClassName(method.getDeclaringClass().getName());
request.setMethodName(method.getName());
request.setParameterTypes(method.getParameterTypes());
request.setParameterValues(args);
NettyMessage nettyMessage = new NettyMessage();
nettyMessage.setType(MessageType.SERVICE_REQ.value());
nettyMessage.setBody(request);
if (TinyWhaleProxy.this.serviceDiscovery != null) {
TinyWhaleProxy.this.serverAddress = TinyWhaleProxy.this.serviceDiscovery.discover();
}
String[] array = TinyWhaleProxy.this.serverAddress.split(":");
String host = array[0];
int port = Integer.parseInt(array[1]);
NettyClient client = new NettyClient(host, port);
NettyMessage nettyResponse = client.send(nettyMessage);
return nettyResponse != null ? JSON.toJSONString(nettyResponse.getBody()) : null;
}
});
enhancer.setInterfaces(new Class[]{interfaceClass});
T cglibProxy = enhancer.create();
return cglibProxy;
}
Below code is the NettyClient and its send method, you can see that I used the ClientHandler to handle message sending:
public class NettyClient {
private static final Logger logger = LoggerFactory.getLogger(NettyClient.class);
private ClientHandler clientHandler = new ClientHandler();
private EventLoopGroup group = new NioEventLoopGroup();
private Bootstrap bootstrap = new Bootstrap();
private Channel clientChannel;
public NettyClient(String host, int port) throws InterruptedException {
((Bootstrap)((Bootstrap)((Bootstrap)this.bootstrap.group(this.group)).channel(NioSocketChannel.class)).option(ChannelOption.TCP_NODELAY, true)).handler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline().addLast("idleStateHandler", new IdleStateHandler(5, 5, 12));
channel.pipeline().addLast("nettyMessageDecoder", new NettyMessageDecoder(1048576, 4, 4));
channel.pipeline().addLast("nettyMessageEncoder", new NettyMessageEncoder());
channel.pipeline().addLast("heartBeatHandler", new HeartBeatRequestHandler());
channel.pipeline().addLast("clientHandler", NettyClient.this.clientHandler);
channel.pipeline().addLast("loginAuthHandler", new LoginAuthRequestHandler());
}
});
ChannelFuture channelFuture = this.bootstrap.connect(host, port);
channelFuture.addListener((future) -> {
if (future.isSuccess()) {
logger.info("客户端[" + channelFuture.channel().localAddress().toString() + "]已连接...");
this.clientChannel = channelFuture.channel();
} else {
logger.info("客户端[" + channelFuture.channel().localAddress().toString() + "]连接失败,重新连接中...");
future.channel().close();
this.bootstrap.connect(host, port);
}
});
channelFuture.channel().closeFuture().addListener((cfl) -> {
this.close();
logger.info("客户端[" + channelFuture.channel().localAddress().toString() + "]已断开...");
});
}
private void close() {
if (this.clientChannel != null) {
this.clientChannel.close();
}
if (this.group != null) {
this.group.shutdownGracefully();
}
}
public NettyMessage send(NettyMessage message) throws InterruptedException, ExecutionException {
ChannelPromise promise = this.clientHandler.sendMessage(message);
promise.await(3L, TimeUnit.SECONDS);
return this.clientHandler.getResponse();
}
Below code is the detail of sending message from NettyClient to NettyServer:
public class ClientHandler extends ChannelInboundHandlerAdapter {
private static final Logger logger = LoggerFactory.getLogger(ClientHandler.class);
private ChannelHandlerContext ctx;
private ChannelPromise promise;
private NettyMessage response;
public ClientHandler() {
}
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
this.ctx = ctx;
}
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
NettyMessage message = (NettyMessage)msg;
if (message != null && message.getType() == MessageType.SERVICE_RESP.value()) {
this.response = message;
this.promise.setSuccess();
} else {
ctx.fireChannelRead(msg);
}
}
public synchronized ChannelPromise sendMessage(Object message) {
while(this.ctx == null) {
try {
TimeUnit.MILLISECONDS.sleep(1L);
logger.error("等待ChannelHandlerContext实例化");
} catch (InterruptedException var3) {
logger.error("等待ChannelHandlerContext实例化过程中出错", var3);
}
}
this.promise = this.ctx.newPromise();
this.ctx.writeAndFlush(message);
return this.promise;
}
public NettyMessage getResponse() {
return this.response;
}
}
All works that ok for me when I start a benchmark:
#BenchmarkMode(Mode.Throughput)
#OutputTimeUnit(TimeUnit.SECONDS)
#State(Scope.Thread)
public class ProxyBenchmark {
private TinyWhaleProxy rpcProxy;
private ServiceDiscovery serviceDiscovery;
private HelloService helloService;
#Setup
public void init() {
serviceDiscovery = new ServiceDiscovery("127.0.0.1:2181");
rpcProxy = new TinyWhaleProxy();
rpcProxy.setServiceDiscovery(serviceDiscovery);
helloService = rpcProxy.create(HelloService.class);
}
#Benchmark
#GroupThreads(4)
public String test() {
String result = helloService.hello("sdfsdfsdf");
return result;
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(ProxyBenchmark.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
}
}
And the benchmark result:
Warmup Iteration 2: 121.743 ops/s
Warmup Iteration 3: 103.496 ops/s
Warmup Iteration 4: 125.844 ops/s
Warmup Iteration 5: 179.424 ops/s
Warmup Iteration 6: 120.635 ops/s
Warmup Iteration 7: 86.545 ops/s
Warmup Iteration 8: 184.926 ops/s
Warmup Iteration 9: 136.450 ops/s
Warmup Iteration 10: 169.146 ops/s
Warmup Iteration 11: 161.570 ops/s
Warmup Iteration 12: 157.327 ops/s
Warmup Iteration 13: 137.892 ops/s
Warmup Iteration 14: 120.632 ops/s
Warmup Iteration 15: 191.016 ops/s
Warmup Iteration 16: 183.634 ops/s
Warmup Iteration 17: 97.625 ops/s
Warmup Iteration 18: 145.066 ops/s
Warmup Iteration 19: 203.741 ops/s
Warmup Iteration 20: 111.741 ops/s
Iteration 1: 156.113 ops/s
Iteration 2: 203.618 ops/s
Iteration 3: 134.177 ops/s
Iteration 4: 121.404 ops/s
Iteration 5: 197.915 ops/s
Iteration 6: 199.298 ops/s
Iteration 7: 65.825 ops/s
Iteration 8: 184.359 ops/s
Iteration 9: 212.469 ops/s
Iteration 10: 139.439 ops/s
Iteration 11: 112.174 ops/s
Iteration 12: 199.004 ops/s
Iteration 13: 195.232 ops/s
Iteration 14: 90.157 ops/s
Iteration 15: 115.724 ops/s
Iteration 16: 191.738 ops/s
Iteration 17: 213.633 ops/s
Iteration 18: 159.058 ops/s
Iteration 19: 62.399 ops/s
Iteration 20: 165.608 ops/s
Result "com.tw.PerformanceBenchmark.test":
155.967 ±(99.9%) 42.118 ops/s [Average]
(min, avg, max) = (62.399, 155.967, 213.633), stdev = 48.503
CI (99.9%): [113.849, 198.085] (assumes normal distribution)
The performance is really poor, only 155 ops, that's what I can't believe. I checked the code and found that below code snap took a lot of time:
NettyClient client = new NettyClient(host, port);
Any advice on how to improve the performance? using pool?
EDIT:
Late in the day, I checked the NettyClient class and found that, I send message to server side and receive the server response, this process is in sync mode that caused poor performance. Then I tried to fix this process by using promise/future pool, but not help.
Then I checked other rpc structures and found that all of these structures handle this scenario in async mode.
So I think that my research is correct, sending message to server and receive the respon in async mode. But I don't come out a good solution. Any advice?

Related

can anyone help me Spring Batch Issue? (Unintended schedule Spring Batch)

The implemented function is to send LMS to the user at the alarm time.
Send a total of 4 alarms (9:00, 13:00, 19:00, 21:00).
Log was recorded regardless of success.
It was not recorded in the Log, but when I looked at the batch data in the DB, I found an unintended COMPLETED.
Issue>
Batch was successfully executed at 9 and 13 on the 18th.
But at 13:37 it's not even a schedule, but it's executed. (and FAILED)
Subsequently, 13:38, 40, 42, 44 minutes executed. (all COMPLETED)
Q1. Why was it executed when it wasn't even the batch execution time?
Q2. I save the log even when executing batch and sending SMS. Log was printed normally at 9 and 13 o'clock.
But Log is not saved for non-schedule(13:37, 38, 40, 42, 44).
Check spring boot service and tomcat service with one
server CPU, memory usage is normal
Batch Problem
Spring Boot (2.2.6 RELEASE)
Spring Boot - Embedded Tomcat
===== Start Scheduler =====
#Component
public class DosageAlarmScheduler {
public static final int MORNING_HOUR = 9;
public static final int LUNCH_HOUR = 13;
public static final int DINNER_HOUR = 19;
public static final int BEFORE_SLEEP_HOUR = 21;
#Scheduled(cron = "'0 0 */1 * * *") // every hour
public void executeDosageAlarmJob() {
LocalDateTime nowDateTime = LocalDateTime.now();
try {
if(isExecuteTime(nowDateTime)) {
log.info("[Send LMS], {}", nowDateTime);
EatFixCd eatFixCd = currentEatFixCd(nowDateTime);
jobLauncher.run(
alarmJob,
new JobParametersBuilder()
.addString("currentDate", nowDateTime.toString())
.addString("eatFixCodeValue", eatFixCd.getCodeValue())
.toJobParameters()
);
} else {
log.info("[Not Send LMS], {}", nowDateTime);
}
} catch (JobExecutionAlreadyRunningException e) {
log.error("[JobExecutionAlreadyRunningException]", e);
} catch (JobRestartException e) {
log.error("[JobRestartException]", e);
} catch (JobInstanceAlreadyCompleteException e) {
log.error("[JobInstanceAlreadyCompleteException]", e);
} catch (JobParametersInvalidException e) {
log.error("[JobParametersInvalidException]", e);
} catch(Exception e) {
log.error("[Exception]", e);
}
/* Start private method */
private boolean isExecuteTime(LocalDateTime nowDateTime) {
return nowDateTime.getHour() == MORNING_TIME.getHour()
|| nowDateTime.getHour() == LUNCH_TIME.getHour()
|| nowDateTime.getHour() == DINNER_TIME.getHour()
|| nowDateTime.getHour() == BEFORE_SLEEP_TIME.getHour();
}
private EatFixCd currentEatFixCd(LocalDateTime nowDateTime) {
switch(nowDateTime.getHour()) {
case MORNING_HOUR:
return EatFixCd.MORNING;
case LUNCH_HOUR:
return EatFixCd.LUNCH;
case DINNER_HOUR:
return EatFixCd.DINNER;
case BEFORE_SLEEP_HOUR:
return EatFixCd.BEFORE_SLEEP;
default:
throw new RuntimeException("Not Dosage Time");
}
}
/* End private method */
}
}
===== End Scheduler =====
===== Start Job =====
#Configuration
public class DosageAlarmConfiguration {
private final int chunkSize = 20;
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
private final EntityManagerFactory entityManagerFactory;
#Bean
public Job dosageAlarmJob() {
log.info("[dosageAlarmJob excute]");
return jobBuilderFactory.get("dosageAlarmJob")
.start(dosageAlarmStep(null, null)).build();
}
#Bean
#JobScope
public Step dosageAlarmStep(
#Value("#{jobParameters[currentDate]}") String currentDate,
#Value("#{jobParameters[eatFixCodeValue]}") String eatFixCodeValue
) {
log.info("[dosageAlarm Step excute]");
return stepBuilderFactory.get("dosageAlarmStep")
.<Object[], DosageReceiverInfoDto>chunk(chunkSize)
.reader(dosageAlarmReader(currentDate, eatFixCodeValue))
.processor(dosageAlarmProcessor(currentDate, eatFixCodeValue))
.writer(dosageAlarmWriter(currentDate, eatFixCodeValue))
.build();
}
#Bean
#StepScope
public JpaPagingItemReader<Object[]> dosageAlarmReader(
#Value("#{jobParameters[currentDate]}") String currentDate,
#Value("#{jobParameters[eatFixCodeValue]}") String eatFixCodeValue
) {
log.info("[dosageAlarm Reader excute : {}, {}]", currentDate, eatFixCodeValue);
if(currentDate == null) {
return null;
} else {
JpaPagingItemReader<Object[]> jpaPagingItemReader = new JpaPagingItemReader<>();
jpaPagingItemReader.setName("dosageAlarmReader");
jpaPagingItemReader.setEntityManagerFactory(entityManagerFactory);
jpaPagingItemReader.setPageSize(chunkSize);
jpaPagingItemReader.setQueryString("select das from DosageAlarm das where :currentDate between das.startDate and das.endDate ");
HashMap<String, Object> parameterValues = new HashMap<>();
parameterValues.put("currentDate", LocalDateTime.parse(currentDate).toLocalDate());
jpaPagingItemReader.setParameterValues(parameterValues);
return jpaPagingItemReader;
}
}
#Bean
#StepScope
public ItemProcessor<Object[], DosageReceiverInfoDto> dosageAlarmProcessor(
#Value("#{jobParameters[currentDate]}") String currentDate,
#Value("#{jobParameters[eatFixCodeValue]}") String eatFixCodeValue
) {
log.info("[dosageAlarm Processor excute : {}, {}]", currentDate, eatFixCodeValue);
...
convert to DosageReceiverInfoDto
...
}
#Bean
#StepScope
public ItemWriter<DosageReceiverInfoDto> dosageAlarmWriter(
#Value("#{jobParameters[currentDate]}") String currentDate,
#Value("#{jobParameters[eatFixCodeValue]}") String eatFixCodeValue
) {
log.info("[dosageAlarm Writer excute : {}, {}]", currentDate, eatFixCodeValue);
...
make List
...
if(reqMessageDtoList != null) {
sendMessages(reqMessageDtoList);
} else {
log.info("[reqMessageDtoList not Exist]");
}
}
public SmsExternalSendResDto sendMessages(List<reqMessagesDto> reqMessageDtoList) {
log.info("[receiveList] smsTypeCd : {}, contentTypeCd : {}, messages : {}", smsTypeCd.LMS, contentTypeCd.COMM, reqMessageDtoList);
...
send Messages
}
}
===== End Job =====
Thank U.
i want to fix my problem and i hope this question is hepled another people.

How to run only one TimerTask?

I use stomp to send data A every 2 seconds and data B every 10 minutes when the websocket connection is complete.
So I divided the timerTask and implemented it.
works fine though. In addition, when websockets are connected, the same timerTask is called and the number of connected websockets is called continuously.
I need a "don't call if Timertask is already running" workaround.
//method called by controller
public void publish(DateParam param, CustomLuluSession session) throws BizException {
sendTrend(param, session); //every 2 seconds
sendIssueKeyword(param, session); //every 10 minutes
}
/*
sendTrend and sendIssueKeyword
is implemented the same, only the set time is different, so I will register only one as an example.
*/
public void sendTrend(DateParam param, CustomLuluSession session) throws BizException {
TimerTask task = trendTimerTask(param, session);
final Timer timer = new Timer();
timer.schedule(task, 0, TWO_SECOND);
}
public TimerTask trendTimerTask(DateParam param, CustomLuluSession session) throws BizException {
TimerTask tempTask = new TimerTask() {
#Override
public void run() {
try {
if (getSession() == 0) this.cancel();
getTrendInfo(param, session);
} catch (BizException e) {
throw new RuntimeException(e);
}
}
};
return tempTask;
}
// Access the db to get data and send it to the client
public void getTrendInfo(DateParam param, CustomLuluSession session) throws BizException {
FindInquiryTrendResult inquiryTrend = inquiryFacade.findInquiryTrend(param, session);
messagingTemplate.convertAndSend(ROOM_ID, inquiryTrend);
}

Spring Boot WebSocket URL Not Responding and RxJS Call Repetition?

I'm trying to follow a guide to WebSockets at https://www.devglan.com/spring-boot/spring-boot-angular-websocket
I'd like it to respond to ws://localhost:8448/wsb/softlayer-cost-file, but I'm sure I misunderstood something. I'd like to get it to receive a binary file and issue periodic updates as the file is being processed.
Questions are:
How come Spring does not respond to my requests despite all the multiple URLs I try (see below).
Does my RxJS call run once and then conclude, or does it keep running until some closure has happened? Sorry to ask what might be obvious to others.
On my Spring Boot Server start, I see no errors. After about 5-7 minutes of running, I saw the following log message:
INFO o.s.w.s.c.WebSocketMessageBrokerStats - WebSocketSession[0 current WS(0)-HttpStream(0)-HttpPoll(0), 0 total, 0 closed abnormally (0 connect failure, 0 send limit, 0 transport error)], stompSubProtocol[processed CONNECT(0)-CONNECTED(0)-DISCONNECT(0)], stompBrokerRelay[null], inboundChannel[pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0], outboundChannel[pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0], sockJsScheduler[pool size = 6, active threads = 1, queued tasks = 0, completed tasks = 5]
I've pointed my browser at these URLs and can't get the Spring Boot server to show any reaction:
ws://localhost:8448/app/message
ws://localhost:8448/greeting/app/message
ws://localhost:8448/topic
ws://localhost:8448/queue
(I got the initial request formed in Firefox, then clicked edit/resend to try again).
WebSocketConfig.java
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Autowired
CostFileUploadWebSocketHandler costFileUploadWebSocketHandler;
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new SocketTextHandler(), "/wst");
registry.addHandler(costFileUploadWebSocketHandler, "/wsb/softlayer-cost-file");
}
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic/", "/queue/");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/greeting").setAllowedOrigins("*");
// .withSockJS();
}
}
CostFileUploadWebSocketHandler.java
#Component
public class CostFileUploadWebSocketHandler extends BinaryWebSocketHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private SoftLayerJobService softLayerJobService;
private SoftLayerService softLayerService;
private AuthenticationFacade authenticationFacade;
#Autowired
CostFileUploadWebSocketHandler(SoftLayerJobService softLayerJobService, SoftLayerService softLayerService,
AuthenticationFacade authenticationFacade) {
this.softLayerJobService = softLayerJobService;
this.softLayerService = softLayerService;
this.authenticationFacade = authenticationFacade;
}
Map<WebSocketSession, FileUploadInFlight> sessionToFileMap = new WeakHashMap<>();
#Override
public boolean supportsPartialMessages() {
return true;
}
class WebSocketProgressReporter implements ProgressReporter {
private WebSocketSession session;
public WebSocketProgressReporter(WebSocketSession session) {
this.session = session;
}
#Override
public void reportCurrentProgress(BatchStatus currentBatchStatus, long currentPercentage) {
try {
session.sendMessage(new TextMessage("BatchStatus "+currentBatchStatus));
session.sendMessage(new TextMessage("Percentage Complete "+currentPercentage));
} catch(IOException e) {
throw new RuntimeException(e);
}
}
}
#Override
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
ByteBuffer payload = message.getPayload();
FileUploadInFlight inflightUpload = sessionToFileMap.get(session);
if (inflightUpload == null) {
throw new IllegalStateException("This is not expected");
}
inflightUpload.append(payload);
if (message.isLast()) {
File fileNameSaved = save(inflightUpload.name, "websocket", inflightUpload.bos.toByteArray());
BatchStatus currentBatchStatus = BatchStatus.UNKNOWN;
long percentageComplete;
ProgressReporter progressReporter = new WebSocketProgressReporter(session);
SoftLayerCostFileJobExecutionThread softLayerCostFileJobExecutionThread =
new SoftLayerCostFileJobExecutionThread(softLayerService, softLayerJobService, fileNameSaved,progressReporter);
logger.info("In main thread about to begin separate thread");
ForkJoinPool.commonPool().submit(softLayerCostFileJobExecutionThread);
while(!softLayerCostFileJobExecutionThread.jobDone());
// softLayerCostFileJobExecutionThread.run();
// Wait for above to complete somehow
// StepExecution foundStepExecution = jobExplorer.getJobExecution(
// jobExecutionThread.getJobExecutionResult().getJobExecution().getId()
// ).getStepExecutions().stream().filter(stepExecution->stepExecution.getStepName().equals("softlayerUploadFile")).findFirst().orElseGet(null);
// if (!"COMPLETED".equals(jobExecutionResult.getExitStatus())) {
// throw new UploadFileException(file.getOriginalFilename() + " exit status: " + jobExecutionResult.getExitStatus());
// }
logger.info("In main thread after separate thread submitted");
session.sendMessage(new TextMessage("UPLOAD "+inflightUpload.name));
session.close();
sessionToFileMap.remove(session);
logger.info("Uploaded "+inflightUpload.name);
}
String response = "Upload Chunk: size "+ payload.array().length;
logger.debug(response);
}
private File save(String fileName, String prefix, byte[] data) throws IOException {
Path basePath = Paths.get(".", "uploads", prefix, UUID.randomUUID().toString());
logger.info("Saving incoming cost file "+fileName+" to "+basePath);
Files.createDirectories(basePath);
FileChannel channel = new FileOutputStream(Paths.get(basePath.toString(), fileName).toFile(), false).getChannel();
channel.write(ByteBuffer.wrap(data));
channel.close();
return new File(basePath.getFileName().toString());
}
#Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
sessionToFileMap.put(session, new FileUploadInFlight(session));
}
static class FileUploadInFlight {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
String name;
String uniqueUploadId;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
/**
* Fragile constructor - beware not prod ready
* #param session
*/
FileUploadInFlight(WebSocketSession session) {
String query = session.getUri().getQuery();
String uploadSessionIdBase64 = query.split("=")[1];
String uploadSessionId = new String(Base64Utils.decodeUrlSafe(uploadSessionIdBase64.getBytes()));
List<String> sessionIdentifiers = Splitter.on("\\").splitToList(uploadSessionId);
String uniqueUploadId = session.getRemoteAddress().toString()+sessionIdentifiers.get(0);
String fileName = sessionIdentifiers.get(1);
this.name = fileName;
this.uniqueUploadId = uniqueUploadId;
logger.info("Preparing upload for "+this.name+" uploadSessionId "+uploadSessionId);
}
public void append(ByteBuffer byteBuffer) throws IOException{
bos.write(byteBuffer.array());
}
}
}
Below is a snippet of Angular code where I make the call to the websocket. The service is intended to receive a file, then provide regular updates of percentage complete until the service is completed. Does this call need to be in a loop, or does the socket run until it's closed?
Angular Snippet of call to WebSocket:
this.softlayerService.uploadBlueReportFile(this.blueReportFile)
.subscribe(data => {
this.showLoaderBlueReport = false;
this.successBlueReport = true;
this.blueReportFileName = "No file selected";
this.responseBlueReport = 'File '.concat(data.fileName).concat(' ').concat('is ').concat(data.exitStatus);
this.blueReportSelected = false;
this.getCurrentUserFiles();
},
(error)=>{
if(error.status === 504){
this.showLoaderBlueReport = false;
this.stillProcessing = true;
}else{
this.showLoaderBlueReport = false;
this.displayUploadBlueReportsError(error, 'File upload failed');
}
});
}

Spring Boot Hystrix fallback method not being invoked after timeout limit

I want a fallback method to be invoked after a service takes more than 350 milliseconds to complete. Even after 10,000 milliseconds, the method is still not invoked.
I'm running a JUnit test that uses Mockito.
UnitTesting.java
...
#Test
public void test1() {
when(service.getId()).thenAnswer(new Answer<Integer>() {
public Integer answer(InvocationOnMock invocation){
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return -1;
}});
LateralService latService= new LateralService(service);
ArrayList<IdService> returnedIdServices = latService.getIdServices(); //getId() is invoked in this method
...
}
LateralService.java
...
#HystrixCommand(groupKey = "fallback",
commandKey = "fallback",
fallbackMethod = "idServiceFallBack",
commandProperties = {
#HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "350")})
public ArrayList<IdService> getIdServices() {
int id = service.getId();
...
}
public ArrayList<IdService> idServiceFallBack(){
return new ArrayList<IdService>() { add(new IdService(1)) }
}

Finding WebSocket Server memory leak

Hello guys i am currently testing a websocket server written in Asp .Net Core 2.0 and i believe i have a memory leak.I can not find it even though i have tried to dispose everything that might be a concern.The tests were done consecutively and the value of the ram is taken when it reaches stability.(this varies from 5 sec to 20 sec).
The occupied Ram is measured with plain Task Manager Monitor.The testing tool: Thor : https://github.com/observing/thor
The command : thor --amount [amount] ws://[HostIP (localhost)]:[portnumber]
Results:
Connections | RAM Consumed at the end of test (GB):
0 4.54
50 4.55
100 4.55
150 4.61
200 4.68
300 4.76
400 4.59
400 4.59
500 4.62
500 4.65
550 4.65
The WebSocket Server:
SocketMiddleware -used by the appbuilder:
public class SocketMiddleware
{
public byte[] ToSegment(string message) => System.Text.Encoding.UTF8.GetBytes(message);
ClientTracker clientTracker; //the socket clients tracker this is the object we're speaking of
RequestDelegate next;
public SocketMiddleware(ClientTracker tracker,RequestDelegate del)
{
this.clientTracker=tracker;
this.next=del;
}
public async Task Invoke(HttpContext context)
{
if(!context.WebSockets.IsWebSocketRequest)
{
await this.next.Invoke(context);
return;
}
await this.clientTracker.AddClient(context.WebSockets);
}
}
SocketTracker- this is the suspect which is dealing with all the opened sockets
public class ClientTracker
{
ConcurrentDictionary<string, Client> clientMap = new ConcurrentDictionary<string, Client>();
public string CreateConnectionID() => Guid.NewGuid().ToString();
public string GetIDOfSocket(WebSocket socket) => this.clientMap.First(x => x.Value.webSocket.Equals(socket)).Key;
public Client GetClientByID(string id)
{
this.clientMap.TryGetValue(id, out Client client);
return client;
}
public async Task AddClient(WebSocketManager manager)
{
using (WebSocket socket = await manager.AcceptWebSocketAsync())
{
Client newClient = Client.CreateClient(socket, CreateConnectionID());
if(clientMap.TryAdd(newClient.clientID, newClient))
{
await ReceiveMessage(newClient);
}
}
}
public async Task ReceiveMessage(Client client)
{
while (client.webSocket.State == WebSocketState.Open)
{
WebSocketReceiveResult result = await client.ReceiveResult();
//dosomething with result...
if (result.MessageType == WebSocketMessageType.Close)
{
await client.webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Client closed", CancellationToken.None);
break;
}
//send custom message
await client.SendMessage("lala");
}
}
}
Client- socketWrapper which performs all required operations on the socket and stores temporary data to be used by the tracker
public class Client
{
//Fields
public readonly WebSocket webSocket;
public readonly string clientID;
public StringBuilder tempData;
//Auxiliary
private const int BufferSize = 1024 * 4;
public static Client CreateClient(WebSocket socket, string id)
{
Client client = new Client(socket, id);
return client;
}
public Client(WebSocket socket, string id)
{
this.webSocket = socket;
this.clientID = id;
tempData = new StringBuilder();
}
public async Task<WebSocketReceiveResult> ReceiveResult()
{
tempData.Clear();
ArraySegment<byte> segment = new ArraySegment<byte>(new byte[BufferSize]);
WebSocketReceiveResult result = await this.webSocket.ReceiveAsync(segment, CancellationToken.None);
tempData.Append(BitConverter.ToString(segment.Array));
return result;
}
public async Task SendMessage(string message)
{
byte[] bytes = Encoding.UTF8.GetBytes(message);
await this.webSocket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, CancellationToken.None);
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<ClientTracker>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseWebSockets();
app.UseMiddleware<SocketMiddleware>();
}
P.S:
The server was not closed between requests.Could it be the concurrent dictionary ?Besides it, the reference of the Client is cleaned ,the Client is disposed,and the socket is closed.The string builder can not be disposed,and the Tracker/Middleware live as long as the app lives.

Resources