I am trying to communicate with an external TCP server using TcpOutboundGateway and a client TcpConnectionFactory. In my scenario, each connection should be associated with different thread (each connection on the thread might be used for more then one request/response).
So I used a ThreadAffinityClientConnectionFactory from this topic: Spring Integration tcp client multiple connections
It worked fine until I tried to open more than 4 concurrent connections, the fifth (and over) connection is failing on timeout.
I figured out that org.springframework.integration.ip.tcp.TcpOutboundGateway uses semaphore in handleRequestMessage method to acquire a connection, so I overridden TcpOuboundGateway like this:
public class NoSemaphoreTcpOutboundGateway extends TcpOutboundGateway {
private volatile AbstractClientConnectionFactory connectionFactory;
private final Map<String, NoSemaphoreTcpOutboundGateway.AsyncReply> pendingReplies = new ConcurrentHashMap();
#Override
public boolean onMessage(Message<?> message) {
String connectionId = (String)message.getHeaders().get("ip_connectionId");
if(connectionId == null) {
this.logger.error("Cannot correlate response - no connection id");
this.publishNoConnectionEvent(message, (String)null, "Cannot correlate response - no connection id");
return false;
}
if(this.logger.isTraceEnabled()) {
this.logger.trace("onMessage: " + connectionId + "(" + message + ")");
}
NoSemaphoreTcpOutboundGateway.AsyncReply reply = (NoSemaphoreTcpOutboundGateway.AsyncReply)this.pendingReplies.get(connectionId);
if(reply == null) {
if(message instanceof ErrorMessage) {
return false;
} else {
String errorMessage = "Cannot correlate response - no pending reply for " + connectionId;
this.logger.error(errorMessage);
this.publishNoConnectionEvent(message, connectionId, errorMessage);
return false;
}
} else {
reply.setReply(message);
return false;
}
}
#Override
protected Message handleRequestMessage(Message<?> requestMessage) {
connectionFactory = (AbstractClientConnectionFactory) this.getConnectionFactory();
Assert.notNull(this.getConnectionFactory(), this.getClass().getName() + " requires a client connection factory");
TcpConnection connection = null;
String connectionId = null;
Message var7;
try {
/*if(!this.isSingleUse()) {
this.logger.debug("trying semaphore");
if(!this.semaphore.tryAcquire(this.requestTimeout, TimeUnit.MILLISECONDS)) {
throw new MessageTimeoutException(requestMessage, "Timed out waiting for connection");
}
haveSemaphore = true;
if(this.logger.isDebugEnabled()) {
this.logger.debug("got semaphore");
}
}*/
connection = this.getConnectionFactory().getConnection();
NoSemaphoreTcpOutboundGateway.AsyncReply e = new NoSemaphoreTcpOutboundGateway.AsyncReply(10000);
connectionId = connection.getConnectionId();
this.pendingReplies.put(connectionId, e);
if(this.logger.isDebugEnabled()) {
this.logger.debug("Added pending reply " + connectionId);
}
connection.send(requestMessage);
//connection may be closed after send (in interceptor) if its disconnect message
if (!connection.isOpen())
return null;
Message replyMessage = e.getReply();
if(replyMessage == null) {
if(this.logger.isDebugEnabled()) {
this.logger.debug("Remote Timeout on " + connectionId);
}
this.connectionFactory.forceClose(connection);
throw new MessageTimeoutException(requestMessage, "Timed out waiting for response");
}
if(this.logger.isDebugEnabled()) {
this.logger.debug("Response " + replyMessage);
}
var7 = replyMessage;
} catch (Exception var11) {
this.logger.error("Tcp Gateway exception", var11);
if(var11 instanceof MessagingException) {
throw (MessagingException)var11;
}
throw new MessagingException("Failed to send or receive", var11);
} finally {
if(connectionId != null) {
this.pendingReplies.remove(connectionId);
if(this.logger.isDebugEnabled()) {
this.logger.debug("Removed pending reply " + connectionId);
}
}
}
return var7;
}
private void publishNoConnectionEvent(Message<?> message, String connectionId, String errorMessage) {
ApplicationEventPublisher applicationEventPublisher = this.connectionFactory.getApplicationEventPublisher();
if(applicationEventPublisher != null) {
applicationEventPublisher.publishEvent(new TcpConnectionFailedCorrelationEvent(this, connectionId, new MessagingException(message, errorMessage)));
}
}
private final class AsyncReply {
private final CountDownLatch latch;
private final CountDownLatch secondChanceLatch;
private final long remoteTimeout;
private volatile Message<?> reply;
private AsyncReply(long remoteTimeout) {
this.latch = new CountDownLatch(1);
this.secondChanceLatch = new CountDownLatch(1);
this.remoteTimeout = remoteTimeout;
}
public Message<?> getReply() throws Exception {
try {
if(!this.latch.await(this.remoteTimeout, TimeUnit.MILLISECONDS)) {
return null;
}
} catch (InterruptedException var2) {
Thread.currentThread().interrupt();
}
for(boolean waitForMessageAfterError = true; this.reply instanceof ErrorMessage; waitForMessageAfterError = false) {
if(!waitForMessageAfterError) {
if(this.reply.getPayload() instanceof MessagingException) {
throw (MessagingException)this.reply.getPayload();
}
throw new MessagingException("Exception while awaiting reply", (Throwable)this.reply.getPayload());
}
NoSemaphoreTcpOutboundGateway.this.logger.debug("second chance");
this.secondChanceLatch.await(2L, TimeUnit.SECONDS);
}
return this.reply;
}
public void setReply(Message<?> reply) {
if(this.reply == null) {
this.reply = reply;
this.latch.countDown();
} else if(this.reply instanceof ErrorMessage) {
this.reply = reply;
this.secondChanceLatch.countDown();
}
}
}
}
the configurations of SpringContext looks like this:
#Configuration
#ImportResource("gateway.xml")
public class Conf {
#Bean
#Autowired
#ServiceActivator(inputChannel = "clientOutChannel")
public NoSemaphoreTcpOutboundGateway noSemaphoreTcpOutboundGateway(ThreadAffinityClientConnectionFactory cf, DirectChannel clientInChannel){
NoSemaphoreTcpOutboundGateway gw = new NoSemaphoreTcpOutboundGateway();
gw.setConnectionFactory(cf);
gw.setReplyChannel(clientInChannel);
gw.setRequestTimeout(10000);
return gw;
}
<int-ip:tcp-connection-factory
id="delegateCF"
type="client"
host="${remoteService.host}"
port="${remoteService.port}"
single-use="true"
lookup-host="false"
ssl-context-support="sslContext"
deserializer="clientDeserializer"
serializer="clientSerializer"
interceptor-factory-chain="clientLoggingTcpConnectionInterceptorFactory"
using-nio="false"/>
The delegateCF is passed to ThreadAffinityClientConnectionFactory constructor
So, the question is:
Is it OK to use NoSemaphoreTcpOutboundGateway in conjunction with ThreadAffinityClientConnectionFactory in terms of concurrency?
Looks like you go right way, but at the same time I think you don't need custom TcpOutboundGateway. The semaphore logic is based on the:
if (!this.isSingleUse) {
logger.debug("trying semaphore");
if (!this.semaphore.tryAcquire(this.requestTimeout, TimeUnit.MILLISECONDS)) {
throw new MessageTimeoutException(requestMessage, "Timed out waiting for connection");
}
at the same time look at Gary's solution for the ThreadAffinityClientConnectionFactory:
#Bean
public TcpNetClientConnectionFactory delegateCF() {
TcpNetClientConnectionFactory clientCF = new TcpNetClientConnectionFactory("localhost", 1234);
clientCF.setSingleUse(true); // so each thread gets his own connection
return clientCF;
}
#Bean
public ThreadAffinityClientConnectionFactory affinityCF() {
return new ThreadAffinityClientConnectionFactory(delegateCF());
}
Pay attention to the comment. Only you need is delegate isSingleUse().
Related
I am trying to figure out NIO in Java doing some simple client-server project.
The case is I have to concurrent clients in cached thread pool executor, who are communicating with single-threaded server using non-blocking NIO channels.
The problem is that last client cannot receive last server's sent message. It locks in infinite loop waiting for upcoming data.
ClientTask class:
public class ClientTask extends FutureTask<String> {
private Client client;
private List<String> reqList; // requests list (without last and first)
private boolean showRes; // print request results
public ClientTask(Client client, List<String> reqList, boolean showRes) {
super(() -> ClientTask.getLogWhenArrives(client, reqList, showRes));
this.client = client;
this.reqList = reqList;
this.showRes = showRes;
}
public static ClientTask create(Client c, List<String> reqList, boolean showRes) {
return new ClientTask(c, reqList, showRes);
}
private static String getLogWhenArrives(Client client, List<String> reqList, boolean showRes) {
client.connect();
String response = client.send("login " + client.getId());
if (showRes) System.out.println(response);
for (String req : reqList) {
response = client.send(req);
if (showRes) System.out.println(response);
}
String responseLog = client.send("bye and log transfer");
client.close();
return responseLog;
}
}
Client send():
public String send(String req) {
ByteBuffer reqBuffer = ByteBuffer.wrap((req + END).getBytes());
try {
channel.write(reqBuffer);
} catch (IOException e) {
e.printStackTrace();
}
return receive();
}
Client receive()
public String receive() {
StringBuilder result = new StringBuilder();
try {
inBuff.clear();
readLoop:
while (true) { // THIS LOOP WON'T END
int n = channel.read(inBuff);
if (n == -1) {
break;
}
if (n > 0) {
inBuff.flip();
CharBuffer cb = charset.decode(inBuff);
while (cb.hasRemaining()) {
char c = cb.get();
if (c == END.charAt(0)) {
break readLoop;
}
result.append(c);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return result.toString();
}
Main:
public class Main {
public static void main(String[] args) throws Exception {
String fileName = System.getProperty("user.home") + "/PassTimeServerOptions.yaml";
Options opts = Tools.createOptionsFromYaml(fileName);
String host = opts.getHost();
int port = opts.getPort();
boolean concur = opts.isConcurMode();
boolean showRes = opts.isShowSendRes();
Map<String, List<String>> clRequests = opts.getClientsMap();
ExecutorService es = Executors.newCachedThreadPool();
List<ClientTask> ctasks = new ArrayList<>();
List<String> clogs = new ArrayList<>();
Server s = new Server(host, port);
s.startServer();
// start clients
clRequests.forEach( (id, reqList) -> {
Client c = new Client(host, port, id);
if (concur) {
ClientTask ctask = ClientTask.create(c, reqList, showRes);
ctasks.add(ctask);
es.execute(ctask);
}
});
if (concur) {
ctasks.forEach( task -> {
try {
String log = task.get();
clogs.add(log);
} catch (InterruptedException | ExecutionException exc) {
System.out.println(exc);
}
});
clogs.forEach( System.out::println);
es.shutdown();
}
s.stopServer();
System.out.println("\n=== Server log ===");
System.out.println(s.getServerLog());
}
}
Server is sending all the info and channels are open and connected.
I'm taking my first steps with Java Sockets and Threads.
I want to try make synchonized connection with serwer where multiple threades adds their request to the queue and signle thread send all this request. In the meantime other threads wait for there resoults.
How it's work:
Client ask servert to log in by caling method
User user = logIn("sdasd");
public User logIn(String name){
System.out.println("!Log in");
//Function create request
RequestHandler<User> request = new RequestHandler<>("logIn", name, out, results);
//Request is added to queue
requestQueue.addLast(request);
RequestStatus status;
//Thread who call this function wait for request to be handle (changed status)
while (true){
status = request.getStatus();
System.out.println(status);
if (status == RequestStatus.SUCCESSFUL) {
System.out.println("Try to get result: ");
User user = request.result();
System.out.println(user.getName());
return request.result();
}
if(status == RequestStatus.FAILED) {
return null;
}
}
}
In the meantime other thread send request to server:
new Thread(new Runnable() {
#Override
public void run() {
while(isConnected()){
//is request to be handle?
if(requestQueue.size() != 0){
//remove request form queue
RequestHandler request = (RequestHandler) requestQueue.removeFirst();
//change request status
request.setStatus(RequestStatus.IN_PROGRESS);
System.out.println("!Request: ");
System.out.println(request.getStatus());
//process request
request.request();
//change request status to finished
request.setStatus(RequestStatus.SUCCESSFUL);
System.out.print("!Request end: ");
System.out.println(request.getStatus());
}
}
Iterator<Request> iterator = requestQueue.iterator();
for (Iterator<Request> it = iterator; it.hasNext(); ) {
Request request = it.next();
request.setStatus(RequestStatus.FAILED);
}
}
}).start();
Inside class RequestHandler is process this code:
#Override
public void request() {
try {
//send commend
out.writeObject(requestCommend);
//wait for result (other thread handle this functionality)
while(!results.containsKey(commend)){}
//attach result
result = (T) results.remove(commend);
} catch (IOException e) {
e.printStackTrace();
}
}
When the request is send to the server other thread wait for response for the server and add result to the HashMap:
new Thread(new Runnable() {
#Override
public void run() {
while(isConnected()) {
try {
String commend = (String) in.readObject();
if(commend.charAt(0) == '#') { // # mean its result of request
Object object = in.readObject();
//This is debug case to see everything work properly
if(object == null){
System.out.println("!I am null");
}else{
System.out.println("I am user: " +((User) object).getName());
}
System.out.println(commend);
results.put(commend, object);
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}).start();
This is it. Required code for this question:
public class ServerConnection extends Socket{
public static final int PORT = 8888;
private ObjectInputStream in;
private ObjectOutputStream out;
private LinkedList<Request> requestQueue;
private ConcurrentHashMap<String, Object> results;
public ServerConnection() throws IOException{
super("localhost", PORT);
System.out.println("Connected to the server.");
in = new ObjectInputStream(getInputStream());
out = new ObjectOutputStream(getOutputStream());
requestQueue = new LinkedList<>();
results = new ConcurrentHashMap<>();
new Thread(new Runnable() {
#Override
public void run() {
while(isConnected()){
if(requestQueue.size() != 0){
RequestHandler request = (RequestHandler) requestQueue.removeFirst();
request.setStatus(RequestStatus.IN_PROGRESS);
System.out.println("!Request: ");
System.out.println(request.getStatus());
request.request();
request.setStatus(RequestStatus.SUCCESSFUL);
System.out.print("!Request end: ");
System.out.println(request.getStatus());
}
}
Iterator<Request> iterator = requestQueue.iterator();
for (Iterator<Request> it = iterator; it.hasNext(); ) {
Request request = it.next();
request.setStatus(RequestStatus.FAILED);
}
}
}).start();
new Thread(new Runnable() {
#Override
public void run() {
while(isConnected()) {
try {
String commend = (String) in.readObject();
if(commend.charAt(0) == '#') { // # mean its result of request
Object object = in.readObject();
//This is debug case
if(object == null){
System.out.println("!I am null");
}else{
System.out.println("I am user: " +((User) object).getName());
}
System.out.println(commend);
results.put(commend, object);
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}).start();
}
public User logIn(String name){
System.out.println("!Log in");
RequestHandler<User> request = new RequestHandler<>("logIn", name, out, results);
requestQueue.addLast(request);
RequestStatus status;
while (true){
status = request.getStatus();
System.out.println(status);
if (status == RequestStatus.SUCCESSFUL) {
System.out.println("Try to get result: ");
User user = request.result();
System.out.println(user.getName());
return request.result();
}
if(status == RequestStatus.FAILED) {
return null;
}
}
}
public ArrayList<Room> getListOfRooms(){
Request<ArrayList<Room>> request = new RequestHandler<>("listOfRooms", out, results);
requestQueue.addLast(request);
while (true){
RequestStatus status = request.getStatus();
if (status == RequestStatus.SUCCESSFUL)
return request.result();
if(status == RequestStatus.FAILED) {
return null;
}
}
}
}
RequestHandler looks like this:
public class RequestHandler<T> implements Request<T>{
private T result;
private RequestStatus status = RequestStatus.NEW;
private ObjectOutputStream out;
private String commend;
private String requestCommend;
private ConcurrentHashMap<String, Object> results;
public RequestHandler(String commend, String parameters, ObjectOutputStream out, ConcurrentHashMap<String, Object> results) {
this.commend = "#" + commend;
this.requestCommend = "?" + commend + ":" + parameters;
this.out = out;
this.results = results;
}
public RequestHandler(String commend, ObjectOutputStream out, ConcurrentHashMap<String, Object> results) {
this.commend = "#" + commend;
this.requestCommend = "?" + commend;
this.out = out;
this.results = results;
}
#Override
public void request() {
try {
out.writeObject(requestCommend);
while(!results.containsKey(commend)){}
result = (T) results.remove(commend);
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public T result() {
return result;
}
#Override
public RequestStatus getStatus() {
return status;
}
#Override
public void setStatus(RequestStatus status) {
this.status = status;
}
}
The output looks like this WHEN ITS WORK:
Connected to the server.
!Log in
NEW
IN_PROGRESS
...
IN_PROGRESS
!Request: IN_PROGRESS
IN_PROGRESS
...
IN_PROGRESS
I am user: sdsad
#logIn
IN_PROGRESS
IN_PROGRESS
SUCCESSFUL
!Request end: SUCCESSFUL
Try to get result:
sdsad
But when I COMMENT one debug msg I got this:
Connected to the server.
!Log in
!Request: IN_PROGRESS
I am user: dfdsfsdf4324
#logIn
!Request end: SUCCESSFUL
And the loop while(true) never end becouse I got always status IN_PROGRESS.
That's why I want to ask you why it's happen? Is Java have some weird way to optimalize output of functions to make is faster and is it thinking if it was reapet milion times so it has to be this state always?
I am new in netty I have a tcp client application developed with netty. When i use future get async response from server some response returning but future is not completing into timeout. TCPClient class like following;
public TcpClient {
public boolean connect(Host host) {
try {
Bootstrap clientBootstrap = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE,true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 50)
.remoteAddress(new InetSocketAddress(host.getIp(), host.getPort()))
.handler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) {
socketChannel.config().setRecvByteBufAllocator(new FixedRecvByteBufAllocator(2146));
FalconClientHandler falconClientHandler = new FalconClientHandler(host);
host.setFalconClientHandler(falconClientHandler);
socketChannel.pipeline().addLast(falconClientHandler);
}
});
channelFuture = clientBootstrap.connect().sync(); //BAŞARI İLE BAĞLANDI
channelFuture.channel().closeFuture().sync();
return host.isActive();
} catch (Exception e) {
log.info("Connection timed out --> " + e);
host.setActive(false);
return false;
} finally {
host.setActive(false);
}
}
public synchronized ResponseFuture send(long transactionId,String message) {
final Map<Long,ResponseFuture> responseFuture = new ConcurrentHashMap<>();
responseFuture.put(transactionId,new ResponseFuture());
if (!hostSelector.getUpHostList().isEmpty()) {
int hostCount = hostSelector.getUpHostList().size();
Host host;
host = hostSelector.getUpHostList().get(index.incrementAndGet() % hostCount);
if (host.isActive()) {
int headerLength = Integer.parseInt(message.substring(8, 12));
log.info("[{}] Host {} Tcp Request",message.substring(52, 52 + headerLength),host.getIp());
channelFuture.addListener((GenericFutureListener<ChannelFuture>) future -> {
log.info("[{}] Tcp request added to map",transactionId);
channelFuture.channel().pipeline().get(FalconClientHandler.class).setResponseFuture(responseFuture);
byte[] byteBuffer = message.getBytes();
channelFuture.channel().writeAndFlush(Unpooled.copiedBuffer(byteBuffer));
});
}
} else {
log.error("AYAKTA HOST YOK");
}
return responseFuture.get(transactionId);
}
}
Send method have transactionId and request message, When i send this message with transaction id response will return with this thransaction id. I am calling this send like following;
ResponseFuture responseFuture = falconClient.send(Long.valueOf(transactionId), finalMessage);
try {
Object obj = responseFuture.get(ddaTimeoutParam, TimeUnit.MILLISECONDS);
if(obj!=null) {
response = obj.toString();
ddaDelta = System.currentTimeMillis()-ddaRequestStartTime;
}
} catch (InterruptedException | ExecutionException | TimeoutException e) {
log.warn("[{}] DDA timeout. Timeout parameter: {}",transactionId,ddaTimeoutParam);
responseFuture.cancel(true);
response = "TIMEOUT";
ddaDelta = System.currentTimeMillis()-ddaRequestStartTime;
}
Response future is a basic Future implementation class. Put and get methods like that;
public class ResponseFuture implements Future<String> {
private volatile State state = State.WAITING;
ArrayBlockingQueue<String> blockingResponse = new ArrayBlockingQueue<String>(1);
private enum State {
WAITING,
DONE
}
#Override
public String get(long timeout, TimeUnit unit) throws InterruptedException,
ExecutionException, TimeoutException {
final String responseAfterWait = blockingResponse.poll(timeout, unit);
if (responseAfterWait == null) {
throw new TimeoutException();
}
return responseAfterWait;
}
public void set(String msg) {
if (state == State.DONE) {
return;
}
try {
blockingResponse.put(msg);
state = State.DONE;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
My Handler class for receive server response message like following;
public class FalconClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
private ChannelHandlerContext ctx;
private Map<Long,ResponseFuture> responseFuture;
public synchronized void setResponseFuture(Map<Long,ResponseFuture> responseFuture) {
log.info("{} ResponseFuture setted",responseFuture.keySet());
this.responseFuture = responseFuture;
}
#Override
public void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf in) {
String input = in.toString(CharsetUtil.UTF_8);
String transactionKey = input.substring(52, 66).trim();
if(responseFuture.get(Long.valueOf(transactionKey))!=null)
responseFuture.get(Long.valueOf(transactionKey)).set(input);
else
log.info("[{}] Tcp Response map is empty",transactionKey);
}
}
When i run this code under high load like 30 transaction per second, tcp response returned from netty server but future get method received timeout.This situation not occuring every request for example %20 request is fail when 30 tps %50 request fail in 40 tps. What can be occur under load?
I have an JavaFx application that has no stage. It only runs at system tray. Basically it listen to a service and show notification according to it.
The connection between app and service is done using Socket.
However, service can send a priority message, which will be shown first than others.
The problem: I have all my messages in a PriorityQueue but I don't know how to handle a notification await for the other finish to show. Is that the best approach? Is the architecture correct? Also, since TrayNotification class will show a Scene, I'm afraid of having problems with UI Thread.
This is Message class:
public class Message implements Comparable<Message> {
private int priority;
private String notificationType;
private String title;
private String message;
public Message() {
}
public Message (int priority, String notificationType, String title, String message) {
this.priority = priority;
this.notificationType = notificationType;
this.title = title;
this.message = message;
}
public void setPriority(int priority) {
this.priority = priority;
}
public int getPriority() {
return this.priority;
}
public void setNotificationType(String notificationType) {
this.notificationType = notificationType;
}
public NotificationType getNotificationType() {
if (this.notificationType.equals(NotificationType.CUSTOM.toString())) {
return NotificationType.CUSTOM;
}
else if (this.notificationType.equals(NotificationType.ERROR.toString())) {
return NotificationType.ERROR;
}
else if (this.notificationType.equals(NotificationType.INFORMATION.toString())) {
return NotificationType.INFORMATION;
}
else if (this.notificationType.equals(NotificationType.NOTICE.toString())) {
return NotificationType.NOTICE;
}
else if (this.notificationType.equals(NotificationType.SUCCESS.toString())) {
return NotificationType.SUCCESS;
}
else if (this.notificationType.equals(NotificationType.WARNING.toString())) {
return NotificationType.WARNING;
}
else {
throw new IllegalArgumentException("Invalid notification type.");
}
}
public void setTitle(String title) {
this.title = title;
}
public String getTitle() {
return this.title;
}
public void setMessage(String message) {
this.message = message;
}
public String getMessage() {
return this.message;
}
#Override
public int compareTo(Message otherMessage) {
return Integer.compare(this.priority, otherMessage.getPriority());
}
}
My application class, SystemtrayLauncher, has this code on start method, after configuring tray:
/** Start to listen to service **/
ServiceConnector connector = new ServiceConnector(8888);
new Thread(connector).start();
ServiceConnector (which I think needs to be improved to handle PriorityQueue):
public class ServiceConnector extends Task<Void> {
private ServerSocket socket;
private int port;
public static PriorityQueue<Message> messageQueue = new PriorityQueue<>();
public ServiceConnector(int port) {
this.port = port;
}
public void connect() {
try {
System.out.println("Opening connection...");
socket = new ServerSocket(this.port);
socket.setSoTimeout(0);
System.out.println("Connection opened at port " + this.port);
while (true) {
System.out.println("Awaiting service connection...");
Socket service = socket.accept();
System.out.println(
"Service at " + service.getInetAddress().getHostAddress() + " connected");
Message message = MessageListener.getMessage(service);
if (message != null) {
messageQueue.offer(message);
// get top priority message
Platform.runLater(() -> MessageListener.notifyUser(messageQueue.peek()));
}
else {
CustomAlert dialog = new CustomAlert(Alert.AlertType.ERROR);
dialog.setContentText(SystemConfiguration.LOCALE.getString("MESSAGE_ERROR"));
dialog.showAndWait();
}
service.close();
}
} catch (IOException exc) {
exc.printStackTrace();
}
}
#Override
protected Void call() throws Exception {
this.connect();
return null;
}
}
MessageListener
public class MessageListener {
private static TrayNotification trayNotification;
public static Message getMessage(Socket service) {
System.out.println("Processing message...");
try {
BufferedReader inputReader =
new BufferedReader(new InputStreamReader(service.getInputStream()));
/**
* JSON format:
* {
* "priority": "1 for urgent and greater with less priority",
* "notificationType": "ERROR|INFORMATION|NOTICE|SUCCESS|WARNING",
* "title": "A string to be show as notification windows title",
* "message": "A string to be show as message"
* }
*/
JSONObject jsonMessage = new JSONObject(inputReader.readLine());
Message message = new Message();
message.setPriority(jsonMessage.getInt("priority"));
message.setNotificationType(jsonMessage.getString("notificationType"));
message.setTitle(jsonMessage.getString("title"));
message.setMessage(jsonMessage.getString("message"));
inputReader.close();
service.close();
System.out.println("Message with priority " + message.getPriority() + " processed.");
return message;
} catch (IOException exc) {
exc.printStackTrace();
return null;
}
}
/**
* Notify user with processed service message.
* #param message
*
*/
public static void notifyUser(Message message) {
System.out.println("Priority: " + message.getPriority());
trayNotification = new TrayNotification();
trayNotification.setAnimationType(AnimationType.POPUP);
trayNotification.setRectangleFill(Paint.valueOf("#0277BD"));
trayNotification.setImage(new Image(SystemConfiguration.ICON));
trayNotification.setNotificationType(message.getNotificationType());
trayNotification.setTitle(message.getTitle());
trayNotification.setMessage(message.getMessage());
trayNotification.showAndDismiss(Duration.seconds(3.5));
ServiceConnector.messageQueue.poll();
}
}
Problem statement
I have a JMS listener running as a thread listening to a topic. As soon a message comes in, I spawn a new Thread to process the in-bounded message. So for each incoming message I spawn a new Thread.
I have a scenario where duplicate message is also being processed when it is injected immediately in a sequential order. I need to prevent this from being processed. I tried using a ConcurrentHashMap to hold the process times where I add in the entry as soon as Thread is spawn and remove it from the map as soon Thread completes its execution. But it did not help when I tried with the scenario where I passed in same one after the another in concurrent fashion.
General Outline of my issue before you plunge into the actual code base
onMessage(){
processIncomingMessage(){
ExecutorService executorService = Executors.newFixedThreadPool(1000);
//Map is used to make an entry before i spawn a new thread to process incoming message
//Map contains "Key as the incoming message" and "value as boolean"
//check map for duplicate check
//The below check is failing and allowing duplicate messages to be processed in parallel
if(entryisPresentInMap){
//return doing nothing
}else{
//spawn a new thread for each incoming message
//also ensure a duplicate message being processed when it in process by an active thread
executorService.execute(new Runnable() {
#Override
public void run() {
try {
//actuall business logic
}finally{
//remove entry from the map so after processing is done with the message
}
}
}
}
Standalone example to mimic the scenario
public class DuplicateCheck {
private static Map<String,Boolean> duplicateCheckMap =
new ConcurrentHashMap<String,Boolean>(1000);
private static String name=null;
private static String[] nameArray = new String[20];
public static void processMessage(String message){
System.out.println("Processed message =" +message);
}
public static void main(String args[]){
nameArray[0] = "Peter";
nameArray[1] = "Peter";
nameArray[2] = "Adam";
for(int i=0;i<=nameArray.length;i++){
name=nameArray[i];
if(duplicateCheckMap.get(name)!=null && duplicateCheckMap.get(name)){
System.out.println("Thread detected for processing your name ="+name);
return;
}
addNameIntoMap(name);
new Thread(new Runnable() {
#Override
public void run() {
try {
processMessage(name);
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
freeNameFromMap(name);
}
}
}).start();
}
}
private static synchronized void addNameIntoMap(String name) {
if (name != null) {
duplicateCheckMap.put(name, true);
System.out.println("Thread processing the "+name+" is added to the status map");
}
}
private static synchronized void freeNameFromMap(String name) {
if (name != null) {
duplicateCheckMap.remove(name);
System.out.println("Thread processing the "+name+" is released from the status map");
}
}
Snippet of the code is below
public void processControlMessage(final Message message) {
RDPWorkflowControlMessage rdpWorkflowControlMessage= unmarshallControlMessage(message);
final String workflowName = rdpWorkflowControlMessage.getWorkflowName();
final String controlMessageEvent=rdpWorkflowControlMessage.getControlMessage().value();
if(controlMessageStateMap.get(workflowName)!=null && controlMessageStateMap.get(workflowName)){
log.info("Cache cleanup for the workflow :"+workflowName+" is already in progress");
return;
}else {
log.info("doing nothing");
}
Semaphore controlMessageLock = new Semaphore(1);
try{
controlMessageLock.acquire();
synchronized(this){
new Thread(new Runnable(){
#Override
public void run() {
try {
lock.lock();
log.info("Processing Workflow Control Message for the workflow :"+workflowName);
if (message instanceof TextMessage) {
if ("REFRESH".equalsIgnoreCase(controlMessageEvent)) {
clearControlMessageBuffer();
enableControlMessageStatus(workflowName);
List<String> matchingValues=new ArrayList<String>();
matchingValues.add(workflowName);
ConcreteSetDAO tasksSetDAO=taskEventListener.getConcreteSetDAO();
ConcreteSetDAO workflowSetDAO=workflowEventListener.getConcreteSetDAO();
tasksSetDAO.deleteMatchingRecords(matchingValues);
workflowSetDAO.deleteMatchingRecords(matchingValues);
fetchNewWorkflowItems();
addShutdownHook(workflowName);
}
}
} catch (Exception e) {
log.error("Error extracting item of type RDPWorkflowControlMessage from message "
+ message);
} finally {
disableControlMessageStatus(workflowName);
lock.unlock();
}
}
}).start();
}
} catch (InterruptedException ie) {
log.info("Interrupted Exception during control message lock acquisition"+ie);
}finally{
controlMessageLock.release();
}
}
private void addShutdownHook(final String workflowName) {
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
disableControlMessageStatus(workflowName);
}
});
log.info("Shut Down Hook Attached for the thread processing the workflow :"+workflowName);
}
private RDPWorkflowControlMessage unmarshallControlMessage(Message message) {
RDPWorkflowControlMessage rdpWorkflowControlMessage = null;
try {
TextMessage textMessage = (TextMessage) message;
rdpWorkflowControlMessage = marshaller.unmarshalItem(textMessage.getText(), RDPWorkflowControlMessage.class);
} catch (Exception e) {
log.error("Error extracting item of type RDPWorkflowTask from message "
+ message);
}
return rdpWorkflowControlMessage;
}
private void fetchNewWorkflowItems() {
initSSL();
List<RDPWorkflowTask> allTasks=initAllTasks();
taskEventListener.addRDPWorkflowTasks(allTasks);
workflowEventListener.updateWorkflowStatus(allTasks);
}
private void clearControlMessageBuffer() {
taskEventListener.getRecordsForUpdate().clear();
workflowEventListener.getRecordsForUpdate().clear();
}
private synchronized void enableControlMessageStatus(String workflowName) {
if (workflowName != null) {
controlMessageStateMap.put(workflowName, true);
log.info("Thread processing the "+workflowName+" is added to the status map");
}
}
private synchronized void disableControlMessageStatus(String workflowName) {
if (workflowName != null) {
controlMessageStateMap.remove(workflowName);
log.info("Thread processing the "+workflowName+" is released from the status map");
}
}
I have modified my code to incorporate suggestions provided below but still it is not working
public void processControlMessage(final Message message) {
ExecutorService executorService = Executors.newFixedThreadPool(1000);
try{
lock.lock();
RDPWorkflowControlMessage rdpWorkflowControlMessage= unmarshallControlMessage(message);
final String workflowName = rdpWorkflowControlMessage.getWorkflowName();
final String controlMessageEvent=rdpWorkflowControlMessage.getControlMessage().value();
if(controlMessageStateMap.get(workflowName)!=null && controlMessageStateMap.get(workflowName)){
log.info("Cache cleanup for the workflow :"+workflowName+" is already in progress");
return;
}else {
log.info("doing nothing");
}
enableControlMessageStatus(workflowName);
executorService.execute(new Runnable() {
#Override
public void run() {
try {
//actual code
fetchNewWorkflowItems();
addShutdownHook(workflowName);
}
}
} catch (Exception e) {
log.error("Error extracting item of type RDPWorkflowControlMessage from message "
+ message);
} finally {
disableControlMessageStatus(workflowName);
}
}
});
} finally {
executorService.shutdown();
lock.unlock();
}
}
private void addShutdownHook(final String workflowName) {
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
disableControlMessageStatus(workflowName);
}
});
log.info("Shut Down Hook Attached for the thread processing the workflow :"+workflowName);
}
private synchronized void enableControlMessageStatus(String workflowName) {
if (workflowName != null) {
controlMessageStateMap.put(workflowName, true);
log.info("Thread processing the "+workflowName+" is added to the status map");
}
}
private synchronized void disableControlMessageStatus(String workflowName) {
if (workflowName != null) {
controlMessageStateMap.remove(workflowName);
log.info("Thread processing the "+workflowName+" is released from the status map");
}
}
This is how you should add a value to a map. This double checking makes sure that only one thread adds a value to a map at any particular moment of time and you can control the access afterwards. Remove all the locking logic afterwards. It is as simple as that
public void processControlMessage(final String workflowName) {
if(!tryAddingMessageInProcessingMap(workflowName)){
Thread.sleep(1000); // sleep 1 sec and try again
processControlMessage(workflowName);
return ;
}
System.out.println(workflowName);
try{
// your code goes here
} finally{
controlMessageStateMap.remove(workflowName);
}
}
private boolean tryAddingMessageInProcessingMap(final String workflowName) {
if(controlMessageStateMap .get(workflowName)==null){
synchronized (this) {
if(controlMessageStateMap .get(workflowName)==null){
controlMessageStateMap.put(workflowName, true);
return true;
}
}
}
return false;
}
Read here more for https://en.wikipedia.org/wiki/Double-checked_locking
The issue is fixed now. Many thanks to #awsome for the approach. It is avoiding the duplicates when a thread is already processing the incoming duplicate message. If no thread is processing then it gets picked up
public void processControlMessage(final Message message) {
try {
lock.lock();
RDPWorkflowControlMessage rdpWorkflowControlMessage = unmarshallControlMessage(message);
final String workflowName = rdpWorkflowControlMessage.getWorkflowName();
final String controlMessageEvent = rdpWorkflowControlMessage.getControlMessage().value();
new Thread(new Runnable() {
#Override
public void run() {
try {
if (message instanceof TextMessage) {
if ("REFRESH".equalsIgnoreCase(controlMessageEvent)) {
if (tryAddingWorkflowNameInStatusMap(workflowName)) {
log.info("Processing Workflow Control Message for the workflow :"+ workflowName);
addShutdownHook(workflowName);
clearControlMessageBuffer();
List<String> matchingValues = new ArrayList<String>();
matchingValues.add(workflowName);
ConcreteSetDAO tasksSetDAO = taskEventListener.getConcreteSetDAO();
ConcreteSetDAO workflowSetDAO = workflowEventListener.getConcreteSetDAO();
tasksSetDAO.deleteMatchingRecords(matchingValues);
workflowSetDAO.deleteMatchingRecords(matchingValues);
List<RDPWorkflowTask> allTasks=fetchNewWorkflowItems(workflowName);
updateTasksAndWorkflowSet(allTasks);
removeWorkflowNameFromProcessingMap(workflowName);
} else {
log.info("Cache clean up is already in progress for the workflow ="+ workflowName);
return;
}
}
}
} catch (Exception e) {
log.error("Error extracting item of type RDPWorkflowControlMessage from message "
+ message);
}
}
}).start();
} finally {
lock.unlock();
}
}
private boolean tryAddingWorkflowNameInStatusMap(final String workflowName) {
if(controlMessageStateMap.get(workflowName)==null){
synchronized (this) {
if(controlMessageStateMap.get(workflowName)==null){
log.info("Adding an entry in to the map for the workflow ="+workflowName);
controlMessageStateMap.put(workflowName, true);
return true;
}
}
}
return false;
}
private synchronized void removeWorkflowNameFromProcessingMap(String workflowName) {
if (workflowName != null
&& (controlMessageStateMap.get(workflowName) != null && controlMessageStateMap
.get(workflowName))) {
controlMessageStateMap.remove(workflowName);
log.info("Thread processing the " + workflowName+ " is released from the status map");
}
}