How Future works in Vert.x? - java

I send a message using EventBus and i want to get the reply message into a variable then will return it.this is the code block.
public class MessageExecute {
private static final Logger logger = LoggerFactory.getLogger(MessageExecute.class);
public static <T> T sendMessage(Vertx vertx,String address,T message){
Future<Message<T>> future = Future.future();
vertx.eventBus().send(address, message, future.completer());
future.setHandler(new Handler<AsyncResult<Message<T>>>() {
#Override
public void handle(AsyncResult<Message<T>> event) {
logger.info("received reply message | thread - " + Thread.currentThread().getName());
}
});
boolean notFound = true;
while(notFound){
try{
if(future.result()!= null){
notFound = false;
}
}catch(Exception e){
}
}
return message;
}
}
Actually this is working fine.But some times While block never exit.Its mean future.result() not getting the value ,even after the reply message is received.I don't know this the correct way and I don't have clear idea about how the Futures work in vert.x .Is there any other way to implement these kind of scenario.

I recommend you to read about the Vertx-Sync project - http://vertx.io/docs/vertx-sync/java/
In examples, have the follow example that appears very similar to you case:
EventBus eb = vertx.eventBus();
HandlerReceiverAdaptor<Message<String>> adaptor = streamAdaptor();
eb.<String>consumer("some-address").handler(adaptor);
// Receive 10 messages from the consumer:
for (int i = 0; i < 10; i++) {
Message<String> received1 = adaptor.receive();
System.out.println("got message: " + received1.body());
}

Related

Prevent automatic exit on unsuccessful ActiveMQ reconnect

I have a small spring-boot app set up that connects to one or more Topics on ActiveMQ, which are set in the application's application.properties file on startup - and then sends these messages on to a database.
This is all working fine, but I am having some problems when trying to implement a failover - basically, the app will try to reconnect, but after a certain number of retries, the application process will just automatically exit, preventing the retry (ideally, I would like the app to just retry forever until killed manually or ActiveMQ becomes available again). I have tried explicitly setting the connection options (such as maxReconnectAttempts) in the connection URL (using url.options in application.properties) to -1/0/99999 but none of these seem to be right as the behavior is the same each time. From looking at the advice on Apache's own reference page I would also expect this behavior to be working as default too.
If anyone has any advice to force the app not to quit, I would be very grateful! The bits of my code that I think will matter is below:
#Configuration
public class AmqConfig {
private static final Logger LOG = LogManager.getLogger(AmqConfig.class);
private static final String LOG_PREFIX = "[AmqConfig] ";
private String clientId;
private static ArrayList<String> amqUrls = new ArrayList<>();
private static String amqConnectionUrl;
private static Integer numSubs;
private static ArrayList<String> destinations = new ArrayList<>();
#Autowired
DatabaseService databaseService;
public AmqConfig (#Value("${amq.urls}") String[] amqUrl,
#Value("${amq.options}") String amqOptions,
#Value("${tocCodes}") String[] tocCodes,
#Value("${amq.numSubscribers}") Integer numSubs,
#Value("${clientId}") String clientId) throws UnknownHostException {
Arrays.asList(amqUrl).forEach(url -> {
amqUrls.add("tcp://" + url);
});
String amqServerAddress = "failover:(" + String.join(",", amqUrls) + ")";
String options = Strings.isNullOrEmpty(amqOptions) ? "" : "?" + amqOptions;
this.amqConnectionUrl = amqServerAddress + options;
this.numSubs = Optional.ofNullable(numSubs).orElse(4);
this.clientId = Strings.isNullOrEmpty(clientId) ? InetAddress.getLocalHost().getHostName() : clientId;
String topic = "Consumer." + this.clientId + ".VirtualTopic.Feed";
if (tocCodes.length > 0){
Arrays.asList(tocCodes).forEach(s -> destinations.add(topic + "_" + s));
} else { // no TOC codes = connecting to default feed
destinations.add(topic);
}
}
#Bean
public ActiveMQConnectionFactory connectionFactory() throws JMSException {
LOG.info("{}Connecting to AMQ at {}", LOG_PREFIX, amqConnectionUrl);
LOG.info("{}Using client id {}", LOG_PREFIX, clientId);
ActiveMQConnectionFactory connectionFactory =
new ActiveMQConnectionFactory(amqConnectionUrl);
Connection conn = connectionFactory.createConnection();
conn.setClientID(clientId);
conn.setExceptionListener(new AmqExceptionListener());
conn.start();
destinations.forEach(destinationName -> {
try {
for (int i = 0; i < numSubs; i++) {
Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue(destinationName);
MessageConsumer messageConsumer = session.createConsumer(destination);
messageConsumer.setMessageListener(new MessageReceiver(databaseService, destinationName));
}
} catch (JMSException e) {
LOG.error("{}Error setting up queue # {}", LOG_PREFIX, destinationName);
LOG.error(e.getMessage());
}
});
return connectionFactory;
}
}
public class MessageReceiver implements MessageListener, ExceptionListener {
public static final Logger LOG = LogManager.getLogger(MessageReceiver.class);
private static final String LOG_PREFIX = "[Message Receiver] ";
private DatabaseService databaseService;
public MessageReceiver(DatabaseService databaseService, String destinationName){
this.databaseService = databaseService;
LOG.info("{}Creating MessageReceiver for queue with destination: {}", LOG_PREFIX, destinationName);
}
#Override
public void onMessage(Message message) {
String messageText = null;
if (message instanceof TextMessage) {
TextMessage tm = (TextMessage) message;
try {
messageText = tm.getText();
} catch (JMSException e) {
LOG.error("{} Error getting message from AMQ", e);
}
} else if (message instanceof ActiveMQMessage) {
messageText = message.toString();
} else {
LOG.warn("{}Unrecognised message type, cannot process", LOG_PREFIX);
LOG.warn(message.toString());
}
try {
databaseService.sendMessageNoResponse(messageText);
} catch (Exception e) {
LOG.error("{}Unable to acknowledge message from AMQ. Message: {}", LOG_PREFIX, messageText, e);
}
}
}
public class AmqExceptionListener implements ExceptionListener {
public static final Logger LOG = LogManager.getLogger(AmqExceptionListener.class);
private static final String LOG_PREFIX = "[AmqExceptionListener ] ";
#Override
public void onException(JMSException e){
LOG.error("{}Exception thrown by ActiveMQ", LOG_PREFIX, e);
}
}
The console output I get from my application is just the below (apologies, as it is not much to go off)
[2019-12-12 14:43:30.292] [WARN ] Transport (tcp://[address]:61616) failed , attempting to automatically reconnect: java.io.EOFException
[2019-12-12 14:43:51.098] [WARN ] Failed to connect to [tcp://[address]:61616] after: 10 attempt(s) continuing to retry.
Process finished with exit code 0
Very interesting Question!
Configuring the maxReconnectAttempts=-1 will cause the connection attempts to be retried forever, but what I feel the problem here are as follows:
You are trying to connect to ActiveMQ while creating the Bean at App
startup, If ActiveMQ is not running when APP is starting up, the
Bean creation would retry the connection attempts forever causing a
timeout and not letting the APP to start.
Also when the ActiveMQ stops running midway you are not reattempting the connection as it is done inside #Bean and will only happen on APP startup
Hence the Connection shouldn't happen at Bean creation time, but maybe it can be done after the APP is up (maybe inside a #PostConstruct block)
These are just the pointers, You need to take it forward
Hope this helps!
Good luck!

In akka actor if the message is exception how to call the same for few time with fixed interval between them

I have an utility class from which I am calling a service api. If everything is fine I will get desired output which is in string format.So that's success case. But It may happen that I get an error suppose 404 if the server is down.In such cases I want to call that api for suppose 3 times with 1 second interval between them. If within these three retries I get success from the Api then I will not cal the api and log the result, Or if after 3 retries it still throws error then I will not continue and just log the error.
my Utility class
public class Utils {
public void doOperation(String service_Url) throws Exception {
Object message= someFunction(service_Url);//this function is calling the service api
final ActorSystem system = ActorSystem.create("helloakka");
final ActorRef akkaBot=system.actorOf(Props.create(MyUntypedActor.class), "akkaBot");
akkaBot.tell(message, ActorRef.noSender());
}
}
Here is the Actor
public class MyUntypedActor extends UntypedActor {
#Override
public void onReceive(Object message) {
if (message instanceof HTTPException) {
System.out.println(message);
//Here as it got exception.I want to call the actor 3 times with 1 second interval between them
}
else if (message instanceof String) {
System.out.println(message);
getSender().tell(value, getSelf());
}
else {
unhandled(message);
}
}
}
The objective is test a particular api is working or not using akka actor.If the api returns exception then call that api using actor 3 times with 1 sec interval between each call.If after 3 times we are still getting error then then log the error,if while retrying we get desired output then log it.
I don't know how to achieve it using akka actors.Please guide me if you know.
What you are asking for can be accomplished with a slight modification of your code organization. It would be easier for your Actor to do all of the querying rather than just the queries subsequent to a failure.
You'll first need some way of representing the failures:
public class RequestFailure {
public String request;
public int count;
public RequestFailure(String r, int c) {
request = r;
count = c;
}
}
This class can then be a message type sent to your Actor which will act accordingly:
public class MyUntypedActor extends UntypedActor {
public void onReceive(Object message) {
if(message instanceOf String) {
Object response = someFunction( (String) message);
if(response instanceOf HTTPException) {
Thread.sleep(1000);
getSelf().tell(new RequestFailure((String) message, 1), getSender());
}
else
getSender().tell(response, getSelf())
}
else if(message instanceOf RequestFailure) {
RequestFailure rf = (RequestFailure) message;
if(rf.count <= 3) {
Object response = someFunction(rf.request);
if(response instanceOf HTTPException) {
Thread.sleep(1000);
getSelf().tell(new RequestFailure(rf.request, rf.count + 1), getSender();
}
else
getSender().tell(response, getSelf())
}
}
else
//FIXME : question doesn't specify what to return
// if max count reached
}
}
}
You would then ask the Actor with a request String to start the process.

akka websocket with java, counting clients number, sending message to client

I'm following the akka java websocket tutorial in attempt to create a websocket server. I want to implement 2 extra features:
Being able to display the number of connected clients, but the result
is always 0 or 1 , even when I know I have 100's concurrently
connected clients.
Websocket communication is biDirectional. Currently the server only respond with a message when client sends a message. How do I initiate sending a message from server to client?
Here's original akka java server example code with minimum modification of my client counting implementation:
public class websocketServer {
private static AtomicInteger connections = new AtomicInteger(0);//connected clients count.
public static class MyTimerTask extends TimerTask {
//called every second to display number of connected clients.
#Override
public void run() {
System.out.println("Conncurrent connections: " + connections);
}
}
//#websocket-handling
public static HttpResponse handleRequest(HttpRequest request) {
HttpResponse result;
connections.incrementAndGet();
if (request.getUri().path().equals("/greeter")) {
final Flow<Message, Message, NotUsed> greeterFlow = greeter();
result = WebSocket.handleWebSocketRequestWith(request, greeterFlow);
} else {
result = HttpResponse.create().withStatus(413);
}
connections.decrementAndGet();
return result;
}
public static void main(String[] args) throws Exception {
ActorSystem system = ActorSystem.create();
TimerTask timerTask = new MyTimerTask();
Timer timer = new Timer(true);
timer.scheduleAtFixedRate(timerTask, 0, 1000);
try {
final Materializer materializer = ActorMaterializer.create(system);
final Function<HttpRequest, HttpResponse> handler = request -> handleRequest(request);
CompletionStage<ServerBinding> serverBindingFuture =
Http.get(system).bindAndHandleSync(
handler, ConnectHttp.toHost("****", 1183), materializer);
// will throw if binding fails
serverBindingFuture.toCompletableFuture().get(1, TimeUnit.SECONDS);
System.out.println("Press ENTER to stop.");
new BufferedReader(new InputStreamReader(System.in)).readLine();
timer.cancel();
} catch (Exception e){
e.printStackTrace();
}
finally {
system.terminate();
}
}
//#websocket-handler
/**
* A handler that treats incoming messages as a name,
* and responds with a greeting to that name
*/
public static Flow<Message, Message, NotUsed> greeter() {
return
Flow.<Message>create()
.collect(new JavaPartialFunction<Message, Message>() {
#Override
public Message apply(Message msg, boolean isCheck) throws Exception {
if (isCheck) {
if (msg.isText()) {
return null;
} else {
throw noMatch();
}
} else {
return handleTextMessage(msg.asTextMessage());
}
}
});
}
public static TextMessage handleTextMessage(TextMessage msg) {
if (msg.isStrict()) // optimization that directly creates a simple response...
{
return TextMessage.create("Hello " + msg.getStrictText());
} else // ... this would suffice to handle all text messages in a streaming fashion
{
return TextMessage.create(Source.single("Hello ").concat(msg.getStreamedText()));
}
}
//#websocket-handler
}
Addressing your 2 bullet points below:
1 - you need to attach your metrics to the Message flow - and not to the HttpRequest flow - to effectively count the active connections. You can do this by using watchTermination. Code example for the handleRequest method below
public static HttpResponse handleRequest(HttpRequest request) {
HttpResponse result;
if (request.getUri().path().equals("/greeter")) {
final Flow<Message, Message, NotUsed> greeterFlow = greeter().watchTermination((nu, cd) -> {
connections.incrementAndGet();
cd.whenComplete((done, throwable) -> connections.decrementAndGet());
return nu;
});
result = WebSocket.handleWebSocketRequestWith(request, greeterFlow);
} else {
result = HttpResponse.create().withStatus(413);
}
return result;
}
2 - for the server to independently send messages you could create its Message Flow using Flow.fromSinkAndSource. Example below (this will only send one message):
public static Flow<Message, Message, NotUsed> greeter() {
return Flow.fromSinkAndSource(Sink.ignore(),
Source.single(new akka.http.scaladsl.model.ws.TextMessage.Strict("Hello!"))
);
}
In the handleRequest method you increment and then decrement the counter connections, so at the end the value is always 0.
public static HttpResponse handleRequest(HttpRequest request) {
...
connections.incrementAndGet();
...
connections.decrementAndGet();
return result;
}

readObject intermittent classCastException thrown

I am developing simple Android to Java app, where i want them to communicate with class Message as shown:
public class Message implements Serializable {
/**
*
*/
private static final long serialVersionUID = 120L;
private MessageType type;
private Object content;
public Message(MessageType type, Object content) {
this.type = type;
this.content = content;
}
public String toString() {
StringBuilder string = new StringBuilder();
string.append(type).append(System.getProperty("line.separator"));
if (content != null) {
string.append(content.toString());
}
return string.toString();
}
//======== Getters and Setters ===========
...
}
Object content is always type User which is very simple.
Now, Java server receives Android requests for registration, and answers with list of available users.
There is some other request/response communication but it is not focus here.
Now, sometimes i have no problem with communication between Android clients and Java server, but sometimes classCastException is thrown (either on Android and Java).
I have two threads on both sides for sending and receiving messages on the same socket.
Do i have to make two separate sockets for sending and receiving which i doubt?
Does anyone has an idea or some experience with that?
Is it connected with multithreading or internet connection weakness?
EDIT:
Type for content field in Message is Object for flexibility (i will need it to pass some other classes here, but for now, i always send User class:
public class User implements Serializable {
/**
*
*/
private static final long serialVersionUID = 123L;
private String name;
private String IP;
private int tcpPort;
private int bufferSize;
public User(User user) {
this.name = new String(user.name);
this.IP = new String(user.IP);
this.tcpPort = user.tcpPort;
this.bufferSize = user.bufferSize;
}
public Integer getHashCode() {
return ((String) (IP + tcpPort)).hashCode();
}
public String toString() {
StringBuilder string = new StringBuilder();
string.append(name).append(System.getProperty("line.separator"));
string.append(IP).append(System.getProperty("line.separator"));
string.append(tcpPort).append(System.getProperty("line.separator"));
return string.toString();
}
Here is simple User class.
Here is what i do on server-side:
- Server receives waits for new requests from clients and adds it to the list of new connections - Waiter.pendingConnections (if statement).
- If no new request came thread goes to receiving new messages from existing connections also with soTimeout period.
NOTE: ClientTcpConnector and ServerTcpConnector are wrappers for sockets, input and output streams and stuff (i will not post those classes because of complexity, for now...)
public class DispatchingManager extends Thread {
private final static Logger Log = LogManager.getLogger(Connector.class.getName());
private static final int SERVER_WAITING_PORT = 50001;
public static final ServerTcpConnector syncConnector = new ServerTcpConnector(SERVER_WAITING_PORT);
private final int SOCKET_TIMEOUT_PERIOD = 300;
#Override public void run() {
/* InetAddress remoteIPaddress = syncConnector.waitConnection(); */
ClientTcpConnector awaitedConnector;
boolean isReceived = false;
while (!isInterrupted()) {
//wrapper for socket.accept() with soTimeout argument
awaitedConnector = syncConnector.waitRequest(SOCKET_TIMEOUT_PERIOD);
if (awaitedConnector != null) {
Log.debug("New connection - available");
awaitedConnector.connect();
awaitedConnector.receive();
Waiter.pendingConnections.addFirst(new CompleteUser(null, awaitedConnector));
} else {
for (CompleteUser user : Waiter.onlineConnections.values()) {
awaitedConnector = (ClientTcpConnector) user.getConnector();
isReceived = awaitedConnector.receive(SOCKET_TIMEOUT_PERIOD);
if (isReceived) {
Log.debug("Message received from: " + user.getNetworkUser().getName());
Waiter.pendingConnections.addFirst(user);
isReceived = false;
}
}
}
}
}
}
My server is designed to have few threads which take requests from Waiter.pendingConnections and process them with responses to clients. For now i have only one thread processing pending connections.
On the client side is this (very similar):
Here is the main thread after WelcomeActivity.
...
#Override
public void run() {
tcpConnector = new TcpConnector(remoteServerIP, remoteServerPort);
while (true) {
registerWithServer();
sendTCPThread = new Thread(new Runnable() {
#Override
public void run() {
sendDataToServer();
}
}, "sendTCPThread");
sendTCPThread.start();
waitNewServerMessages();
sendTCPThread.interrupt();
}
}
private void sendDataToServer() {
while (!Thread.interrupted()) {
try {
Message message = getSendingMessageQueue().takeFirst();
tcpConnector.send(message);
Log.d(TAG, "Sent message - " + message.toString());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static boolean waitNewServerMessages() {
Message newMessage;
while (!syncManager.interrupted()) {
newMessage = (Message) tcpConnector.receiveObject();
if (newMessage != null) {
switch (newMessage.getType()) {
case NEW_USER:
onlineUsers.add((User) newMessage.getContent());
updateUIWith((User) newMessage.getContent(),
AppState.IDLE);
break;
case END_ADDING:
break;
case DISCONNECTED_USER:
updateUIWith((User) newMessage.getContent(),
AppState.DISCONNECTED_USER);
break;
case DISCONNECT:
syncManager.interrupt();
break;
default:
break;
}
Log.d(TAG, "Receive message - " + newMessage.toString());
}
}
return true;
}
Basically this is structure of receiving and sending messages, code is too long to be posted completely, but i can do it...
The biggest problem is that i can finish communication sometimes, but sometimes i cannot... I know that me sending receiving code is ok because everything is ok sometimes. But on the other side i don't know how to debug it because it throws this exception sometimes:
11:48:34.002 [Thread-1] DEBUG networkUtils.connectors.Connector - ClassCastException:
java.lang.ClassCastException: java.io.ObjectStreamClass cannot be cast to networkUtils.networkMessage.Message
at networkUtils.connectors.ClientTcpConnector.receive(ClientTcpConnector.java:42) [bin/:?]
at networkUtils.connectors.ClientTcpConnector.receive(ClientTcpConnector.java:75) [bin/:?]
at networkUtils.DispatchingManager.run(DispatchingManager.java:37) [bin/:?]
11:48:34.609 [Thread-1] DEBUG networkUtils.connectors.Connector - ClassCastException:
java.lang.ClassCastException: networkUtils.beans.User cannot be cast to networkUtils.networkMessage.Message
at networkUtils.connectors.ClientTcpConnector.receive(ClientTcpConnector.java:42) [bin/:?]
at networkUtils.connectors.ClientTcpConnector.receive(ClientTcpConnector.java:75) [bin/:?]
at networkUtils.DispatchingManager.run(DispatchingManager.java:37) [bin/:?]
11:48:35.219 [Thread-1] DEBUG networkUtils.connectors.Connector - ClassCastException:
java.lang.ClassCastException: networkUtils.networkMessage.MessageType cannot be cast to networkUtils.networkMessage.Message
at networkUtils.connectors.ClientTcpConnector.receive(ClientTcpConnector.java:42) [bin/:?]
at networkUtils.connectors.ClientTcpConnector.receive(ClientTcpConnector.java:75) [bin/:?]
at networkUtils.DispatchingManager.run(DispatchingManager.java:37) [bin/:?]
I know where is the problem, but i don't know why it happens. :(
Thanks,
Regards

MINA: Performing synchronous write requests / read responses

I'm attempting to perform a synchronous write/read in a demux-based client application with MINA 2.0 RC1, but it seems to get stuck. Here is my code:
public boolean login(final String username, final String password) {
// block inbound messages
session.getConfig().setUseReadOperation(true);
// send the login request
final LoginRequest loginRequest = new LoginRequest(username, password);
final WriteFuture writeFuture = session.write(loginRequest);
writeFuture.awaitUninterruptibly();
if (writeFuture.getException() != null) {
session.getConfig().setUseReadOperation(false);
return false;
}
// retrieve the login response
final ReadFuture readFuture = session.read();
readFuture.awaitUninterruptibly();
if (readFuture.getException() != null) {
session.getConfig().setUseReadOperation(false);
return false;
}
// stop blocking inbound messages
session.getConfig().setUseReadOperation(false);
// determine if the login info provided was valid
final LoginResponse loginResponse = (LoginResponse)readFuture.getMessage();
return loginResponse.getSuccess();
}
I can see on the server side that the LoginRequest object is retrieved, and a LoginResponse message is sent. On the client side, the DemuxingProtocolCodecFactory receives the response, but after throwing in some logging, I can see that the client gets stuck on the call to readFuture.awaitUninterruptibly().
I can't for the life of me figure out why it is stuck here based upon my own code. I properly set the read operation to true on the session config, meaning that messages should be blocked. However, it seems as if the message no longer exists by time I try to read response messages synchronously.
Any clues as to why this won't work for me?
The reason this wasn't working for me was because of an issue elsewhere in my code where I stupidly neglected to implement the message response encoder/decoder. Ugh. Anyway, the code in my question worked as soon as I fixed that.
I prefer this one (Christian Mueller : http://apache-mina.10907.n7.nabble.com/Mina-Client-which-sends-receives-messages-synchronous-td35672.html)
public class UCPClient {
private Map<Integer, BlockingQueue<UCPMessageResponse>> concurrentMap = new ConcurrentHashMap<Integer, BlockingQueue<UCPMessageResponse>>();
// some other code
public UCPMessageResponse send(UCPMessageRequest request) throws Throwable {
BlockingQueue<UCPMessageResponse> queue = new LinkedBlockingQueue<UCPMessageResponse>(1);
UCPMessageResponse res = null;
try {
if (sendSync) {
concurrentMap.put(Integer.valueOf(request.getTransactionReference()), queue);
}
WriteFuture writeFuture = session.write(request);
if (sendSync) {
boolean isSent = writeFuture.await(transactionTimeout, TimeUnit.MILLISECONDS);
if (!isSent) {
throw new TimeoutException("Could not sent the request in " + transactionTimeout + " milliseconds.");
}
if (writeFuture.getException() != null) {
throw writeFuture.getException();
}
res = queue.poll(transactionTimeout, TimeUnit.MILLISECONDS);
if (res == null) {
throw new TimeoutException("Could not receive the response in " + transactionTimeout + " milliseconds.");
}
}
} finally {
if (sendSync) {
concurrentMap.remove(Integer.valueOf(request.getTransactionReference()));
}
}
return res;
}
}
and the IoHandler:
public class InnerHandler implements IoHandler {
// some other code
public void messageReceived(IoSession session, Object message) throws Exception {
if (sendSync) {
UCPMessageResponse res = (UCPMessageResponse) message;
BlockingQueue<UCPMessageResponse> queue = concurrentMap.get(res.getTransactionReference());
queue.offer(res);
}
}
}
I had this exact problem. It turns out that it's because I was doing reads/writes in my IoHandler.sessionCreated() implementation. I moved the processing onto the thread that established the connection, instead of just waiting for the close future.
You must not use your login() function in IoHandler Thread :
If you call IoFuture.awaitUninterruptibly() in the override event function of IoHandler,
IoHandler don't work and get stuck.
You can call login() in other Thread and it will be work properly.

Categories

Resources